<template>
|
<div>
|
<div class="warn-panel">
|
<div class="warn-header">
|
<div class="warn-header-left">
|
<div class="warn-badge"></div>
|
<span class="warn-title">不合格预警</span>
|
</div>
|
<div class="warn-range" @click="handleRangeClick">近7天</div>
|
</div>
|
|
<div class="warn-body">
|
<div class="warn-list" role="list">
|
<div v-for="item in warnings" :key="item.id" class="warn-item" role="listitem" @click="openWarning(item)">
|
<div class="warn-tag" :class="tagClass(item.type)">{{ item.typeText }}</div>
|
<div class="warn-text" :title="item.title">{{ item.title }}</div>
|
<div class="warn-action" @click.stop="openWarning(item)">查看</div>
|
<div class="warn-date">{{ item.date }}</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { computed, getCurrentInstance, ref, onMounted } from 'vue'
|
import Echarts from '@/components/Echarts/echarts.vue'
|
import { qualityUnqualifiedListPage } from '@/api/qualityManagement/nonconformingManagement.js'
|
|
const { proxy } = getCurrentInstance() || {}
|
|
const warnings = ref([
|
{ id: '1', type: 'raw', typeText: '原材料', title: '关于企业原材料调整通知', date: '2024.08.24' },
|
{ id: '2', type: 'raw', typeText: '原材料', title: '关于原材料消耗方案建设的通知', date: '2024.08.24' },
|
{ id: '3', type: 'final', typeText: '成品', title: '成品工作台系统维护计划安排', date: '2024.08.24' },
|
{ id: '4', type: 'final', typeText: '成品', title: '成品工作台系统维护计划安排', date: '2024.08.24' },
|
{ id: '5', type: 'semi', typeText: '半成品', title: 'HRM系统安全升级公告:加强访问控制…', date: '2024.08.24' },
|
{ id: '6', type: 'semi', typeText: '半成品', title: 'HRM系统安全升级公告:加强访问控制…', date: '2024.08.24' },
|
])
|
|
const TAG_COLORS = {
|
raw: '#7C4DFF',
|
final: '#F5A000',
|
semi: '#FF66CC',
|
}
|
|
const tagClass = (type) => {
|
if (type === 'raw') return 'tag-raw'
|
if (type === 'final') return 'tag-final'
|
return 'tag-semi'
|
}
|
|
const pieChartStyle = { width: '100%', height: '100%' }
|
|
const pieOptions = {
|
backgroundColor: 'transparent',
|
textStyle: { color: '#B8C8E0' },
|
}
|
|
const pieTooltip = {
|
trigger: 'item',
|
formatter: (p) => `${p.name}:${p.value}`,
|
}
|
|
const pieData = computed(() => {
|
const counts = { raw: 0, final: 0, semi: 0 }
|
warnings.value.forEach((w) => {
|
const key = w.type in counts ? w.type : 'raw'
|
counts[key] += 1
|
})
|
return [
|
{ name: '原材料', value: counts.raw, itemStyle: { color: TAG_COLORS.raw } },
|
{ name: '半成品', value: counts.semi, itemStyle: { color: TAG_COLORS.semi } },
|
{ name: '成品', value: counts.final, itemStyle: { color: TAG_COLORS.final } },
|
]
|
})
|
|
const pieSeries = computed(() => {
|
return [
|
{
|
type: 'pie',
|
radius: ['0%', '68%'],
|
center: ['50%', '50%'],
|
startAngle: 90,
|
clockwise: true,
|
avoidLabelOverlap: true,
|
label: { show: false },
|
labelLine: { show: false },
|
itemStyle: {
|
borderColor: '#071a3a',
|
borderWidth: 4,
|
shadowBlur: 14,
|
shadowColor: 'rgba(0, 0, 0, 0.35)',
|
},
|
data: pieData.value,
|
},
|
{
|
// 内圈暗环,增强层次
|
type: 'pie',
|
radius: ['70%', '74%'],
|
center: ['50%', '50%'],
|
silent: true,
|
label: { show: false },
|
labelLine: { show: false },
|
itemStyle: { color: 'rgba(78, 228, 255, 0.12)' },
|
data: [1],
|
},
|
]
|
})
|
|
const fetchWarnings = async () => {
|
try {
|
const res = await qualityUnqualifiedListPage({ pageNum: 1, pageSize: 6 })
|
const rows = res?.rows || res?.data?.rows || res?.data || []
|
if (!Array.isArray(rows) || rows.length === 0) return
|
|
warnings.value = rows.slice(0, 6).map((r, idx) => {
|
const typeCode = r.inspectType ?? r.modelType ?? r.type
|
const mappedType = typeCode === 0 || typeCode === '0' ? 'raw' : typeCode === 1 || typeCode === '1' ? 'semi' : 'final'
|
const title = r.title || r.unqualifiedTitle || r.remark || r.unqualifiedReason || '不合格预警'
|
const date = (r.warningTime || r.createTime || r.updateTime || '').slice(0, 10).replace(/-/g, '.') || '2024.08.24'
|
return {
|
id: r.id ?? r.unqualifiedId ?? `${idx}`,
|
type: mappedType,
|
typeText: mappedType === 'raw' ? '原材料' : mappedType === 'semi' ? '半成品' : '成品',
|
title,
|
date,
|
}
|
})
|
} catch (e) {
|
// 接口失败则保持 mock
|
console.error('获取不合格预警失败:', e)
|
}
|
}
|
|
const openWarning = (item) => {
|
const title = `【${item.typeText}】${item.title}`
|
const content = `${title}时间:${item.date}`
|
if (proxy?.$modal?.alert) {
|
proxy.$modal.alert(content)
|
return
|
}
|
// 兜底:没有全局 modal 时用 console
|
console.log('warning:', { ...item })
|
}
|
|
const handleRangeClick = () => {
|
// 先按截图做静态“近7天”,后续有真实筛选需求再接入
|
}
|
|
onMounted(() => {
|
fetchWarnings()
|
})
|
</script>
|
|
<style scoped>
|
.warn-panel {
|
border: 1px solid #1a58b0;
|
padding: 0 18px 18px;
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
height: 100%;
|
box-sizing: border-box;
|
}
|
|
.warn-header {
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
border-bottom: 1px solid;
|
border-image: linear-gradient(
|
270deg,
|
rgba(0, 126, 255, 0) 0%,
|
rgba(0, 126, 255, 0.4549) 35%,
|
#007eff 78%,
|
#007eff 100%
|
)
|
1;
|
padding: 10px 0 6px;
|
}
|
|
.warn-header-left {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.warn-badge {
|
width: 18px;
|
height: 18px;
|
background: linear-gradient(180deg, #2aa8ff 0%, #4ee4ff 100%);
|
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
|
box-shadow: 0 0 12px rgba(78, 228, 255, 0.25);
|
}
|
|
.warn-title {
|
font-weight: 600;
|
font-size: 18px;
|
background: linear-gradient(360deg, #056dff 0%, #43e8fc 100%);
|
-webkit-background-clip: text;
|
-webkit-text-fill-color: transparent;
|
background-clip: text;
|
line-height: 24px;
|
}
|
|
.warn-range {
|
height: 32px;
|
padding: 0 14px;
|
display: inline-flex;
|
align-items: center;
|
justify-content: center;
|
border-radius: 4px;
|
color: #ffffff;
|
font-weight: 600;
|
background: linear-gradient(180deg, rgba(51, 120, 255, 1) 0%, rgba(0, 164, 237, 1) 100%);
|
border: 1px solid rgba(78, 228, 255, 0.25);
|
cursor: pointer;
|
}
|
|
.warn-body {
|
display: grid;
|
gap: 18px;
|
align-items: stretch;
|
min-height: 260px;
|
}
|
|
.warn-list {
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
padding-top: 6px;
|
}
|
|
.warn-item {
|
display: grid;
|
grid-template-columns: 88px 1fr auto 110px;
|
align-items: center;
|
gap: 12px;
|
color: #b8c8e0;
|
font-size: 14px;
|
line-height: 1;
|
padding: 6px 0;
|
border-radius: 4px;
|
transition: background-color 0.2s, color 0.2s;
|
}
|
|
.warn-item:hover {
|
color: #ff4d4f;
|
background-color: rgba(255, 77, 79, 0.06);
|
}
|
|
.warn-item:hover .warn-text {
|
color: #ff4d4f;
|
}
|
|
.warn-tag {
|
height: 28px;
|
padding: 0 10px;
|
border-radius: 4px;
|
display: inline-flex;
|
align-items: center;
|
justify-content: center;
|
font-weight: 700;
|
color: #ffffff;
|
}
|
|
.tag-raw {
|
background: #7c4dff;
|
}
|
|
.tag-final {
|
background: #f5a000;
|
}
|
|
.tag-semi {
|
background: #ff66cc;
|
}
|
|
.warn-text {
|
color: #e8f1ff;
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
}
|
|
.warn-action {
|
color: #ff4d4f;
|
font-weight: 700;
|
white-space: nowrap;
|
cursor: pointer;
|
}
|
|
.warn-date {
|
color: rgba(184, 200, 224, 0.75);
|
white-space: nowrap;
|
}
|
|
.warn-chart {
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
|
.chart-frame {
|
width: 100%;
|
height: 260px;
|
border: 2px dashed rgba(184, 200, 224, 0.35);
|
position: relative;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
background: radial-gradient(circle at 50% 50%, rgba(78, 228, 255, 0.08) 0%, rgba(0, 0, 0, 0) 65%);
|
}
|
|
/* 外圈刻度环 */
|
.chart-frame::before {
|
content: '';
|
position: absolute;
|
width: 220px;
|
height: 220px;
|
border-radius: 50%;
|
background: repeating-conic-gradient(from 0deg, rgba(78, 228, 255, 0.75) 0 1deg, rgba(78, 228, 255, 0) 1deg 9deg);
|
-webkit-mask: radial-gradient(circle, transparent 62%, #000 63%);
|
mask: radial-gradient(circle, transparent 62%, #000 63%);
|
opacity: 0.5;
|
pointer-events: none;
|
}
|
|
/* 十字辅助线 */
|
.chart-frame::after {
|
content: '';
|
position: absolute;
|
width: 240px;
|
height: 240px;
|
background:
|
linear-gradient(to right, rgba(78, 228, 255, 0) 0%, rgba(78, 228, 255, 0.55) 50%, rgba(78, 228, 255, 0) 100%),
|
linear-gradient(to bottom, rgba(78, 228, 255, 0) 0%, rgba(78, 228, 255, 0.55) 50%, rgba(78, 228, 255, 0) 100%);
|
background-size: 100% 1px, 1px 100%;
|
background-position: center, center;
|
background-repeat: no-repeat;
|
opacity: 0.35;
|
pointer-events: none;
|
}
|
</style>
|