| | |
| | | <template> |
| | | <div> |
| | | <PanelHeader title="工单执行效率分析" /> |
| | | <PanelHeader title="不合格产品排名"> |
| | | <template #extra> |
| | | <span class="range-tip">(近 30 天)</span> |
| | | </template> |
| | | </PanelHeader> |
| | | <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 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.rank }}</div> --> |
| | | <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-title">{{ item.productName }}</div> --> |
| | | <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> |
| | | </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 panelList = ref([]) |
| | | |
| | | const format = (percentage) => { |
| | | return `${percentage}%` |
| | | } |
| | | |
| | | const fetchData = () => { |
| | | unqualifiedProductRanking() |
| | | .then((res) => { |
| | | 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)), // 确保百分比在0-100之间 |
| | | } |
| | | }) |
| | | } |
| | | }) |
| | | .catch((err) => { |
| | | console.error('获取工单执行效率分析数据失败:', err) |
| | | }) |
| | | } |
| | | |
| | | 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{ |
| | | background: red; |
| | | border-radius: 20px; |
| | | text-align: center; |
| | | line-height: 32px; |
| | | margin: 0 20px; |
| | | transition: all 0.3s ease; |
| | | position: relative; |
| | | animation: rankItemFadeIn 0.5s ease-out both; |
| | | } |
| | | |
| | | @keyframes rankItemFadeIn { |
| | | from { |
| | | opacity: 0; |
| | | transform: translateX(20px); |
| | | } |
| | | .main-panel-box-right{ |
| | | to { |
| | | opacity: 1; |
| | | transform: translateX(0); |
| | | } |
| | | } |
| | | |
| | | .main-panel-box:hover { |
| | | transform: translateX(5px); |
| | | } |
| | | |
| | | /* 排名项底部光线 */ |
| | | .main-panel-box::after { |
| | | content: ''; |
| | | position: absolute; |
| | | left: 0; |
| | | right: 0; |
| | | bottom: 0; |
| | | height: 1px; |
| | | background: linear-gradient(90deg, transparent, rgba(255, 77, 79, 0.3), transparent); |
| | | opacity: 0; |
| | | transition: opacity 0.3s; |
| | | } |
| | | |
| | | .main-panel-box:hover::after { |
| | | opacity: 1; |
| | | } |
| | | |
| | | .main-panel-box-left { |
| | | background: linear-gradient(135deg, #ff4d4f 0%, #ff7875 100%); |
| | | border-radius: 20px; |
| | | text-align: center; |
| | | line-height: 32px; |
| | | margin: 0 20px; |
| | | padding: 0 12px; |
| | | font-weight: 600; |
| | | font-size: 12px; |
| | | color: white; |
| | | min-width: 60px; |
| | | transition: all 0.3s ease; |
| | | position: relative; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* 排名标签发光 */ |
| | | .main-panel-box-left::before { |
| | | content: ''; |
| | | position: absolute; |
| | | top: 0; |
| | | left: -100%; |
| | | width: 100%; |
| | | height: 100%; |
| | | background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); |
| | | animation: rankShine 2s ease-in-out infinite; |
| | | } |
| | | |
| | | @keyframes rankShine { |
| | | 0% { left: -100%; } |
| | | 50%, 100% { left: 100%; } |
| | | } |
| | | |
| | | .main-panel-box:hover .main-panel-box-left { |
| | | box-shadow: 0 0 15px rgba(255, 77, 79, 0.5); |
| | | } |
| | | |
| | | .main-panel-box-right { |
| | | display: flex; |
| | | flex-direction: column; |
| | | 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; |
| | | flex-direction: column; |
| | | .main-panel-box-right-text{ |
| | | font-size: 12px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | padding-right: 60px; |
| | | justify-content: space-between; |
| | | padding-right: 60px; |
| | | margin-bottom: 4px; |
| | | color: rgba(184, 200, 224, 0.8); |
| | | transition: color 0.3s; |
| | | } |
| | | |
| | | .main-panel-box:hover .main-panel-box-right-text { |
| | | color: rgba(184, 200, 224, 1); |
| | | } |
| | | |
| | | .main-panel-box-right-progress { |
| | | :deep(.el-progress__text) { |
| | | color: white !important; |
| | | } |
| | | .main-panel-box-right-progress{ |
| | | :deep(.el-progress__text){ |
| | | color: white !important; |
| | | } |
| | | :deep(.el-progress-bar__outer) { |
| | | background-color: rgba(78, 228, 255, 0.15); |
| | | } |
| | | :deep(.el-progress-bar__inner) { |
| | | transition: width 0.8s ease; |
| | | } |
| | | } |
| | | } |
| | | |
| | | .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; |
| | | position: relative; |
| | | } |
| | | |
| | | /* 面板角落装饰 */ |
| | | .panel-item-customers::before, |
| | | .panel-item-customers::after { |
| | | content: ''; |
| | | position: absolute; |
| | | width: 15px; |
| | | height: 15px; |
| | | border-color: rgba(255, 77, 79, 0.5); |
| | | border-style: solid; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .panel-item-customers::before { |
| | | top: -1px; |
| | | left: -1px; |
| | | border-width: 2px 0 0 2px; |
| | | } |
| | | |
| | | .panel-item-customers::after { |
| | | bottom: -1px; |
| | | right: -1px; |
| | | border-width: 0 2px 2px 0; |
| | | } |
| | | |
| | | /* 面板背景脉冲 */ |
| | | .panel-item-customers::before { |
| | | content: ''; |
| | | position: absolute; |
| | | inset: 0; |
| | | background: radial-gradient(circle at 50% 50%, rgba(255, 77, 79, 0.03) 0%, transparent 70%); |
| | | pointer-events: none; |
| | | animation: panelPulse 5s ease-in-out infinite; |
| | | z-index: 0; |
| | | } |
| | | |
| | | @keyframes panelPulse { |
| | | 0%, 100% { opacity: 0.3; } |
| | | 50% { opacity: 0.5; } |
| | | } |
| | | |
| | | .range-tip { |
| | | font-size: 14px; |
| | | color: rgba(184, 200, 224, 0.75); |
| | | animation: tipFade 2s ease-in-out infinite; |
| | | } |
| | | |
| | | @keyframes tipFade { |
| | | 0%, 100% { opacity: 0.75; } |
| | | 50% { opacity: 1; } |
| | | } |
| | | </style> |