spring
2 天以前 b12b55a5ee1b34b5a3f9d21533fa9fc909207285
src/views/reportAnalysis/qualityAnalysis/components/center-center.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,355 @@
<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.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>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
import { computed, getCurrentInstance, ref, onMounted } from 'vue'
import Echarts from '@/components/Echarts/echarts.vue'
import { nonComplianceWarning } from '@/api/viewIndex.js'
const { proxy } = getCurrentInstance() || {}
const warnings = ref([])
// å æ¯”数据
const ratios = ref({
  rawMaterialRatio: 0,
  semiFinishedProductRatio: 0,
  finishedProductRatio: 0,
})
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'
}
// æ ¹æ®productTitle映射类型
const mapProductTitleToType = (productTitle) => {
  if (productTitle === '原材料') return 'raw'
  if (productTitle === '半成品') return 'semi'
  if (productTitle === '成品') return 'final'
  return 'raw' // é»˜è®¤å€¼
}
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(() => {
  return [
    { name: '原材料', value: ratios.value.rawMaterialRatio, itemStyle: { color: TAG_COLORS.raw } },
    { name: '半成品', value: ratios.value.semiFinishedProductRatio, itemStyle: { color: TAG_COLORS.semi } },
    { name: '成品', value: ratios.value.finishedProductRatio, 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 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,
        }
      })
    }
  } catch (e) {
    // æŽ¥å£å¤±è´¥åˆ™ä¿æŒç©ºæ•°æ®
    console.error('获取不合格预警失败:', e)
  }
}
const openWarning = (item) => {
  const title = `【${item.parentProductTitle}-${item.productTitle}】${item.title}`
  if (proxy?.$modal?.alert) {
    proxy.$modal.alert(title)
    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: 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;
}
</style>