<template>
|
<div>
|
<PanelHeader title="不合格预警" />
|
<div class="warn-panel">
|
<div class="warn-body">
|
<div class="warn-list" role="list">
|
<!-- loading:展示骨架架子 -->
|
<template v-if="loading">
|
<div v-for="n in 6" :key="`skeleton-${n}`" class="warn-item skeleton" role="listitem">
|
<div class="warn-tag skeleton-block"></div>
|
<div class="warn-text skeleton-block"></div>
|
<div class="warn-action skeleton-block"></div>
|
<div class="warn-date skeleton-block"></div>
|
</div>
|
</template>
|
|
<!-- 空数据:先展示架子 + 空状态 -->
|
<template v-else-if="warnings.length === 0">
|
<div v-for="n in 4" :key="`empty-row-${n}`" class="warn-item is-empty" role="listitem">
|
<div class="warn-tag tag-empty">—</div>
|
<div class="warn-text empty-text">暂无预警信息</div>
|
<div class="warn-action empty-action">查看</div>
|
<div class="warn-date empty-date">--</div>
|
</div>
|
</template>
|
|
<!-- 有数据:正常渲染 -->
|
<template v-else>
|
<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.parentProductTitle }}-{{ item.productTitle }}
|
</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>
|
</template>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script setup>
|
import { getCurrentInstance, ref, onMounted } from 'vue'
|
import { nonComplianceWarning } from '@/api/viewIndex.js'
|
import PanelHeader from "@/views/reportAnalysis/qualityAnalysis/components/PanelHeader.vue";
|
|
const { proxy } = getCurrentInstance() || {}
|
|
const loading = ref(false)
|
const warnings = ref([])
|
|
// 占比数据
|
const ratios = ref({
|
rawMaterialRatio: 0,
|
semiFinishedProductRatio: 0,
|
finishedProductRatio: 0,
|
})
|
|
const tagClass = (type) => {
|
if (type === 'raw') return 'tag-raw'
|
if (type === 'final') return 'tag-final'
|
return 'tag-semi'
|
}
|
|
// 根据productTitle映射类型
|
const mapProductTitleToType = (productTitle) => {
|
if (productTitle === '原材料') return 'raw'
|
if (productTitle === '半成品') return 'semi'
|
if (productTitle === '成品') return 'final'
|
return 'raw' // 默认值
|
}
|
|
const fetchWarnings = async () => {
|
loading.value = true
|
try {
|
const res = await nonComplianceWarning()
|
if (res?.code === 200 && res?.data) {
|
const data = res.data
|
|
// 更新占比数据
|
ratios.value = {
|
rawMaterialRatio: data.rawMaterialRatio ?? 0,
|
semiFinishedProductRatio: data.semiFinishedProductRatio ?? 0,
|
finishedProductRatio: data.finishedProductRatio ?? 0,
|
}
|
|
// 更新警告列表
|
const children = data.children || []
|
warnings.value = children.map((item, idx) => {
|
const type = mapProductTitleToType(item.parentProductTitle)
|
const date = item.date ? item.date.replace(/-/g, '.') : ''
|
return {
|
id: item.id ?? `warning-${idx}`,
|
type,
|
parentProductTitle: item.parentProductTitle || '原材料',
|
productTitle: item.productTitle || '原材料',
|
title: item.description || '不合格预警',
|
date,
|
}
|
})
|
} else {
|
warnings.value = []
|
}
|
} catch (e) {
|
warnings.value = []
|
console.error('获取不合格预警失败:', e)
|
} finally {
|
loading.value = false
|
}
|
}
|
|
const openWarning = (item) => {
|
const title = `【${item.parentProductTitle}-${item.productTitle}】${item.title}`
|
if (proxy?.$modal?.alert) {
|
proxy.$modal.alert(title)
|
return
|
}
|
console.log('warning:', { ...item })
|
}
|
|
onMounted(() => {
|
fetchWarnings()
|
})
|
</script>
|
|
<style scoped>
|
.warn-panel {
|
border: 1px solid #1a58b0;
|
padding: 0 18px 18px;
|
display: flex;
|
flex-direction: column;
|
gap: 12px;
|
height: 90%;
|
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: 130px 1fr auto 110px;
|
align-items: center;
|
gap: 12px;
|
color: #b8c8e0;
|
font-size: 14px;
|
line-height: 1.2;
|
padding: 8px 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;
|
}
|
|
.warn-empty {
|
padding: 10px 0 0;
|
}
|
|
.warn-item.is-empty {
|
opacity: 0.9;
|
cursor: default;
|
}
|
|
.tag-empty {
|
background: rgba(184, 200, 224, 0.22);
|
color: rgba(184, 200, 224, 0.85);
|
font-weight: 700;
|
}
|
|
.empty-text {
|
color: rgba(232, 241, 255, 0.75);
|
}
|
|
.empty-action {
|
color: rgba(255, 77, 79, 0.55);
|
}
|
|
.empty-date {
|
color: rgba(184, 200, 224, 0.45);
|
}
|
|
/* skeleton */
|
.warn-item.skeleton {
|
pointer-events: none;
|
}
|
|
.skeleton-block {
|
height: 18px;
|
border-radius: 4px;
|
background: linear-gradient(90deg, rgba(184, 200, 224, 0.08) 25%, rgba(184, 200, 224, 0.18) 37%, rgba(184, 200, 224, 0.08) 63%);
|
background-size: 400% 100%;
|
animation: shimmer 1.2s ease-in-out infinite;
|
}
|
|
.warn-item.skeleton .warn-tag.skeleton-block {
|
height: 28px;
|
}
|
|
@keyframes shimmer {
|
0% { background-position: 100% 0; }
|
100% { background-position: -100% 0; }
|
}
|
</style>
|