| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="工单执行效率分析" /> |
| | | <PanelHeader title="不合格产品排名" /> |
| | | <div class="main-panel panel-item-customers"> |
| | | <div class="main-panel-container"> |
| | | <div |
| | | style="color: white" |
| | | class="main-panel-box" |
| | | v-for="(item, index) in panelList" |
| | | :key="index" |
| | | > |
| | | <div style="flex: 1" class="main-panel-box-left">Top{{ index + 1 }}</div> |
| | | <div style="flex: 3" class="main-panel-box-right"> |
| | | <div class="main-panel-box-right-text"> |
| | | <span>总数量:{{ item.total }}</span> |
| | | <span>已完成:{{ item.finished }}</span> |
| | | <span>合格率:{{ item.qualifiedRate }}</span> |
| | | </div> |
| | | <div class="main-panel-box-right-progress"> |
| | | <el-progress :percentage="item.percentage" :format="format" /> |
| | | </div> |
| | | <!-- loading:骨架架子 --> |
| | | <template v-if="loading"> |
| | | <div class="main-panel-box skeleton" v-for="n in 6" :key="`sk-${n}`"> |
| | | <div class="main-panel-box-left skeleton-block"></div> |
| | | <div class="main-panel-box-right"> |
| | | <div class="main-panel-box-right-text"> |
| | | <span class="skeleton-block"></span> |
| | | <span class="skeleton-block"></span> |
| | | <span class="skeleton-block"></span> |
| | | </div> |
| | | <div class="main-panel-box-right-progress"> |
| | | <el-progress :percentage="0" :show-text="false" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- empty:先给架子,再给空状态 --> |
| | | <template v-else-if="panelList.length === 0"> |
| | | <div style="color: white" class="main-panel-box is-empty" v-for="n in 4" :key="`empty-${n}`"> |
| | | <div style="flex: 1" class="main-panel-box-left">—</div> |
| | | <div style="flex: 3" class="main-panel-box-right"> |
| | | <div class="main-panel-box-right-text"> |
| | | <span>总数量:--</span> |
| | | <span>已完成:--</span> |
| | | <span>合格率:--</span> |
| | | </div> |
| | | <div class="main-panel-box-right-progress"> |
| | | <el-progress :percentage="0" :format="format" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <!-- data --> |
| | | <template v-else> |
| | | <div style="color: white" class="main-panel-box" v-for="(item, index) in panelList" :key="index"> |
| | | <div style="flex: 1" class="main-panel-box-left">{{ item.productName }}</div> |
| | | <div style="flex: 3" class="main-panel-box-right"> |
| | | <div class="main-panel-box-right-text"> |
| | | <span>总数量:{{ item.total }}</span> |
| | | <span>已完成:{{ item.finished }}</span> |
| | | <span>合格率:{{ item.qualifiedRate }}%</span> |
| | | </div> |
| | | <div class="main-panel-box-right-progress"> |
| | | <el-progress :percentage="item.percentage" :format="format" /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { ref, onMounted } from 'vue' |
| | | import { unqualifiedProductRanking } from '@/api/viewIndex.js' |
| | | import PanelHeader from './PanelHeader.vue' |
| | | const panelList = [ |
| | | { total: 100, finished: 100, qualifiedRate: 100, percentage: 100 }, // Top1 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 200, finished: 180, qualifiedRate: 90, percentage: 90 }, // Top2 |
| | | { total: 150, finished: 120, qualifiedRate: 80, percentage: 80 } // Top3 |
| | | ] |
| | | const format = (percentage) => { |
| | | return `${percentage}%`; |
| | | |
| | | const loading = ref(false) |
| | | const panelList = ref([]) |
| | | |
| | | const format = (percentage) => { |
| | | return `${percentage}%` |
| | | } |
| | | |
| | | const fetchData = async () => { |
| | | loading.value = true |
| | | try { |
| | | const res = await unqualifiedProductRanking() |
| | | if (res?.code === 200 && Array.isArray(res?.data)) { |
| | | const data = res.data |
| | | panelList.value = data.map((item, index) => { |
| | | const total = Number(item.totalCount) || 0 |
| | | const finished = Number(item.completedCount) || 0 |
| | | const passRate = Number(item.passRate) || 0 |
| | | |
| | | return { |
| | | rank: `Top${index + 1}`, |
| | | productName: item.productName || `产品${index + 1}`, |
| | | total: total.toFixed(2), |
| | | finished: finished.toFixed(2), |
| | | qualifiedRate: passRate.toFixed(2), |
| | | percentage: Math.min(100, Math.max(0, passRate)), |
| | | } |
| | | }) |
| | | } else { |
| | | panelList.value = [] |
| | | } |
| | | } catch (err) { |
| | | panelList.value = [] |
| | | console.error('获取不合格产品排名数据失败:', err) |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | onMounted(() => { |
| | | // fetchData() |
| | | fetchData() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .main-panel-box{ |
| | | .main-panel-box { |
| | | display: flex; |
| | | flex-direction: row; |
| | | align-items: center; |
| | | height: 40px; |
| | | .main-panel-box-left{ |
| | | |
| | | .main-panel-box-left { |
| | | background: red; |
| | | border-radius: 20px; |
| | | text-align: center; |
| | | line-height: 32px; |
| | | margin: 0 20px; |
| | | margin: 0 20px; |
| | | } |
| | | .main-panel-box-right{ |
| | | |
| | | .main-panel-box-right { |
| | | display: flex; |
| | | flex-direction: column; |
| | | .main-panel-box-right-text{ |
| | | flex: 1; |
| | | |
| | | .main-panel-box-right-title { |
| | | font-size: 14px; |
| | | font-weight: 600; |
| | | color: #ffffff; |
| | | margin-bottom: 6px; |
| | | } |
| | | |
| | | .main-panel-box-right-text { |
| | | font-size: 12px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding-right: 60px; |
| | | margin-bottom: 4px; |
| | | } |
| | | .main-panel-box-right-progress{ |
| | | :deep(.el-progress__text){ |
| | | |
| | | .main-panel-box-right-progress { |
| | | :deep(.el-progress__text) { |
| | | color: white !important; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | .main-panel-container { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 12px; |
| | | height: 100%; |
| | | overflow-y: auto; |
| | | } |
| | | |
| | | .main-panel { |
| | | display: flex; |
| | | flex-direction: column; |
| | |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 449px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .panel-empty { |
| | | padding-top: 6px; |
| | | } |
| | | |
| | | .main-panel-box.is-empty { |
| | | opacity: 0.9; |
| | | } |
| | | |
| | | .main-panel-box.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; |
| | | } |
| | | |
| | | .main-panel-box-left.skeleton-block { |
| | | height: 28px; |
| | | margin: 0 20px; |
| | | } |
| | | |
| | | .main-panel-box.skeleton .main-panel-box-right-text span.skeleton-block { |
| | | display: inline-block; |
| | | width: 30%; |
| | | } |
| | | |
| | | @keyframes shimmer { |
| | | 0% { |
| | | background-position: 100% 0; |
| | | } |
| | | 100% { |
| | | background-position: -100% 0; |
| | | } |
| | | } |
| | | </style> |