zhangwencui
2 天以前 cc13825bb6b3f4185e3db8aa29e58990ee4e01c0
src/views/reportAnalysis/qualityAnalysis/components/center-center.vue
@@ -1,22 +1,46 @@
<template>
  <div>
    <PanelHeader title="不合格预警" />
    <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>
          <!-- 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>
@@ -24,26 +48,21 @@
</template>
<script setup>
import { computed, getCurrentInstance, ref, onMounted } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import { qualityUnqualifiedListPage } from '@/api/qualityManagement/nonconformingManagement.js'
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 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 loading = ref(false)
const warnings = ref([])
const TAG_COLORS = {
  raw: '#7C4DFF',
  final: '#F5A000',
  semi: '#FF66CC',
}
// 占比数据
const ratios = ref({
  rawMaterialRatio: 0,
  semiFinishedProductRatio: 0,
  finishedProductRatio: 0,
})
const tagClass = (type) => {
  if (type === 'raw') return 'tag-raw'
@@ -51,102 +70,60 @@
  return 'tag-semi'
}
const pieChartStyle = { width: '100%', height: '100%' }
const pieOptions = {
  backgroundColor: 'transparent',
  textStyle: { color: '#B8C8E0' },
// 根据productTitle映射类型
const mapProductTitleToType = (productTitle) => {
  if (productTitle === '原材料') return 'raw'
  if (productTitle === '半成品') return 'semi'
  if (productTitle === '成品') return 'final'
  return 'raw' // 默认值
}
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 () => {
  loading.value = true
  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
    const res = await nonComplianceWarning()
    if (res?.code === 200 && res?.data) {
      const data = res.data
    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,
      // 更新占比数据
      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) {
    // 接口失败则保持 mock
    warnings.value = []
    console.error('获取不合格预警失败:', e)
  } finally {
    loading.value = false
  }
}
const openWarning = (item) => {
  const title = `【${item.typeText}】${item.title}`
  const content = `${title}时间:${item.date}`
  const title = `【${item.parentProductTitle}-${item.productTitle}】${item.title}`
  if (proxy?.$modal?.alert) {
    proxy.$modal.alert(content)
    proxy.$modal.alert(title)
    return
  }
  // 兜底:没有全局 modal 时用 console
  console.log('warning:', { ...item })
}
const handleRangeClick = () => {
  // 先按截图做静态“近7天”,后续有真实筛选需求再接入
}
onMounted(() => {
@@ -161,7 +138,7 @@
  display: flex;
  flex-direction: column;
  gap: 12px;
  height: 100%;
  height: 90%;
  box-sizing: border-box;
}
@@ -170,14 +147,11 @@
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid;
  border-image: linear-gradient(
      270deg,
  border-image: linear-gradient(270deg,
      rgba(0, 126, 255, 0) 0%,
      rgba(0, 126, 255, 0.4549) 35%,
      #007eff 78%,
      #007eff 100%
    )
    1;
      #007eff 100%) 1;
  padding: 10px 0 6px;
}
@@ -235,13 +209,13 @@
.warn-item {
  display: grid;
  grid-template-columns: 88px 1fr auto 110px;
  grid-template-columns: 130px 1fr auto 110px;
  align-items: center;
  gap: 12px;
  color: #b8c8e0;
  font-size: 14px;
  line-height: 1;
  padding: 6px 0;
  line-height: 1.2;
  padding: 8px 0;
  border-radius: 4px;
  transition: background-color 0.2s, color 0.2s;
}
@@ -343,4 +317,53 @@
  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>