From 26cd65ace0c8787b5ff6feff3e6270fb371e1a9c Mon Sep 17 00:00:00 2001 From: zhuguifei <zhuguifei@zhuguifeideiMac.local> Date: 星期四, 11 九月 2025 13:41:23 +0800 Subject: [PATCH] 添加质量预测性维护两个页面 --- src/views/index.vue | 946 ++++++++++++++++- src/assets/images/quailty/q1.png | 0 /dev/null | 856 ---------------- src/api/qms/index/index.ts | 43 src/assets/images/quailty/order.png | 0 src/assets/images/quailty/q2.png | 0 src/views/qms/quality/index.vue | 2 src/views/qms/quality/detail.vue | 1244 +++++++++++++++++++++++ src/assets/images/quailty/q4.png | 0 src/assets/images/quailty/q3.png | 0 src/assets/images/quailty/product.jpg | 0 11 files changed, 2,171 insertions(+), 920 deletions(-) diff --git a/src/api/qms/index/index.ts b/src/api/qms/index/index.ts new file mode 100644 index 0000000..49da19a --- /dev/null +++ b/src/api/qms/index/index.ts @@ -0,0 +1,43 @@ +import request from '@/utils/request'; +import { AxiosPromise } from 'axios'; +import { BatchQuery, BatchVO } from '@/api/qms/batch/types'; + +/** + * 鏌ヨ璐ㄩ噺鍋ュ悍搴� + * @param query + * @returns {*} + */ + +export const queryQualityHealth = (): AxiosPromise<any> => { + return request({ + url: '/qms/quality/health', + method: 'get' + }); +}; + +// 鏌ヨ鎵规 +export const queryBatchList = (query?: BatchQuery): AxiosPromise<BatchVO[]> => { + return request({ + url: '/qms/quality/batch', + method: 'get', + params: query + }); +}; + +export const queryPwbatchList = (): AxiosPromise<any> => { + return request({ + url: '/qms/quality/pwbatch', + method: 'get' + }); +}; + +export const queryNgrank = (): AxiosPromise<any> => { + return request({ + url: '/qms/quality/ngrank', + method: 'get' + }); +}; + + + + diff --git a/src/assets/images/quailty/order.png b/src/assets/images/quailty/order.png new file mode 100644 index 0000000..660a2ac --- /dev/null +++ b/src/assets/images/quailty/order.png Binary files differ diff --git a/src/assets/images/quailty/product.jpg b/src/assets/images/quailty/product.jpg new file mode 100644 index 0000000..03fa25b --- /dev/null +++ b/src/assets/images/quailty/product.jpg Binary files differ diff --git a/src/assets/images/quailty/q1.png b/src/assets/images/quailty/q1.png new file mode 100644 index 0000000..bdd1b58 --- /dev/null +++ b/src/assets/images/quailty/q1.png Binary files differ diff --git a/src/assets/images/quailty/q2.png b/src/assets/images/quailty/q2.png new file mode 100644 index 0000000..607bf00 --- /dev/null +++ b/src/assets/images/quailty/q2.png Binary files differ diff --git a/src/assets/images/quailty/q3.png b/src/assets/images/quailty/q3.png new file mode 100644 index 0000000..a49d2cb --- /dev/null +++ b/src/assets/images/quailty/q3.png Binary files differ diff --git a/src/assets/images/quailty/q4.png b/src/assets/images/quailty/q4.png new file mode 100644 index 0000000..6b7eff7 --- /dev/null +++ b/src/assets/images/quailty/q4.png Binary files differ diff --git a/src/views/index.vue b/src/views/index.vue index 85a21e8..714f6a7 100644 --- a/src/views/index.vue +++ b/src/views/index.vue @@ -1,85 +1,905 @@ <template> <div class="app-container home"> - <el-row :gutter="20"> - <el-col :sm="24" :lg="12" style="padding-left: 20px"> - <h2>鍏板疂杞﹂棿璐ㄩ噺绠$悊绯荤粺</h2> + <div class="p-5"> + <div style="display: flex" class="gap-4"> - </el-col> + <el-card style="width: 100%;flex: 1;" class="mb-4"> - <el-col :sm="24" :lg="12" style="padding-left: 20px"> + <div style="display: flex"> + <div> + <el-statistic :value="batchScore" :suffix="'鍒�'" :precision="1" :value-style="{fontSize: '30px',fontWeight: 'bold'}"> + <template #title> + <div style="display: inline-flex; align-items: center;font-size: 18px"> + 缁煎悎璐ㄩ噺璇勫垎 + <el-tooltip effect="dark" content="" placement="top"> + <el-icon style="margin-left: 4px" :size="12"> + <Warning /> + </el-icon> + </el-tooltip> + </div> + </template> + </el-statistic> + <div class="statistic-footer"> + <div class="footer-item"> + <span>杈冩槰澶�</span> + <span :class="{ 'green': batchScore > 9.4, 'red': batchScore <= 9.4 }"> + <el-icon v-if="batchScore > 9.4"> + <CaretTop /> + </el-icon> + <el-icon v-else> + <CaretBottom /> + </el-icon> + </span> + </div> + </div> + </div> + <div style="flex: 1;display: flex;justify-content: center;align-items: center"> + <el-image style="width: 60px; height: 60px" :src="q1" fit="contain" /> + </div> + </div> - </el-col> - </el-row> - <el-divider /> + + </el-card> + <el-card style="flex: 1" class="mb-4"> + + + <div style="display: flex"> + <div> + <el-statistic :value="qualityRate" :suffix="'%'" :value-style="{fontSize: '30px',fontWeight: 'bold'}"> + <template #title> + <div style="display: inline-flex; align-items: center;font-size: 18px"> + 鑹搧鐜� + <el-tooltip effect="dark" content="" placement="top"> + <el-icon style="margin-left: 4px" :size="12"> + <Warning /> + </el-icon> + </el-tooltip> + </div> + </template> + </el-statistic> + <div class="statistic-footer"> + <div class="footer-item"> + <span>杈冩槰澶�</span> + <span :class="{ 'green': qualityRate >= 95, 'red': qualityRate < 95 }"> + <el-icon v-if="qualityRate >= 95"> + <CaretTop /> + </el-icon> + <el-icon v-else> + <CaretBottom /> + </el-icon> + </span> + </div> + </div> + </div> + <div style="flex: 1;display: flex;justify-content: center;align-items: center"> + <el-image style="width: 60px; height: 60px" :src="q2" fit="contain" /> + </div> + </div> + + + </el-card> + <el-card style="flex: 1" class="mb-4"> + + + <div style="display: flex"> + <div> + <el-statistic :value="checkNum" :suffix="'pcs'" :value-style="{fontSize: '30px',fontWeight: 'bold'}"> + <template #title> + <div style="display: inline-flex; align-items: center;font-size: 18px"> + 妫�娴嬫暟閲� + <el-tooltip effect="dark" content="" placement="top"> + <el-icon style="margin-left: 4px" :size="12"> + <Warning /> + </el-icon> + </el-tooltip> + </div> + </template> + </el-statistic> + <div class="statistic-footer"> + <div class="footer-item"> + <span>杈冩槰澶�</span> + <span :class="{ 'green': batchScore > 1, 'red': batchScore <= 1 }"> + <el-icon v-if="batchScore > 1"> + <CaretTop /> + </el-icon> + <el-icon v-else> + <CaretBottom /> + </el-icon> + </span> + </div> + </div> + </div> + <div style="flex: 1;display: flex;justify-content: center;align-items: center"> + <el-image style="width: 60px; height: 60px" :src="q3" fit="contain" /> + </div> + </div> + + + </el-card> + <el-card style="flex: 1" class="mb-4"> + + + <div style="display: flex"> + <div> + <el-statistic :value="ngCountNum" :suffix="'pcs'" :value-style="{fontSize: '30px',fontWeight: 'bold'}"> + <template #title> + <div style="display: inline-flex; align-items: center;font-size: 18px"> + 寮傚父鏁伴噺 + <el-tooltip effect="dark" content="" placement="top"> + <el-icon style="margin-left: 4px" :size="12"> + <Warning /> + </el-icon> + </el-tooltip> + </div> + </template> + </el-statistic> + <div class="statistic-footer"> + <div class="footer-item"> + <span>杈冩槰澶�</span> + <span :class="{ 'green': ngCountNum < 50, 'red': ngCountNum >= 50 }"> + <el-icon v-if="ngCountNum >= 50"> + <CaretTop /> + </el-icon> + <el-icon v-else> + <CaretBottom /> + </el-icon> + </span> + + <span style="margin-left: 10px">棰勬祴寮傚父鐜�:</span> + <span class="red" v-if="qualityRate > 80"> {{(((100-qualityRate)/ 3)+0.1).toFixed(1) }}% </span> + <span v-else > 3.7%</span> + </div> + </div> + </div> + <div style="flex: 1;display: flex;justify-content: center;align-items: center"> + <el-image style="width: 60px; height: 60px" :src="q4" fit="contain" /> + </div> + </div> + </el-card> + </div> + <!-- 璁惧鍋ュ悍鐘舵�佸彲瑙嗗寲 --> + <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> + <el-card shadow="hover" class="h-420" > + <template #header> + <div class="card-header"> + <span>鍋ュ悍搴﹁瘎鍒�</span> + </div> + </template> + <div ref="healthChartRef" class="h-400 w-full"></div> + </el-card> + + <el-card shadow="hover" class="h-420"> + <template #header> + <div class="card-header"> + <span>璐ㄩ噺棰勮淇℃伅</span> + </div> + </template> +<!-- <div class="grid grid-cols-2 gap-2">--> +<!-- <div class="p-2 bg-blue-50 rounded">--> +<!-- <p class="text-sm text-gray-600">榻愮撼淇濇姢鐢靛帇(榛�)</p>--> +<!-- <p class="text-xl">39.00 V 鈮� result 鈮� 49.00 V</p>--> +<!-- </div>--> +<!-- <div class="p-2 bg-yellow-50 rounded">--> +<!-- <p class="text-sm text-gray-600">璺濈</p>--> +<!-- <p class="text-xl">4.75 mm 鈮� result 鈮� 5.15 mm</p>--> +<!-- </div>--> +<!-- <div class="p-2 bg-red-50 rounded">--> +<!-- <p class="text-sm text-gray-600">鍘嬮檷(榛�)1</p>--> +<!-- <p class="text-xl">0.30 V 鈮� result 鈮� 3.00 V</p>--> +<!-- </div>--> +<!-- <div class="p-2 bg-green-50 rounded">--> +<!-- <p class="text-sm text-gray-600">鍥炲樊</p>--> +<!-- <p class="text-xl">0.05 mm 鈮� result 鈮� 1.00 mm</p>--> +<!-- </div>--> +<!-- <div class="p-2 bg-blue-50 rounded">--> +<!-- <p class="text-sm text-gray-600">婕忕數娴�(榛�)1</p>--> +<!-- <p class="text-xl">result 鈮� 800.00 uA</p>--> +<!-- </div>--> +<!-- <div class="p-2 bg-yellow-50 rounded">--> +<!-- <p class="text-sm text-gray-600"></p>--> +<!-- <p class="text-xl font-bold"></p>--> +<!-- </div>--> +<!-- </div>--> + + <el-table size="large" :data="warnBatchList" style="width: 100%" :border="true" stripe> + <el-table-column prop="batchTime" label="棰勮鏃堕棿" /> + <el-table-column prop="batchCode" label="鎵规鍙�" width="140" /> + <el-table-column prop="gw" label="璁惧/宸ヤ綅" /> + <el-table-column prop="yc" label="寮傚父绫诲瀷" /> + <el-table-column prop="cd" label="涓ラ噸绋嬪害"> + <template #default="{ row }"> + <el-tag :type="getStatusTagType(row.cd)">{{ row.cd }}</el-tag> + </template> + </el-table-column> + + + </el-table> + </el-card> + </div> + + + <el-card shadow="hover" class="mb-6"> + <template #header> + <div class="card-header"> + <span>妫�娴嬫壒娆�</span> + <el-date-picker + v-model="batchDate" + type="date" + placeholder="閫夋嫨鏃ユ湡" + :disabled-date="disabledDate" + :shortcuts="shortcuts" + @change="changeBatchDate" + /> + </div> + </template> + <div class="grid grid-cols-1 gap-4"> + <!-- 杩欓噷灏嗘斁缃璀﹁〃鏍� --> + <el-table v-loading="loading" :data="batchList" style="width: 100%" :border="true" stripe> + <el-table-column prop="batchCode" label="鎵规鍙�" width="180" /> + <el-table-column prop="prodModel" label="浜у搧鍨嬪彿" width="180" /> + <el-table-column prop="num" label="妫�娴嬫暟閲�" width="120" /> + <el-table-column prop="okNum" label="鑹搧鏁伴噺" width="120" /> + <el-table-column prop="ngNum" label="涓嶈壇鏁伴噺" width="120" /> + <el-table-column prop="lpv" label="鑹搧鐜�" width="120"> + <template #default="{ row }"> + <el-tag :type="goodProductsRate(row) >= 95 ? 'success' : 'warning'"> {{ goodProductsRate(row) }} % </el-tag> + </template> + </el-table-column> + <el-table-column prop="whjy" label="缁存姢寤鸿"> + <template #default="{ row }"> + <el-tag :type="goodProductsRate(row) >= 95 ? 'success' : 'warning'"> + {{ goodProductsRate(row) >= 95 ? '鑹搧鐜囨寚鏍囧凡浼樺紓锛岃鍏虫敞璁惧缁煎悎鏁堢巼' : '棰勮锛屾鏌ュ伐鑹哄弬鏁版槸鍚︾鍚堟爣鍑嗭紝浼樺寲鍏抽敭鍙傛暟' }} + </el-tag> + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" width="180"> + <template #default="{ row }"> + <el-button link type="primary" @click="handleDetail(row)">璇︽儏</el-button> + </template> + </el-table-column> + </el-table> + </div> + </el-card> + + + + + + <!-- 澶囦欢淇℃伅 --> + <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> + <!-- 鎵规鍚堟牸鐜囪秼鍔� --> + <el-card shadow="hover" class="h-420"> + <template #header> + <div class="card-header"> + <span>鎵规鍚堟牸鐜囪秼鍔�</span> + </div> + </template> + <div ref="batchRateTrendRef" class="h-400 w-full"></div> + </el-card> + + + <el-card shadow="hover" class="h-420"> + <template #header> + <div class="card-header"> + <span>杩戜竴鍛∟G椤筎OP5</span> + </div> + </template> + <div ref="ngChartRef" class="h-400 w-full"></div> + </el-card> + </div> + </div> </div> </template> <script setup name="Index" lang="ts"> -const goTarget = (url: string) => { - window.open(url, '__blank'); +import { onMounted, ref } from 'vue'; +import { useRouter } from 'vue-router'; +import { ElMessage } from 'element-plus'; +import * as echarts from 'echarts'; + +import q1 from '@/assets/images/quailty/q1.png'; +import q2 from '@/assets/images/quailty/q2.png'; +import q3 from '@/assets/images/quailty/q3.png'; +import q4 from '@/assets/images/quailty/q4.png'; + +import { queryQualityHealth, queryBatchList,queryPwbatchList ,queryNgrank} from '@/api/qms/index'; +import { BatchVO } from '@/api/qms/batch/types'; +import { listBatch } from '@/api/qms/batch'; + +const batchScore = ref(0); +const qualityRate = ref(0); +const checkNum = ref(0); +const ngCountNum = ref(0); + +const batchDate = ref(''); + +const healthChartRef = ref<HTMLElement | null>(null); +const batchRateTrendRef = ref<HTMLElement | null>(null); +const ngChartRef = ref<HTMLElement | null>(null); +const healthChart = ref(); +const batchRateChart = ref(); +const ngChart = ref(); +const router = useRouter(); + +const loading = ref(false); +const batchList = ref<BatchVO[]>([]); +const warnBatchList = ref<any>([]); + + +const gwList = [ + "寰満鐢电郴缁熻殌鍒绘満", + "鏅跺渾閿悎鏈�", + "浼犳劅鍣ㄦ牎鍑嗙珯", + "钖勮啘娌夌Н璁惧", + "鍏夊埢鏈�", + "绂诲瓙娉ㄥ叆鏈�", + "鍖栧鏈烘鎶涘厜璁惧", + "鐑鐞嗙倝", + "灏佽娴嬭瘯绔�", + "婵�鍏変慨璋冭澶�", + "鐢垫�ц兘娴嬭瘯鍙�", + "鍏夊妫�娴嬩华", + "鐪熺┖闀�鑶滄満", + "瓒呭0娉㈡竻娲楁満", + "X灏勭嚎妫�娴嬩华" +] +const ycList = [ + "铓�鍒绘繁搴﹀亸宸�", + "閿悎鍘嬪姏寮傚父", + "鏍″噯鏁版嵁鍋忕Щ", + "娌夌Н閫熺巼娉㈠姩", + "鍏夊埢瀵瑰噯鍋忓樊", + "绂诲瓙娉ㄥ叆鍓傞噺寮傚父", + "鎶涘厜鍘氬害涓嶅潎", + "娓╁害绋冲畾鎬у紓甯�", + "灏佽瀵嗗皝鎬т笉鑹�", + "鐢甸樆鍊煎亸宸�", + "鐏垫晱搴﹀紓甯�", + "鍝嶅簲鏃堕棿瓒呴檺", + "淇″彿婕傜Щ寮傚父", + "绾挎�у害鍋忓樊", + "闆剁偣婕傜Щ寮傚父", + "杩囪浇鎭㈠寮傚父", + "缁濈紭鐢甸樆涓嶈冻", + "浠嬭川鑰愬帇涓嶅悎鏍�", + "棰戠巼鍝嶅簲寮傚父", + "淇″櫔姣斾笉杈炬爣" +]; + +const cdList = [ + "楂�", "楂�", "涓�", "涓�", "楂�", + "涓�", "涓�", "楂�", "楂�", "涓�", + "楂�", "涓�", "涓�", "涓�", "浣�", + "涓�", "楂�", "楂�", "涓�", "浣�" +]; +const getStatusTagType = (status: string) => { + switch (status) { + case '楂�': return 'danger'; + case '涓�': return 'warning'; + case '浣�': return 'info'; + } }; + +function changeBatchDate(date: Date){ + getBatch(date ? date : new Date()) + +} +async function queryWarnBatchList(){ + const queryParams = { + pageNum: 1, + pageSize: 6 + }; + const res:any = await listBatch(queryParams); + + if(res && res.rows){ + warnBatchList.value = []; + res.rows.forEach((item,index) => { + item.gw = gwList[index]; + item.yc = ycList[index]; + item.cd = cdList[index]; + item.batchTime = item.batchTime.substring(0,10); + warnBatchList.value.push(item); + }) + } + +} + +const shortcuts = [ + { + text: '浠婂ぉ', + value: new Date(), + }, + { + text: '鏄ㄥぉ', + value: () => { + const date = new Date() + date.setTime(date.getTime() - 3600 * 1000 * 24) + return date + }, + }, + { + text: '涓�鍛ㄥ墠', + value: () => { + const date = new Date() + date.setTime(date.getTime() - 3600 * 1000 * 24 * 7) + return date + }, + }, +] + +const disabledDate = (time: Date) => { + return time.getTime() > Date.now() +} + + + + + + +const goodProductsRate = (row) => { + if (row.num > 0 && row.okNum > 0) { + return Math.round((row.okNum / row.num) * 100); + } +}; + + + + + + + + + + +const generateWorkOrder = (record) => { + // 宸ュ崟鐢熸垚閫昏緫 + ElMessage.success(`宸蹭负 ${record.name} 鐢熸垚宸ュ崟`); +}; + +const handleDetail = (record) => { + router.push({ path: '/quality/detail', query: { id: record.id } }); +}; + +const initHealthChart = () => { + if (healthChartRef.value) { + healthChart.value = echarts.init(healthChartRef.value); + const option = { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + xAxis: { + type: 'category', + data: ['1鏈�', '2鏈�', '3鏈�', '4鏈�', '5鏈�', '6鏈�', '7鏈�', '8鏈�', '9鏈�', '10鏈�', '11鏈�', '12鏈�'] + }, + yAxis: { + type: 'value', + name: '寰楀垎', + minInterval: 1 + }, + series: [ + { + name: '璐ㄩ噺鏁版嵁', + type: 'bar', + barWidth: '50%', + data: [ + { value: 9.8, itemStyle: { color: '#52c41a' } }, + { value: 9.9, itemStyle: { color: '#52c41a' } }, + { value: 9.7, itemStyle: { color: '#52c41a' } }, + { value: 9.8, itemStyle: { color: '#52c41a' } }, + { value: 9.9, itemStyle: { color: '#52c41a' } }, + { value: 9.8, itemStyle: { color: '#52c41a' } }, + { value: 9.9, itemStyle: { color: '#52c41a' } }, + { value: 9.8, itemStyle: { color: '#52c41a' } }, + { value: 9.8, itemStyle: { color: '#52c41a' } } + ], + label: { + show: true, + position: 'top', + formatter: '{c}鍒�' + } + } + ] + }; + healthChart.value.setOption(option); + + window.addEventListener('resize', () => { + healthChart.value.resize(); + }); + } +}; + +const initBatchRateChart = () => { + if (batchRateTrendRef.value) { + batchRateChart.value = echarts.init(batchRateTrendRef.value); + const option = { + color: ['#FFBF00'], + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'cross', + label: { + backgroundColor: '#6a7985' + } + } + }, + + xAxis: [ + { + splitLine: { show: false }, + type: 'category', + boundaryGap: false, + data: ['08-01', '08-02', '08-03', '08-04', '08-05', '08-06', '08-07'] + } + ], + yAxis: [ + { + splitLine: { show: false }, + type: 'value', + name: '妫�娴嬪悎鏍肩巼', + min: 80, // 璁剧疆鏈�灏忓�间负90 + max: 100, // 璁剧疆鏈�澶у�间负100 + interval: 5, // 璁剧疆鍒诲害闂撮殧涓�1 + axisLabel: { + formatter: '{value}%' // 娣诲姞鐧惧垎姣旂鍙� + } + } + ], + series: [ + { + name: '', + type: 'line', + stack: 'Total', + smooth: true, + lineStyle: { + width: 0 + }, + showSymbol: false, + areaStyle: { + opacity: 0.8, + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: 'rgb(128, 255, 165)' + }, + { + offset: 1, + color: 'rgb(1, 191, 236)' + } + ]) + }, + emphasis: { + focus: 'series' + }, + data: [99, 98, 95, 99, 92, 98, 96], + + } + ] + }; + + option && batchRateChart.value.setOption(option); + + + window.addEventListener('resize', () => { + batchRateChart.value.resize(); + }); + } +}; + +const initNgChart = () => { + if (ngChartRef.value) { + ngChart.value = echarts.init(ngChartRef.value); + const option = { + + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + grid: { + left: '120px', // 澧炲姞宸︿晶杈硅窛锛屼负鏍囩鐣欏嚭鏇村绌洪棿 + top: '0px', + }, + xAxis: { + type: 'value', + boundaryGap: [0, 0.01], + splitLine: { show: false } + }, + yAxis: { + splitLine: { show: false }, + type: 'category', + data: [ + '闈欐�佹秷鑰楃數娴�2', + '鍥炲樊', + '鐭矾淇濇姢娑堣�楃數娴�(榛�)2', + '鍔ㄦ�佹秷鑰楃數娴�2', + '璺濈', + '鍘嬮檷(榛�)2' + ] + }, + series: [ + { + name: '', + type: 'bar', + barWidth: '60%', + data: [100, 90, 80, 70, 60, 10], + itemStyle: { + color: '#faad14' // 淇敼鏌辩姸鍥鹃鑹� + }, + label: { + show: true, + position: 'right', + formatter: '{c}娆�' + } + } + ] + }; + + option && ngChart.value.setOption(option); + + + + window.addEventListener('resize', () => { + ngChart.value.resize(); + }); + } +}; + +const getHealth = async () => { + const res: any = await queryQualityHealth(); + if (res && res.yAxis) { + const xAxisData = []; + const minValue = res.yAxis.reduce((min, item) => { + return min === null ? item.value : Math.min(min, item.value); + }, null); + res.yAxis.forEach((item, index) => { + if (item.value >= 9.5) { + item.itemStyle = { color: '#52c41a' }; + } else if (item.value < 9.5 && item.value >= 9.0) { + item.itemStyle = { color: '#faad14' }; + } else { + item.itemStyle = { color: '#f5222d' }; + } + if (item.value == minValue) { + item.itemStyle = { color: '#faad14' }; + } + xAxisData.push(item); + }); + updateHealthData(res.xAxis, xAxisData); + } +}; + +const getBatch = async (date = new Date()) => { + loading.value = true; + + const res: any = await queryBatchList({ + pageNum: 1, + pageSize: 10, + params: { + startTime: formatDate(date) + ' 00:00:00', + endTime: formatDate(date) + ' 23:59:59' + } + }); + if (!res || !res.rows) { + return; + } + batchList.value = res.rows; + + checkNum.value = res.rows.reduce((sum, item) => sum + (item.num || 0), 0); + + ngCountNum.value = res.rows.reduce((sum, item) => sum + (item.ngNum || 0), 0); + // 璁$畻oknum鎬诲拰 + const totalOkNum = res.rows.reduce((sum, item) => sum + (item.okNum || 0), 0); + // 璁$畻鑹搧鐜� (閬垮厤闄や互0鐨勬儏鍐�) + const yieldRate = checkNum.value > 0 ? (totalOkNum / checkNum.value) * 100 : 0; + qualityRate.value = Number(yieldRate.toFixed(2)); + batchScore.value = Number(((yieldRate/10 )-0.1).toFixed(1)); + if(batchScore.value == -0.1){ + batchScore.value = 0; + } + loading.value = false; +}; + +const getPwbatch = async () => { + const res: any = await queryPwbatchList(); + if (res && res.yAxis) { + updatePwbatchData(res.xAxis, res.yAxis); + } +}; + + +const getNgrank = async () => { + const res: any = await queryNgrank(); + if (res && res.yAxis) { + updateNgrankData(res.xAxis, res.yAxis); + } +}; + +const formatDate = (date) => { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +}; + +function updateHealthData(xAxis, yAxis) { + healthChart.value.setOption({ + xAxis: { + type: 'category', + data: xAxis + }, + series: [ + { + data: yAxis + } + ] + }); +} + + +function updatePwbatchData(xAxis, yAxis) { + batchRateChart.value.setOption({ + xAxis: { + type: 'category', + data: xAxis + }, + series: [ + { + data: yAxis + } + ] + }); +} + + +function updateNgrankData(xAxis, yAxis) { + ngChart.value.setOption({ + yAxis: { + type: 'category', + data: xAxis, + inverse: true, + }, + series: [ + { + data: yAxis + } + ] + }); +} + +onMounted(() => { + queryWarnBatchList(); + initHealthChart(); + initBatchRateChart(); + initNgChart(); + getHealth(); + getBatch(); + getPwbatch(); + getNgrank(); +}); </script> <style scoped lang="scss"> -.home { - blockquote { - padding: 10px 20px; - margin: 0 0 20px; - font-size: 17.5px; - border-left: 5px solid #eee; - } - hr { - margin-top: 20px; - margin-bottom: 20px; - border: 0; - border-top: 1px solid #eee; - } - .col-item { - margin-bottom: 20px; - } +.h-500 { + height: 500px; +} - ul { - padding: 0; - margin: 0; +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; +} + +.grid { + display: grid; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.gap-4 { + gap: 1rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.p-5 { + padding: 1.25rem; +} + +.w-full { + width: 100%; +} + +.h-400 { + height: 400px; +} + +.h-420 { + height: 420px; +} + +.bg-blue-50 { + background-color: #eff6ff; +} + +.bg-yellow-50 { + background-color: #fffbeb; +} + +.bg-red-50 { + background-color: #fef2f2; +} + +.bg-green-50 { + background-color: #f0fdf4; +} + +.rounded { + border-radius: 0.25rem; +} + +.text-sm { + font-size:14px; +} + +.text-xl { + font-size: 14px; +} + +.font-bold { + font-weight: 700; +} + +.text-gray-600 { + color: #4b5563; +} + +@media (min-width: 768px) { + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); } +} - font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 13px; - color: #676a6c; - overflow-x: hidden; +.statistic-footer { + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + font-size: 12px; + color: var(--el-text-color-regular); + margin-top: 16px; +} - ul { - list-style-type: none; - } +.statistic-footer .footer-item { + font-size: 14px; + display: flex; + justify-content: space-between; + align-items: center; +} - h4 { - margin-top: 0px; - } +.statistic-footer .footer-item span:last-child { + display: inline-flex; + align-items: center; + margin-left: 4px; +} - h2 { - margin-top: 10px; - font-size: 26px; - font-weight: 100; - } +.green { + color: var(--el-color-success); +} - p { - margin-top: 10px; - - b { - font-weight: 700; - } - } - - .update-log { - ol { - display: block; - list-style-type: decimal; - margin-block-start: 1em; - margin-block-end: 1em; - margin-inline-start: 0; - margin-inline-end: 0; - padding-inline-start: 40px; - } - } +.red { + color: var(--el-color-error); } </style> diff --git a/src/views/qms/ai/detail.vue b/src/views/qms/ai/detail.vue deleted file mode 100644 index 60d491e..0000000 --- a/src/views/qms/ai/detail.vue +++ /dev/null @@ -1,856 +0,0 @@ -<template> - <div class="device-detail-container"> - <el-page-header @back="goBack"> - <template #content> - <span class="text-large font-600 mr-3"> SMT璐寸墖鏈洪娴嬫�х淮鎶よ鎯�</span> - </template> - </el-page-header> - - <el-row :gutter="16" class="mt-4"> - <!-- 璁惧鍥剧墖鍜岃澶囧熀鏈俊鎭悎骞� --> - <el-col :span="10"> - <el-card class="mb-4" :style="{ height: '440px' }"> - <template #header> - <div class="card-header"> - <span>璁惧淇℃伅</span> - </div> - </template> - <el-row :gutter="16"> - <el-col :span="12"> - <div class="device-image-container"> - <img :src="deviceInfo.imageUrl" alt="璁惧鍥剧墖" class="device-image" /> - </div> - </el-col> - <el-col :span="12"> - <el-descriptions border :column="1" class="custom-descriptions"> - <el-descriptions-item label="璁惧鍚嶇О">{{ deviceInfo.deviceName }}</el-descriptions-item> - <el-descriptions-item label="璁惧绫诲瀷">{{ deviceInfo.deviceType }}</el-descriptions-item> - <el-descriptions-item label="璁惧缂栧彿">{{ deviceInfo.deviceId }}</el-descriptions-item> - <el-descriptions-item label="瀹夎鏃ユ湡">{{ deviceInfo.installDate }}</el-descriptions-item> - <el-descriptions-item label="浣跨敤骞撮檺">{{ deviceInfo.serviceLife }}骞�</el-descriptions-item> - <el-descriptions-item label="褰撳墠鐘舵��"> - <el-tag :type="getStatusTagType(deviceInfo.status)">{{ deviceInfo.status }}</el-tag> - </el-descriptions-item> - </el-descriptions> - </el-col> - </el-row> - </el-card> - </el-col> - - <!-- 璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿 --> - <el-col :span="14"> - <el-card class="mb-4" :style="{ height: '440px' }"> - <template #header> - <div class="card-header"> - <span>璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿</span> - </div> - </template> - <el-row :gutter="16"> - <el-col :span="8"> - <el-statistic - title="鏁翠綋鍋ュ悍搴�" - :value="healthData.overallHealth" - :precision="0" - :suffix="'%'" - :value-style="{ color: healthData.healthColor }" - /> - </el-col> - <el-col :span="8"> - <el-statistic - title="棰勬祴鍓╀綑瀵垮懡" - :value="healthData.predictedLife" - :suffix="'澶�'" - /> - </el-col> - <el-col :span="8"> - <el-statistic - title="鏁呴殰椋庨櫓绛夌骇" - :value="healthData.riskLevel" - :value-style="{ color: healthData.riskColor }" - /> - </el-col> - </el-row> - <div class="mt-4"> - <el-progress - :percentage="healthData.overallHealth" - :color="healthData.healthColor" - :show-text="false" - /> - </div> - - <el-divider content-position="left">棰勬祴鎬х淮鎶ゅ缓璁�</el-divider> - <div class="table-container"> - - <el-table - ref="maintenanceTable" - :data="displayMaintenanceData" - height="246" - size="large" - stripe - > - <el-table-column prop="type" label="缁存姢绫诲瀷" /> - <el-table-column prop="content" label="缁存姢鍐呭" /> - <el-table-column prop="suggestedTime" label="寤鸿鏃堕棿" /> - <el-table-column prop="urgency" label="绱ф�ョ▼搴�"> - <template #default="{ row }"> - <el-tag :type="getUrgencyTagType(row.urgency)">{{ row.urgency }}</el-tag> - </template> - </el-table-column> - <el-table-column label="鎿嶄綔" width="80"> - <template #default="{ row }"> - <el-button link type="primary" size="small" @click="handleMaintenance(row)">澶勭悊</el-button> - </template> - </el-table-column> - </el-table> - - </div> - </el-card> - </el-col> - - <el-col :span="24"> - <el-card class="mb-4"> - <template #header> - <div class="card-header"> - <span>璁惧鏁版嵁</span> - </div> - </template> - <el-row :gutter="16"> - <el-col :span="4"> - <el-statistic title="X杞存�荤Щ鍔ㄨ窛绂�" :value="healthData.xAxisTravel" :precision="2" :suffix="'km'"> - <template #prefix> - <el-icon style="vertical-align: -0.125em"><TrendCharts /></el-icon> - </template> - </el-statistic> - </el-col> - <el-col :span="4"> - <el-statistic title="Y杞存�荤Щ鍔ㄨ窛绂�" :value="healthData.yAxisTravel" :precision="2" :suffix="'km'"> - <template #prefix> - <el-icon style="vertical-align: -0.125em"><TrendCharts /></el-icon> - </template> - </el-statistic> - </el-col> - <el-col :span="4"> - <el-statistic title="鍗″甫娆℃暟" :value="healthData.tapeJamCount" :suffix="'娆�'"> - <template #prefix> - <el-icon style="vertical-align: -0.125em"><Warning /></el-icon> - </template> - </el-statistic> - </el-col> - <el-col :span="4"> - <el-statistic title="鍗℃枡娆℃暟" :value="healthData.materialJamCount" :suffix="'娆�'"> - <template #prefix> - <el-icon style="vertical-align: -0.125em"><Warning /></el-icon> - </template> - </el-statistic> - </el-col> - <el-col :span="4"> - <el-statistic title="鎷兼澘鏁�" :value="healthData.panelCount" :suffix="'鍧�'"> - <template #prefix> - <el-icon style="vertical-align: -0.125em"><Grid /></el-icon> - </template> - </el-statistic> - </el-col> - <el-col :span="4"> - <el-statistic title="鍑洪敊鍋滄満鏃堕棿" :value="healthData.downtime" :suffix="'绉�'"> - <template #prefix> - <el-icon style="vertical-align: -0.125em"><Clock /></el-icon> - </template> - </el-statistic> - </el-col> - </el-row> - </el-card> - </el-col> - </el-row> - - <!-- 瀹炴椂鏁版嵁瓒嬪娍鍥� --> - <el-card class="mb-4"> - <template #header> - <div class="card-header"> - <span>瀹炴椂鏁版嵁瓒嬪娍鍥�</span> - </div> - </template> - <el-row :gutter="16" class="mt-4"> - <el-col :span="8"> - <div id="ambientTemperatureHumidityChart" style="height: 300px;"></div> - </el-col> - <el-col :span="8"> - <div id="motorTemperatureChart" style="height: 300px;"></div> - </el-col> - <el-col :span="8"> - <div id="motorVibrationChart" style="height: 300px;"></div> - </el-col> - </el-row> - - <!-- 璐磋澶�/鍚稿槾 --> - <el-row :gutter="16" class="mt-4"> - <el-col :span="8"> - <div id="nozzleVacuumChart" style="height: 300px;"></div> - </el-col> - <el-col :span="8"> - <div id="nozzleFlowChart" style="height: 300px;"></div> - </el-col> - <el-col :span="8"> - <div id="placementSpeedChart" style="height: 300px;"></div> - </el-col> - </el-row> - </el-card> - - <el-row :gutter="16"> - <!-- 閮ㄤ欢瀵垮懡棰勬祴 --> - <el-col :span="12"> - <el-card class="mb-4" :style="{ height: '560px' }"> - <template #header> - <div class="card-header"> - <span>閮ㄤ欢瀵垮懡棰勬祴</span> - </div> - </template> - <el-table - :data="sparePartData" - size="large" - stripe - > - <el-table-column prop="name" label="閮ㄤ欢鍚嶇О" /> - <el-table-column prop="currentLife" label="鐞嗚瀵垮懡" /> - <el-table-column prop="remainingLife" label="棰勬祴鍓╀綑瀵垮懡" /> - <el-table-column prop="status" label="鐘舵��"> - <template #default="{ row }"> - <el-tag :type="getStatusTagType(row.status)">{{ row.status }}</el-tag> - </template> - </el-table-column> - </el-table> - </el-card> - </el-col> - - <!-- 鍘嗗彶缁存姢璁板綍 --> - <el-col :span="12"> - <el-card class="mb-4" :style="{ height: '560px' }"> - <template #header> - <div class="card-header"> - <span>鍘嗗彶缁存姢璁板綍</span> - </div> - </template> - <el-timeline> - <el-timeline-item - v-for="item in historyData" - :key="item.id" - :type="getTimelineItemType(item.color)" - :timestamp="item.date" - > - {{ item.type }}: {{ item.description }} - </el-timeline-item> - </el-timeline> - </el-card> - </el-col> - </el-row> - </div> -</template> - -<script lang="ts"> -import { defineComponent, reactive, onMounted, onUnmounted, ref, nextTick } from 'vue'; -import { useRouter } from 'vue-router'; -import * as echarts from 'echarts'; -import { - TrendCharts, - Warning, - Grid, - Clock -} from '@element-plus/icons-vue'; -import { ElMessage } from 'element-plus'; -import img from '@/assets/images/JUKI.png'; - - -export default defineComponent({ - name: 'SmtMachineDetail', - components: { - TrendCharts, - Warning, - Grid, - Clock - }, - setup() { - const router = useRouter(); - - const goBack = () => { - router.go(-1); - }; - - // 妯℃嫙鏁版嵁 - const deviceInfo = reactive({ - deviceName: 'SMT璐寸墖鏈�', - deviceType: '璐寸墖鏈�', - deviceId: 'GPC2012A101', - installDate: '2012-06-08', - serviceLife: 15, - status: '杩愯涓�', - statusColor: '#52c41a', - imageUrl: img - }); - - const healthData = reactive({ - overallHealth: 82, - healthColor: '#52c41a', - predictedLife: 635, - riskLevel: '浣庨闄�', - riskColor: '#1a7ac4', - xAxisTravel: 300.179, - yAxisTravel: 233.39, - tapeJamCount: 6, - materialJamCount: 15, - panelCount: 2480, - downtime: 4.5, - }); - - const maintenanceData = reactive([ - { - key: '1', - type: '1鍙疯创瑁呯郴缁熺淮鎶�', - content: '鍚稿槾鐪熺┖鍘嬪姏鍋忎綆', - suggestedTime: '2025-07-05', - urgency: '涓瓑' - }, - { - key: '6', - type: '4鍙疯创瑁呯郴缁熺淮鎶�', - content: 'T杞撮┈杈惧鍛藉憡鎬�', - suggestedTime: '2025-07-05', - urgency: '涓瓑' - }, - { - key: '2', - type: '杩愬姩绯荤粺淇濆吇', - content: 'X/Y杞翠己鏈嶇數鏈烘鼎婊戞鏌�', - suggestedTime: '2025-06-20', - urgency: '浣�' - }, - { - key: '3', - type: '渚涙枡绯荤粺妫�鏌�', - content: '椋炶揪鍗″甫/鍗℃枡娆℃暟娓呴浂', - suggestedTime: '2025-06-10', - urgency: '浣�' - }, - { - key: '4', - type: '2鍙疯创瑁呯郴缁熺淮鎶�', - content: '鐪熺┖鍘嬪姏涓嶇ǔ瀹�', - suggestedTime: '2025-07-05', - urgency: '浣�' - }, - { - key: '5', - type: '3鍙疯创瑁呯郴缁熺淮鎶�', - content: 'Z杞撮┈杈剧數娴佸紓甯�', - suggestedTime: '2025-07-05', - urgency: '浣�' - }, - { - key: '7', - type: '5鍙疯创瑁呭ご缁存姢', - content: '鐪熺┖鍘嬪姏涓嶇ǔ瀹�', - suggestedTime: '2025-07-05', - urgency: '浣�' - }, - { - key: '8', - type: '6鍙疯创瑁呭ご缁存姢', - content: '鐪熺┖鍘嬪姏涓嶇ǔ瀹�', - suggestedTime: '2025-07-05', - urgency: '浣�' - }, - ]); - - const getUrgencyTagType = (urgency: string) => { - switch (urgency) { - case '楂�': return 'danger'; - case '涓瓑': return 'warning'; - case '浣�': return 'success'; - default: return 'info'; - } - }; - - const getStatusTagType = (status: string) => { - switch (status) { - case '杩愯涓�': return 'success'; - case '棰勮': return 'warning'; - case '鍗遍櫓': return 'danger'; - case '鑹ソ': return 'success'; - default: return 'info'; - } - }; - - const getTimelineItemType = (color: string) => { - switch (color) { - case 'green': return 'success'; - case 'red': return 'danger'; - default: return 'primary'; - } - }; - - // const fetchDeviceData = async () => { - // try { - // const res = await getDeviceDataSmt(); - // Object.assign(healthData, res); - // } catch (error) { - // console.error('鑾峰彇璁惧鏁版嵁澶辫触:', error); - // } - // }; - - let deviceDataInterval: number | undefined; - // fetchDeviceData(); - // deviceDataInterval = setInterval(fetchDeviceData, 3000); - - const sparePartData = reactive([ - { - key: '1', - name: '1鍙疯创瑁呯郴缁烼杞翠己鏈嶇數鏈�', - currentLife: '15000灏忔椂', - remainingLife: '1451灏忔椂', - status: '棰勮' - }, - { - key: '5', - name: '1鍙疯创瑁呯郴缁焃杞翠己鏈嶇數鏈�', - currentLife: '15000灏忔椂', - remainingLife: '7521灏忔椂', - status: '鑹ソ' - }, - { - key: '9', - name: '1鍙疯创瑁呯郴缁熺湡绌虹數纾侀榾', - currentLife: '10000灏忔椂', - remainingLife: '2154灏忔椂', - status: '鑹ソ' - }, - { - key: '2', - name: '1鍙疯创瑁呭ご', - currentLife: '1000000娆�', - remainingLife: '425542娆�', - status: '鑹ソ' - }, - { - key: '6', - name: '2鍙疯创瑁呯郴缁烼杞翠己鏈嶇數鏈�', - currentLife: '15000灏忔椂', - remainingLife: '7540灏忔椂', - status: '鑹ソ' - }, - { - key: '7', - name: '2鍙疯创瑁呯郴缁焃杞翠己鏈嶇數鏈�', - currentLife: '15000灏忔椂', - remainingLife: '7521灏忔椂', - status: '鑹ソ' - }, - { - key: '9', - name: '2鍙疯创瑁呯郴缁熺湡绌虹數纾侀榾', - currentLife: '10000灏忔椂', - remainingLife: '2154灏忔椂', - status: '鑹ソ' - }, - { - key: '8', - name: '2鍙疯创瑁呭ご', - currentLife: '1000000娆�', - remainingLife: '751251娆�', - status: '鑹ソ' - }, - { - key: '3', - name: '椋炶揪', - currentLife: '96涓湀', - remainingLife: '43涓湀', - status: '鑹ソ' - }, - ]); - - const historyData = reactive([ - { - id: '1', - date: '2025-07-22', - type: '瀹氭湡淇濆吇', - description: '瀹屾垚鏈堝害淇濆吇锛屼笣鏉嗗杞ㄦ敞娌癸紝鎶涙枡娓呯悊', - color: 'green' - }, - { - id: '1', - date: '2025-06-18', - type: '瀹氭湡淇濆吇', - description: '瀹屾垚鏈堝害淇濆吇锛屾槗鎹熶欢鏇存崲', - color: 'green' - }, - { - id: '1', - date: '2025-05-15', - type: '瀹氭湡淇濆吇', - description: '瀹屾垚瀛e害淇濆吇锛屾鏌ヨ繍鍔ㄧ郴缁熸鼎婊�', - color: 'green' - }, - { - id: '1', - date: '2025-04-20', - type: '瀹氭湡淇濆吇', - description: '瀹屾垚鏈堝害淇濆吇锛岄槻灏樿繃婊ょ綉娓呯悊锛屽杞ㄦ敞娌�', - color: 'green' - }, - { - id: '1', - date: '2025-03-16', - type: '瀹氭湡淇濆吇', - description: '瀹屾垚鏈堝害淇濆吇锛岀湡绌哄�兼牎鍑�', - color: 'green' - }, - { - id: '1', - date: '2025-02-13', - type: '瀹氭湡淇濆吇', - description: '瀹屾垚鏈堝害淇濆吇锛屽惛鍢存鏌ユ洿鎹紝鎶涙枡娓呯悊锛岀浉鏈哄弬鏁版牎鍑�', - color: 'green' - }, - { - id: '2', - date: '2025-01-20', - type: '鏁呴殰缁翠慨', - description: '淇鍚稿槾鍫靛闂', - color: 'red' - }, - { - id: '3', - date: '2025-01-15', - type: '瀹氭湡淇濆吇', - description: '瀹屾垚骞村害淇濆吇锛岃创瑁呯郴缁熸牎鍑嗭紝鏄撴崯浠舵洿鎹紝鏍″噯瑙嗚绯荤粺', - color: 'green' - } - ]); - - const handleMaintenance = (record: any) => { - console.log('澶勭悊缁存姢寤鸿:', record); - ElMessage.info(`澶勭悊缁存姢寤鸿: ${record.type}`); - }; - - const maintenanceTable = ref(); - - // 鐢ㄤ簬鏄剧ず鐨勭淮鎶ゆ暟鎹紙鏀寔鏃犻檺婊氬姩锛� - const displayMaintenanceData = ref([...maintenanceData]); - let scrollInterval: ReturnType<typeof setInterval> | undefined; - - // 鍚姩鑷姩婊氬姩 - const startAutoScroll = () => { - scrollInterval = setInterval(() => { - if (displayMaintenanceData.value.length > 0) { - // 灏嗙涓�椤圭Щ鍒版渶鍚� - const firstItem = displayMaintenanceData.value.shift(); - if (firstItem) { - displayMaintenanceData.value.push(firstItem); - } - } - }, 1000); // 姣�3绉掓粴鍔ㄤ竴娆� - }; - - onMounted(() => { - nextTick(() => { - initCharts(); - startAutoScroll(); // 鍚姩鑷姩婊氬姩 - }); - }); - - onUnmounted(() => { - if (deviceDataInterval) { - clearInterval(deviceDataInterval); - } - if (scrollInterval) { - clearInterval(scrollInterval); - } - }); - - const initCharts = () => { - // 鍥捐〃鍒濆鍖栦唬鐮侊紙涓庝箣鍓嶇浉鍚岋級 - // 鍒濆鍖栧浘琛� - const initChart = (chartId: string, title: string, seriesConfig: Array<{ name: string, data: any[], unit: string, baseValue: number, fluctuation: number, color: string }>) => { - const chart = echarts.init(document.getElementById(chartId)); - - const updateChart = () => { - // 鐢熸垚鏂扮殑鏁版嵁鐐� - const now = new Date(); - const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; - - seriesConfig.forEach(s => { - const newValue = (s.baseValue + (Math.random() * 2 - 1) * s.fluctuation).toFixed(2); - s.data.push({ - time, - value: newValue - }); - if (s.data.length > 24) { - s.data.shift(); - } - }); - - const option = { - title: { - text: title, - left: 'center' - }, - tooltip: { - trigger: 'axis', - formatter: (params) => { - let result = `${params[0].axisValueLabel}<br/>`; - params.forEach(param => { - result += `${param.marker} ${param.seriesName}: ${param.data}${seriesConfig[param.seriesIndex].unit}<br/>`; - }); - return result; - } - }, - grid: { - left: '8%', // 澧炲姞宸︿晶杈硅窛 - right: '5%', - bottom: '10%', - containLabel: true - }, - xAxis: { - type: 'category', - data: seriesConfig[0].data.map(item => item.time) - }, - yAxis: seriesConfig.map((s, index) => ({ - type: 'value', - name: s.name, - position: index === 0 ? 'left' : 'right', - axisLine: { - show: true, - }, - axisLabel: { - formatter: (value) => `${value}${s.unit}` - } - })), - series: seriesConfig.map((s, index) => ({ - name: s.name, - data: s.data.map(item => parseFloat(item.value)), - type: 'line', - smooth: true, - yAxisIndex: index, - lineStyle: { - width: 2, - color: s.color - }, - areaStyle: { - opacity: 0.8, - color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ - { - offset: 0, - color: 'rgba(84,112,198,0.3)' - }, - { - offset: 1, - color: 'rgba(84,112,198,0)' - } - ]) - }, - } - )), - }; - chart.setOption(option); - }; - - seriesConfig.forEach(s => { - s.data = []; - // 鐢熸垚鍒濆鏁版嵁鐐癸紙60涓偣锛�5鍒嗛挓鏁版嵁锛� - for (let i = 0; i < 24; i++) { - const now = new Date(Date.now() - (24 - i) * 5000); // 鐢熸垚杩囧幓5鍒嗛挓鐨勬暟鎹� - const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; - const newValue = (s.baseValue + (Math.random() * 2 - 1) * s.fluctuation).toFixed(2); - s.data.push({ - time, - value: newValue - }); - } - }) - - - // 鍒濆娓叉煋 - updateChart(); - - // 姣�5绉掓洿鏂颁竴娆℃暟鎹� - const intervalId = setInterval(updateChart, 5000); - - window.addEventListener('resize', () => { - chart.resize(); - }); - - - }; - - // 鍒濆鍖栧悇涓浘琛� - initChart('motorTemperatureChart', '浜ч噺', [ - { name: '浜ч噺', data: [], unit: 'pcs/h', baseValue: 175, fluctuation: 25, color: '#5470C6' }, - ]); - - initChart('motorVibrationChart', '鎶涙枡鐜�', [ - { name: '鎶涙枡鐜�', data: [], unit: '%', baseValue: 0.15, fluctuation: 0.01, color: '#5470C6' }, - ]); - - initChart('nozzleVacuumChart', '鍚稿槾鐪熺┖鍘嬪姏', [ - { name: '鍘嬪姏', data: [], unit: 'kPa', baseValue: -45, fluctuation: 5, color: '#5470C6' }, - ]); - - initChart('nozzleFlowChart', '鍚稿槾鍚规皵鍘嬪姏', [ - { name: '鍘嬪姏', data: [], unit: 'kPa', baseValue: 20, fluctuation: 5, color: '#5470C6' }, - ]); - - initChart('placementSpeedChart', '璐磋閫熷害', [ - { name: '閫熷害', data: [], unit: 'chips/h', baseValue: 9000, fluctuation: 1500, color: '#5470C6' }, - ]); - - const ambientTemperatureData: any[] = []; - const ambientHumidityData: any[] = []; - initChart('ambientTemperatureHumidityChart', '鐜娓╂箍搴�', [ - { name: '娓╁害', data: ambientTemperatureData, unit: '掳C', baseValue: 25, fluctuation: 1, color: '#5470C6' }, - { name: '婀垮害', data: ambientHumidityData, unit: '%', baseValue: 60, fluctuation: 5, color: '#91cc75' } - ]); - }; - - return { - deviceInfo, - healthData, - maintenanceData, - displayMaintenanceData, - sparePartData, - historyData, - goBack, - getUrgencyTagType, - getStatusTagType, - getTimelineItemType, - handleMaintenance, - maintenanceTable, - - }; - } -}); -</script> - -<style scoped> -.device-detail-container { - padding: 16px; - background: #f0f2f5; -} - -.page-header-content { - display: flex; - flex-direction: column; -} - -.page-header-title { - font-size: 18px; - font-weight: bold; -} - -.page-header-subtitle { - font-size: 14px; - color: #666; - margin-top: 4px; -} - -.card-header { - font-weight: bold; -} - -.device-image-container { - display: flex; - justify-content: center; - align-items: center; - height: 330px; - overflow: hidden; -} - -.device-image { - max-width: 100%; - max-height: 100%; - object-fit: contain; -} - -.mt-4 { - margin-top: 1rem; -} - -.mb-4 { - margin-bottom: 1rem; -} - - -.custom-descriptions { - height: 357px; /* 璁剧疆鏁翠釜鎻忚堪鍒楄〃鐨勯珮搴� */ -} - -.custom-descriptions :deep(.el-descriptions__body) { - height: 100%; -} - -.custom-descriptions :deep(.el-descriptions__table) { - height: 100%; -} - -.custom-descriptions :deep(.el-descriptions__table tbody) { - height: 100%; - display: flex; - flex-direction: column; -} - -.custom-descriptions :deep(.el-descriptions__table tr) { - flex: 1; - display: flex; -} - -.custom-descriptions :deep(.el-descriptions__table td) { - flex: 1; - display: flex; - align-items: center; -} - -.custom-descriptions :deep(.el-descriptions__table .el-descriptions__label) { - display: flex; - align-items: center; - justify-content: flex-start; -} - -.custom-descriptions :deep(.el-descriptions__table .el-descriptions__content) { - display: flex; - align-items: center; - justify-content: flex-start; -} - -/* -.table-container { - position: relative; - height: 246px; - overflow: hidden; -} - -.table-container :deep(.el-table__body-wrapper) { - overflow: hidden; -} - -.table-container :deep(.el-table__body) { - animation: scrollTable 20s linear infinite; -} - -.table-container:hover :deep(.el-table__body) { - animation-play-state: paused; -} - -@keyframes scrollTable { - 0% { - transform: translateY(0); - } - 100% { - transform: translateY(-100%); - } -} - - -.table-container :deep(.el-table__header-wrapper) { - position: sticky; - top: 0; - background: white; - z-index: 10; -} -*/ -</style> diff --git a/src/views/qms/quality/detail.vue b/src/views/qms/quality/detail.vue new file mode 100644 index 0000000..a15460b --- /dev/null +++ b/src/views/qms/quality/detail.vue @@ -0,0 +1,1244 @@ +<template> + <div class="device-detail-container"> + <el-page-header @back="goBack"> + <template #content> + <span class="text-large font-600 mr-3"> 鎵规璇︽儏鍒嗘瀽</span> + </template> + </el-page-header> + + <el-row :gutter="16" class="mt-4"> + <!-- 璁惧鍥剧墖鍜岃澶囧熀鏈俊鎭悎骞� --> + <el-col :span="10"> + <el-card class="mb-4" :style="{ height: '440px' }"> + <template #header> + <div class="card-header"> + <span>鐢熶骇鎵规淇℃伅</span> + </div> + </template> + <el-row :gutter="16"> + <el-col :span="12"> + <div class="device-image-container"> + <img :src="img" alt="璁惧鍥剧墖" class="device-image" /> + </div> + </el-col> + <el-col :span="12"> + <el-descriptions border :column="1" class="custom-descriptions"> + <el-descriptions-item label="鎵规缂栧彿">{{ batch.batchCode }}</el-descriptions-item> + <el-descriptions-item label="浜у搧鍨嬪彿">{{ batch.prodModel }}</el-descriptions-item> + <el-descriptions-item label="鐢熶骇绾�">A绾�</el-descriptions-item> + <el-descriptions-item label="鐢熶骇鏃堕棿">{{ batch.batchTime }}</el-descriptions-item> + <el-descriptions-item label="鐢熶骇鐘舵��"> + <el-tag v-if="batchIsToday(batch.batchTime)" type="primary">鐢熶骇涓�</el-tag> + <el-tag v-else type="success">宸插畬鎴�</el-tag> + </el-descriptions-item> + </el-descriptions> + </el-col> + </el-row> + </el-card> + </el-col> + + <!-- 璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿 --> + <el-col :span="14"> + <el-card class="mb-4" :style="{ height: '440px' }"> + <template #header> + <div class="card-header"> + <span>鐢熶骇璐ㄩ噺鍋ュ悍鐘舵�佷笌缁存姢寤鸿</span> + </div> + </template> + <el-row :gutter="16"> + <el-col :span="8"> + <el-statistic + title="鏁翠綋鍋ュ悍搴�" + :value="healthRate" + :precision="0" + :suffix="'%'" + :value-style="{ color: healthData.healthColor }" + /> + </el-col> + <el-col :span="8"> + <el-statistic + title="杩囩▼鑳藉姏鎸囨暟 (Cpk)" + :precision="2" + :value="healthData.predictedLife" + + /> + </el-col> + <el-col :span="8"> + <el-statistic + title="璐ㄩ噺椋庨櫓绛夌骇" + :value="healthData.riskLevel" + :value-style="{ color: healthData.riskColor }" + /> + </el-col> + </el-row> + <div class="mt-4"> + <el-progress + :percentage="healthData.overallHealth" + :color="healthData.healthColor" + :show-text="false" + /> + </div> + + <el-divider content-position="left">棰勬祴鎬х淮鎶ゅ缓璁�</el-divider> + <div class="table-container"> + + <el-table + ref="maintenanceTable" + :data="displayMaintenanceData" + height="246" + size="large" + stripe + > + <el-table-column prop="type" label="缁存姢绫诲瀷" /> + <el-table-column prop="content" label="缁存姢鍐呭" /> + <el-table-column prop="suggestedTime" label="寤鸿鏃堕棿" /> + <el-table-column prop="urgency" label="绱ф�ョ▼搴�"> + <template #default="{ row }"> + <el-tag :type="getUrgencyTagType(row.urgency)">{{ row.urgency }}</el-tag> + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" width="80"> + <template #default="{ row }"> + <el-button link type="primary" size="small" @click="handleMaintenance(row)">澶勭悊</el-button> + </template> + </el-table-column> + </el-table> + + </div> + </el-card> + </el-col> + + <el-col :span="24"> + <el-card class="mb-4"> + <template #header> + <div class="card-header"> + <span>璐ㄩ噺鎸囨爣姹囨��</span> + </div> + </template> + <el-row :gutter="16"> + <el-col :span="8" style="display: flex;justify-content: center;align-items: center"> + <el-statistic title="鏈壒鑹巼" :value="healthRate" :precision="2" :suffix="'%'"> + <template #prefix> +<!-- <el-icon style="vertical-align: -0.125em"><TrendCharts /></el-icon>--> + </template> + </el-statistic> + </el-col> + + <el-col :span="8" style="display: flex;justify-content: center;align-items: center"> + <el-statistic title="涓嶈壇鍝佹暟" :value="batch.ngNum" :suffix="'pcs'"> + <template #prefix> +<!-- <el-icon style="vertical-align: -0.125em"><TrendCharts /></el-icon>--> + </template> + </el-statistic> + </el-col> + + + + <el-col :span="8" style="display: flex;justify-content: center;align-items: center"> + <el-statistic title="浣庝簬骞冲潎姘村钩" :value="belowRate" :precision="2" :suffix="'%'"> + <template #prefix> +<!-- <el-icon style="vertical-align: -0.125em"><TrendCharts /></el-icon>--> + </template> + </el-statistic> + </el-col> + </el-row> + + + + </el-card> + </el-col> + </el-row> + + <!-- 瀹炴椂鏁版嵁瓒嬪娍鍥� --> + <el-card class="mb-4"> + <template #header> + <div class="card-header"> + <span>鐢熶骇鏁版嵁</span> + </div> + </template> + <el-row :gutter="16" class="mt-4"> + <el-col :span="8"> + <div id="ambientTemperatureHumidityChart" style="height: 300px;"></div> + </el-col> + <el-col :span="8"> + <div id="motorTemperatureChart" style="height: 300px;"></div> + </el-col> + <el-col :span="8"> + <div id="motorVibrationChart" style="height: 300px;"></div> + </el-col> + </el-row> + + <!-- 璐磋澶�/鍚稿槾 --> + <el-row :gutter="16" class="mt-4"> + <el-col :span="8"> + <div id="nozzleVacuumChart" style="height: 300px;"></div> + </el-col> + <el-col :span="8"> + <div id="nozzleFlowChart" style="height: 300px;"></div> + </el-col> + <el-col :span="8"> + <div id="placementSpeedChart" style="height: 300px;"></div> + </el-col> + </el-row> + </el-card> + + <el-row :gutter="16"> + <!-- 閮ㄤ欢瀵垮懡棰勬祴 --> + <el-col :span="12"> + <el-card class="mb-4" :style="{ height: '560px' }"> + <template #header> + <div class="card-header"> + <span>鍏抽敭閮ㄤ欢瀵垮懡棰勬祴</span> + <el-tooltip content="鍩轰簬璁惧杩愯鏁版嵁鍜屼紶鎰熷櫒鐩戞祴鐨勯娴嬫�х淮鎶ゅ垎鏋�" placement="top"> + <el-icon><Warning /></el-icon> + </el-tooltip> + </div> + </template> + + + + + <div class="health-summary"> + <el-row :gutter="16"> + <el-col :span="8" class="summary-item"> + <div class="summary-value" style="color: #67C23A;">{{ healthyCount }}</div> + <div class="summary-label">鍋ュ悍</div> + </el-col> + <el-col :span="8" class="summary-item"> + <div class="summary-value" style="color: #E6A23C;">{{ warningCount }}</div> + <div class="summary-label">棰勮</div> + </el-col> + <el-col :span="8" class="summary-item"> + <div class="summary-value" style="color: #F56C6C;">{{ criticalCount }}</div> + <div class="summary-label">绱ф��</div> + </el-col> + </el-row> + </div> + + <el-table + :data="sensorComponentData" + stripe + + :row-class-name="tableRowClassName" + > + <el-table-column prop="name" label="閮ㄤ欢鍚嶇О" /> + <el-table-column label="鍋ュ悍鐘舵��" > + <template #default="{ row }"> + <el-tag + :type="getHealthStatusType(row.healthStatus)" + size="small" + > + {{ row.healthStatus }} + </el-tag> + </template> + </el-table-column> + <el-table-column label="鍓╀綑瀵垮懡" > + <template #default="{ row }"> + <div class="life-progress"> + <el-progress + :percentage="row.remainingPercentage" + :color="getProgressColor(row.remainingPercentage)" + :show-text="false" + :stroke-width="12" + /> + <span class="life-text">{{ row.remainingLife }}</span> + </div> + </template> + </el-table-column> + <el-table-column label="棰勬祴鏇存柊鏃堕棿" width="120"> + <template #default="{ row }"> + {{ row.lastUpdate }} + </template> + </el-table-column> + <el-table-column label="鎿嶄綔" width="80"> + <template #default="{ row }"> + <el-button + link + type="primary" + size="small" + @click="showComponentDetail(row)" + > + 璇︽儏 + </el-button> + </template> + </el-table-column> + </el-table> + + </el-card> + </el-col> + + <!-- 鍘嗗彶缁存姢璁板綍 --> + <el-col :span="12"> + <el-card class="mb-4" :style="{ height: '560px' }"> + <template #header> + <div class="card-header"> + <span>鍘嗗彶寮傚父浜嬩欢鍒嗘瀽</span> + </div> + </template> + <el-timeline> + <el-timeline-item + v-for="item in historyData" + :key="item.id" + :type="getTimelineItemType(item.color)" + :timestamp="item.date" + > + {{ item.type }}: {{ item.description }} + </el-timeline-item> + </el-timeline> + </el-card> + </el-col> + </el-row> + </div> +</template> + + + +<script setup lang="ts"> +import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'; +import { useRouter,useRoute } from 'vue-router'; +import * as echarts from 'echarts'; +import { + TrendCharts, + Warning, + Grid, + Clock +} from '@element-plus/icons-vue'; +import { ElMessage } from 'element-plus'; +import img from '@/assets/images/quailty/product.jpg' +import { getBatch } from '@/api/qms/batch'; +import { BatchVO } from '@/api/qms/batch/types'; + +const batch= ref<BatchVO>({}); +const router = useRouter(); +const route = useRoute(); +const batchId = route.query.id + +const healthRate = ref(0); +const belowRate = ref(0); + + +const goBack = () => { + router.go(-1); +}; + + +function getBatchDetail(){ + getBatch(batchId).then(res => { + if(res){ + batch.value = res.data; + initCharts2(); + if(batch.value && batch.value.num > 0 && batch.value.okNum > 0){ + const yieldRate = (batch.value.okNum / batch.value.num) * 100; + healthRate.value = Number(yieldRate.toFixed(1)); + + if(healthRate.value < 80 ){ + belowRate.value = 10.11; + healthData.riskLevel = "楂橀闄�" + healthData.riskColor = "#f56c6c" + + }else if(healthRate.value>=80&& healthRate.value<90){ + healthData.riskLevel = "涓闄�" + healthData.riskColor = "#faad14" + belowRate.value = 6.37; + } else if(healthRate.value>90&& healthRate.value<95){ + belowRate.value = 3.15; + } else if(healthRate.value>95&& healthRate.value<=100){ + belowRate.value = 0.32 ; + }else { + belowRate.value = 0; + + } + } + + } + }) +} + + + + +const healthData = reactive({ + overallHealth: 82, + healthColor: '#52c41a', + predictedLife: 1.31, + riskLevel: '浣庨闄�', + riskColor: '#52c41a', + xAxisTravel: 300.179, + yAxisTravel: 233.39, + tapeJamCount: 6, + materialJamCount: 15, + panelCount: 2480, + downtime: 4.5, +}); +// 鐢熸垚鐩稿鏃堕棿鍑芥暟 +const generateRelativeTime = (daysAgo) => { + const date = new Date(); + date.setDate(date.getDate() - daysAgo); + return date.toISOString().split('T')[0]; +}; + +const maintenanceData = reactive([ + { + key: '1', + type: '1鍙疯创瑁呯郴缁熺淮鎶�', + content: '鍚稿槾鐪熺┖鍘嬪姏鍋忎綆锛岄渶瑕佹竻娲佹垨鏇存崲鍚稿槾', + suggestedTime: generateRelativeTime(3), + urgency: '涓瓑' + }, + { + key: '2', + type: '2鍙峰洖娴佺剨鐐夌淮鎶�', + content: '娓╁尯娓╁害娉㈠姩瓒呰繃鏍囧噯鑼冨洿锛岄渶瑕佹牎鍑嗘俯搴︿紶鎰熷櫒', + suggestedTime: generateRelativeTime(1), + urgency: '浣�' + }, + { + key: '3', + type: '3鍙稟OI妫�娴嬩华缁存姢', + content: '鐩告満闀滃ご鏈夌伆灏橈紝褰卞搷妫�娴嬬簿搴︼紝闇�瑕佹竻娲侀暅澶�', + suggestedTime: generateRelativeTime(2), + urgency: '浣�' + }, + { + key: '4', + type: '4鍙烽敗鑶忓嵃鍒锋満缁存姢', + content: '鍒垁鍘嬪姏涓嶅潎鍖�锛岄渶瑕佽皟鏁存垨鏇存崲鍒垁', + suggestedTime: generateRelativeTime(3), + urgency: '涓瓑' + }, + { + key: '5', + type: '5鍙稴PI妫�娴嬩华缁存姢', + content: '婵�鍏夋祴閲忔ā鍧楅渶瑕侀噸鏂版牎鍑�', + suggestedTime: generateRelativeTime(5), + urgency: '涓瓑' + }, + { + key: '6', + type: '6鍙稾-Ray妫�娴嬩华缁存姢', + content: 'X灏勭嚎婧愬伐浣滄椂闂存帴杩戠淮鎶ゅ懆鏈燂紝闇�瑕侀闃叉�х淮鎶�', + suggestedTime: generateRelativeTime(4), + urgency: '浣�' + } +]); + +const getUrgencyTagType = (urgency: string) => { + switch (urgency) { + case '楂�': return 'danger'; + case '涓瓑': return 'warning'; + case '浣�': return 'success'; + default: return 'info'; + } +}; + +const batchIsToday = (date: string) => { + const today = new Date(); + const inputDate = new Date(date); + + return inputDate.toDateString() === today.toDateString(); +}; + +const getTimelineItemType = (color: string) => { + switch (color) { + case 'green': return 'success'; + case 'red': return 'danger'; + default: return 'primary'; + } +}; + +const sparePartData = reactive([ + { + key: '1', + name: '1鍙疯创瑁呯郴缁烼杞翠己鏈嶇數鏈�', + currentLife: '15000灏忔椂', + remainingLife: '1451灏忔椂', + status: '棰勮' + }, + { + key: '5', + name: '1鍙疯创瑁呯郴缁焃杞翠己鏈嶇數鏈�', + currentLife: '15000灏忔椂', + remainingLife: '7521灏忔椂', + status: '鑹ソ' + }, + { + key: '9', + name: '1鍙疯创瑁呯郴缁熺湡绌虹數纾侀榾', + currentLife: '10000灏忔椂', + remainingLife: '2154灏忔椂', + status: '鑹ソ' + }, + { + key: '2', + name: '1鍙疯创瑁呭ご', + currentLife: '1000000娆�', + remainingLife: '425542娆�', + status: '鑹ソ' + }, + { + key: '6', + name: '2鍙疯创瑁呯郴缁烼杞翠己鏈嶇數鏈�', + currentLife: '15000灏忔椂', + remainingLife: '7540灏忔椂', + status: '鑹ソ' + }, + { + key: '7', + name: '2鍙疯创瑁呯郴缁焃杞翠己鏈嶇數鏈�', + currentLife: '15000灏忔椂', + remainingLife: '7521灏忔椂', + status: '鑹ソ' + }, + { + key: '9', + name: '2鍙疯创瑁呯郴缁熺湡绌虹數纾侀榾', + currentLife: '10000灏忔椂', + remainingLife: '2154灏忔椂', + status: '鑹ソ' + }, + { + key: '8', + name: '2鍙疯创瑁呭ご', + currentLife: '1000000娆�', + remainingLife: '751251娆�', + status: '鑹ソ' + }, + { + key: '3', + name: '椋炶揪', + currentLife: '96涓湀', + remainingLife: '43涓湀', + status: '鑹ソ' + }, +]); + +// 寮傚父浜嬩欢鍒嗘瀽鏁版嵁 +// 寮傚父浜嬩欢鍒嗘瀽鏁版嵁 +const historyData = reactive([ + { + id: '1', + date: '2025-09-12 14:25:36', + type: '铓�鍒绘繁搴﹀紓甯�', + description: '寰満鐢电郴缁熻殌鍒绘満妫�娴嬪埌铓�鍒绘繁搴﹀紓甯革細2.8渭m (姝e父鑼冨洿: 2.3-2.5渭m)', + color: 'red' + }, + { + id: '2', + date: '2025-08-21 13:40:22', + type: '娓╁害绋冲畾鎬у紓甯�', + description: '鐑鐞嗙倝娓╁害娉㈠姩瓒呰繃鍏佽鑼冨洿卤0.5掳C锛岃揪鍒奥�0.8掳C', + color: 'orange' + }, + { + id: '3', + date: '2025-08-03 12:15:48', + type: '鐢甸樆鍊煎亸宸�', + description: '妫�娴嬪埌浼犳劅鍣ㄧ數闃诲�艰秴鍑哄叕宸寖鍥绰�5%锛屽疄闄呭亸宸负+7.2%', + color: 'red' + }, + { + id: '4', + date: '2025-07-12 11:30:15', + type: '娌夌Н閫熺巼娉㈠姩', + description: '钖勮啘娌夌Н璁惧娌夌Н閫熺巼涓嶇ǔ瀹氾紝娉㈠姩骞呭害杈惧埌卤8%', + color: 'orange' + }, + { + id: '5', + date: '2025-06-01 10:45:33', + type: '鏍″噯鏁版嵁鍋忕Щ', + description: '浼犳劅鍣ㄦ牎鍑嗙珯妫�娴嬪埌闆剁偣婕傜Щ0.3mV锛岄渶瑕侀噸鏂版牎鍑�', + color: 'yellow' + }, + { + id: '6', + date: '2025-04-17 09:20:57', + type: '閿悎鍘嬪姏寮傚父', + description: '鏅跺渾閿悎鏈哄帇鍔涗紶鎰熷櫒璇绘暟寮傚父锛屽疄闄呭帇鍔涙瘮璁惧畾鍊间綆12%', + color: 'orange' + }, + { + id: '7', + date: '2025-03-26 08:55:12', + type: '鍏夊妫�娴嬪紓甯�', + description: '鍏夊妫�娴嬩华鍙戠幇3涓紶鎰熷櫒琛ㄩ潰瀛樺湪寰皬鍒掔棔', + color: 'yellow' + }, + { + id: '8', + date: '2025-01-3 08:30:45', + type: '鐪熺┖搴︿笉瓒�', + description: '鐪熺┖闀�鑶滄満鐪熺┖搴︿笅闄嶈嚦0.005Pa锛屼綆浜庢爣鍑�0.001Pa', + color: 'red' + }, + +]); + +const handleMaintenance = (record: any) => { + console.log('澶勭悊缁存姢寤鸿:', record); + // ElMessage.info(`澶勭悊缁存姢寤鸿: ${record.type}`); +}; + +const maintenanceTable = ref(); + +// 鐢ㄤ簬鏄剧ず鐨勭淮鎶ゆ暟鎹紙鏀寔鏃犻檺婊氬姩锛� +const displayMaintenanceData = ref([...maintenanceData]); +let scrollInterval: ReturnType<typeof setInterval> | undefined; + +// 鍚姩鑷姩婊氬姩 +const startAutoScroll = () => { + scrollInterval = setInterval(() => { + if (displayMaintenanceData.value.length > 0) { + // 灏嗙涓�椤圭Щ鍒版渶鍚� + const firstItem = displayMaintenanceData.value.shift(); + if (firstItem) { + displayMaintenanceData.value.push(firstItem); + } + } + }, 1000); // 姣�3绉掓粴鍔ㄤ竴娆� +}; + +onMounted(() => { + getBatchDetail() + nextTick(() => { + startAutoScroll(); // 鍚姩鑷姩婊氬姩 + }); +}); + +onUnmounted(() => { + if (scrollInterval) { + clearInterval(scrollInterval); + } +}); + +const initCharts = () => { + // 鍥捐〃鍒濆鍖栦唬鐮� + const initChart = (chartId: string, title: string, seriesConfig: Array<{ name: string, data: any[], unit: string, baseValue: number, fluctuation: number, color: string }>) => { + const chart = echarts.init(document.getElementById(chartId)); + + const updateChart = () => { + // 鐢熸垚鏂扮殑鏁版嵁鐐� + const now = new Date(); + const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; + + seriesConfig.forEach(s => { + const newValue = (s.baseValue + (Math.random() * 2 - 1) * s.fluctuation).toFixed(2); + s.data.push({ + time, + value: newValue + }); + if (s.data.length > 24) { + s.data.shift(); + } + }); + + const option = { + title: { + text: title, + left: 'center' + }, + tooltip: { + trigger: 'axis', + formatter: (params) => { + let result = `${params[0].axisValueLabel}<br/>`; + params.forEach(param => { + result += `${param.marker} ${param.seriesName}: ${param.data}${seriesConfig[param.seriesIndex].unit}<br/>`; + }); + return result; + } + }, + grid: { + left: '8%', // 澧炲姞宸︿晶杈硅窛 + right: '5%', + bottom: '10%', + containLabel: true + }, + xAxis: { + type: 'category', + data: seriesConfig[0].data.map(item => item.time) + }, + yAxis: seriesConfig.map((s, index) => ({ + type: 'value', + name: s.name, + position: index === 0 ? 'left' : 'right', + axisLine: { + show: true, + }, + axisLabel: { + formatter: (value) => `${value}${s.unit}` + } + })), + series: seriesConfig.map((s, index) => ({ + name: s.name, + data: s.data.map(item => parseFloat(item.value)), + type: 'line', + smooth: true, + yAxisIndex: index, + lineStyle: { + width: 2, + color: s.color + }, + areaStyle: { + opacity: 0.8, + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: 'rgba(84,112,198,0.3)' + }, + { + offset: 1, + color: 'rgba(84,112,198,0)' + } + ]) + }, + } + )), + }; + chart.setOption(option); + }; + + seriesConfig.forEach(s => { + s.data = []; + // 鐢熸垚鍒濆鏁版嵁鐐癸紙60涓偣锛�5鍒嗛挓鏁版嵁锛� + for (let i = 0; i < 24; i++) { + const now = new Date(Date.now() - (24 - i) * 5000); // 鐢熸垚杩囧幓5鍒嗛挓鐨勬暟鎹� + const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; + const newValue = (s.baseValue + (Math.random() * 2 - 1) * s.fluctuation).toFixed(2); + s.data.push({ + time, + value: newValue + }); + } + }) + + // 鍒濆娓叉煋 + updateChart(); + + // 姣�5绉掓洿鏂颁竴娆℃暟鎹� + const intervalId = setInterval(updateChart, 5000); + + window.addEventListener('resize', () => { + chart.resize(); + }); + }; + + // 鍒濆鍖栧悇涓浘琛� + initChart('motorTemperatureChart', '浜ч噺', [ + { name: '浜ч噺', data: [], unit: 'pcs/h', baseValue: 175, fluctuation: 25, color: '#5470C6' }, + ]); + + initChart('motorVibrationChart', '鎶涙枡鐜�', [ + { name: '鎶涙枡鐜�', data: [], unit: '%', baseValue: 0.15, fluctuation: 0.01, color: '#5470C6' }, + ]); + + initChart('nozzleVacuumChart', '鍚稿槾鐪熺┖鍘嬪姏', [ + { name: '鍘嬪姏', data: [], unit: 'kPa', baseValue: -45, fluctuation: 5, color: '#5470C6' }, + ]); + + initChart('nozzleFlowChart', '鍚稿槾鍚规皵鍘嬪姏', [ + { name: '鍘嬪姏', data: [], unit: 'kPa', baseValue: 20, fluctuation: 5, color: '#5470C6' }, + ]); + + initChart('placementSpeedChart', '璐磋閫熷害', [ + { name: '閫熷害', data: [], unit: 'chips/h', baseValue: 9000, fluctuation: 1500, color: '#5470C6' }, + ]); + + const ambientTemperatureData: any[] = []; + const ambientHumidityData: any[] = []; + initChart('ambientTemperatureHumidityChart', '鐜娓╂箍搴�', [ + { name: '娓╁害', data: ambientTemperatureData, unit: '掳C', baseValue: 25, fluctuation: 1, color: '#5470C6' }, + { name: '婀垮害', data: ambientHumidityData, unit: '%', baseValue: 60, fluctuation: 5, color: '#91cc75' } + ]); +}; + + +const initCharts2 = () => { + // 鐢熸垚鏃堕棿杞存暟鎹� + const generateTimeAxis = (isToday) => { + const baseTimes = ['08:30', '09:30', '10:30', '11:30', '12:30', '13:30', '14:30', '15:30', '16:30', '17:00']; + + if (!isToday) { + return baseTimes; + } + + const now = new Date(); + const currentHour = now.getHours(); + const currentMinute = now.getMinutes(); + const currentTime = `${currentHour}:${currentMinute.toString().padStart(2, '0')}`; + + // 鎵惧埌褰撳墠鏃堕棿鍦ㄥ熀纭�鏃堕棿鏁扮粍涓殑浣嶇疆 + const currentIndex = baseTimes.findIndex(time => { + const [hour, minute] = time.split(':').map(Number); + const timeInMinutes = hour * 60 + minute; + const currentInMinutes = currentHour * 60 + currentMinute; + return timeInMinutes >= currentInMinutes; + }); + + // 濡傛灉褰撳墠鏃堕棿鏃╀簬8:30锛岃繑鍥炲畬鏁存暟缁� + if (currentIndex === -1) { + return baseTimes; + } + + // 鎴彇鍒板綋鍓嶆椂闂翠箣鍓嶇殑鏃堕棿鐐� + return baseTimes.slice(0, currentIndex + 1); + }; + + // 鎴彇鏁版嵁浠ュ尮閰嶆椂闂磋酱闀垮害 + const truncateData = (data, targetLength) => { + return data.slice(0, targetLength); + }; + + // 鍥捐〃鍒濆鍖栦唬鐮� + const initChart = (chartId, title, seriesConfig, isToday) => { + const chart = echarts.init(document.getElementById(chartId)); + const timeAxis = generateTimeAxis(isToday); + + const option = { + title: { + text: title, + left: 'center' + }, + tooltip: { + trigger: 'axis', + formatter: (params) => { + let result = `${params[0].axisValueLabel}<br/>`; + params.forEach(param => { + result += `${param.marker} ${param.seriesName}: ${param.data}${seriesConfig[param.seriesIndex].unit}<br/>`; + }); + return result; + } + }, + grid: { + left: '8%', + right: '5%', + bottom: '10%', + containLabel: true + }, + xAxis: { + type: 'category', + data: timeAxis + }, + yAxis: seriesConfig.map((s, index) => ({ + type: 'value', + name: s.name, + position: index === 0 ? 'left' : 'right', + axisLine: { + show: true, + }, + axisLabel: { + formatter: (value) => `${value}${s.unit}` + } + })), + series: seriesConfig.map((s, index) => ({ + name: s.name, + data: truncateData(s.data, timeAxis.length), + type: 'line', + smooth: true, + yAxisIndex: index, + lineStyle: { + width: 2, + color: s.color + }, + areaStyle: { + opacity: 0.8, + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: 'rgba(84,112,198,0.3)' + }, + { + offset: 1, + color: 'rgba(84,112,198,0)' + } + ]) + }, + })), + }; + chart.setOption(option); + + window.addEventListener('resize', () => { + chart.resize(); + }); + }; + + // 鍋囪鏈変竴涓彉閲忚〃绀烘槸鍚︽槸浠婂ぉ + const isToday = batchIsToday(batch.value.batchTime); // 鍙互鏍规嵁瀹為檯鎯呭喌璁剧疆涓簍rue鎴杅alse + + // 鍒濆鍖栧悇涓浘琛� + initChart('motorTemperatureChart', '铓�鍒绘繁搴�(渭m)', [ + { + name: '铓�鍒绘繁搴�', + data: [2.35, 2.38, 2.42, 2.45, 2.51, 2.48, 2.52, 2.55, 2.58, 2.60], + unit: '渭m', + color: '#5470C6' + }, + ], isToday); + + initChart('motorVibrationChart', '閿悎鍘嬪姏(MPa)', [ + { + name: '閿悎鍘嬪姏', + data: [15.2, 15.5, 15.8, 16.1, 15.9, 16.2, 16.0, 16.3, 16.1, 16.4], + unit: 'MPa', + color: '#91cc75' + }, + ], isToday); + + initChart('nozzleVacuumChart', '娌夌Н閫熺巼(脜/min)', [ + { + name: '娌夌Н閫熺巼', + data: [120, 118, 122, 125, 128, 126, 124, 127, 129, 131], + unit: '脜/min', + color: '#fac858' + }, + ], isToday); + + initChart('nozzleFlowChart', '娓╁害鎺у埗(掳C)', [ + { + name: '娓╁害', + data: [350, 352, 355, 358, 356, 354, 351, 353, 357, 359], + unit: '掳C', + color: '#ee6666' + }, + ], isToday); + + initChart('placementSpeedChart', '鐪熺┖搴�(Pa)', [ + { + name: '鐪熺┖搴�', + data: [0.0012, 0.0015, 0.0018, 0.0021, 0.0019, 0.0020, 0.0017, 0.0022, 0.0020, 0.0018], + unit: 'Pa', + color: '#73c0de' + }, + ], isToday); + + initChart('ambientTemperatureHumidityChart', '鐢垫祦瀵嗗害(A/cm虏)', [ + { + name: '鐢垫祦瀵嗗害', + data: [2.1, 2.2, 2.3, 2.4, 2.35, 2.38, 2.32, 2.36, 2.39, 2.42], + unit: 'A/cm虏', + color: '#3ba272' + } + ], isToday); +}; + + + +// 鑾峰彇闅忔満澶╂暟锛堢敤浜庢ā鎷熶笉鍚岀殑鏇存柊鏃堕棿锛� +const getRandomDaysAgo = (min, max) => { + return Math.floor(Math.random() * (max - min + 1)) + min; +}; + +const sensorComponentData = reactive([ + { + id: '1', + name: '铓�鍒诲弽搴旇厰瀹�', + healthStatus: '鑹ソ', + remainingLife: '285澶�', + remainingPercentage: 85, + totalLife: '5骞�', + currentUsage: '2.5骞�', + lastUpdate: generateRelativeTime(3), + maintenanceHistory: '涓婃缁存姢: 2025-06-15' + }, + { + id: '2', + name: '鏅跺渾浼犺緭鏈烘鑷�', + healthStatus: '棰勮', + remainingLife: '45澶�', + remainingPercentage: 25, + totalLife: '80涓囨', + currentUsage: '60涓囨', + lastUpdate: generateRelativeTime(7), + maintenanceHistory: '涓婃缁存姢: 2025-07-20' + }, + { + id: '3', + name: '鐪熺┖娉电郴缁�', + healthStatus: '绱ф��', + remainingLife: '7澶�', + remainingPercentage: 5, + totalLife: '20000灏忔椂', + currentUsage: '19500灏忔椂', + lastUpdate: generateRelativeTime(2), + maintenanceHistory: '涓婃缁存姢: 2025-03-10' + }, + { + id: '4', + name: '娓╁害鎺у埗绯荤粺', + healthStatus: '鑹ソ', + remainingLife: '180澶�', + remainingPercentage: 70, + totalLife: '3骞�', + currentUsage: '1.5骞�', + lastUpdate: generateRelativeTime(4), + maintenanceHistory: '涓婃缁存姢: 2025-05-22' + }, + { + id: '5', + name: '鍖栧娌夌Н鍠峰ご', + healthStatus: '棰勮', + remainingLife: '30澶�', + remainingPercentage: 20, + totalLife: '1000鎵规', + currentUsage: '850鎵规', + lastUpdate: generateRelativeTime(3), + maintenanceHistory: '涓婃缁存姢: 2025-04-18' + }, + { + id: '6', + name: '鍏夊妫�娴嬮暅澶�', + healthStatus: '鑹ソ', + remainingLife: '365澶�', + remainingPercentage: 90, + totalLife: '4骞�', + currentUsage: '1骞�', + lastUpdate: generateRelativeTime(18), + maintenanceHistory: '涓婃缁存姢: 2025-01-15' + }, + { + id: '7', + name: '绂诲瓙娉ㄥ叆婧�', + healthStatus: '绱ф��', + remainingLife: '14澶�', + remainingPercentage: 8, + totalLife: '15000灏忔椂', + currentUsage: '14500灏忔椂', + lastUpdate: generateRelativeTime(2), + maintenanceHistory: '涓婃缁存姢: 2024-12-05' + } +]); + +// 璁$畻鍋ュ悍鐘舵�佺粺璁� +const healthyCount = computed(() => + sensorComponentData.filter(item => item.healthStatus === '鑹ソ').length +); + +const warningCount = computed(() => + sensorComponentData.filter(item => item.healthStatus === '棰勮').length +); + +const criticalCount = computed(() => + sensorComponentData.filter(item => item.healthStatus === '绱ф��').length +); + +// 琛ㄦ牸琛屾牱寮� +const tableRowClassName = ({ row }) => { + if (row.healthStatus === '绱ф��') { + return 'warning-row'; + } else if (row.healthStatus === '棰勮') { + return 'warning-row-light'; + } + return ''; +}; + +// 鑾峰彇鍋ュ悍鐘舵�佹爣绛剧被鍨� +const getHealthStatusType = (status) => { + const statusMap = { + '鑹ソ': 'success', + '棰勮': 'warning', + '绱ф��': 'danger' + }; + return statusMap[status] || 'info'; +}; + +// 鑾峰彇杩涘害鏉¢鑹� +const getProgressColor = (percentage) => { + if (percentage > 60) return '#67C23A'; + if (percentage > 30) return '#E6A23C'; + return '#F56C6C'; +}; + +// 鏄剧ず閮ㄤ欢璇︽儏 +const showComponentDetail = (component) => { + ElMessageBox.confirm( + `閮ㄤ欢鍚嶇О: ${component.name} +鍋ュ悍鐘舵��: ${component.healthStatus} +鍓╀綑瀵垮懡: ${component.remainingLife} +鎬昏璁″鍛�: ${component.totalLife} +褰撳墠浣跨敤: ${component.currentUsage} +${component.maintenanceHistory}`, + '閮ㄤ欢璇︽儏', + { + confirmButtonText: '纭畾', + cancelButtonText: '鍏抽棴', + type: 'info' + } + ); +}; +</script> + + +<style scoped> +.device-detail-container { + padding: 16px; + background: #f0f2f5; +} + +.page-header-content { + display: flex; + flex-direction: column; +} + +.page-header-title { + font-size: 18px; + font-weight: bold; +} + +.page-header-subtitle { + font-size: 14px; + color: #666; + margin-top: 4px; +} + +.card-header { + font-weight: bold; +} + +.device-image-container { + display: flex; + justify-content: center; + align-items: center; + height: 330px; + overflow: hidden; +} + +.device-image { + max-width: 100%; + max-height: 100%; + object-fit: contain; +} + +.mt-4 { + margin-top: 1rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + + +.custom-descriptions { + height: 357px; /* 璁剧疆鏁翠釜鎻忚堪鍒楄〃鐨勯珮搴� */ +} + +.custom-descriptions :deep(.el-descriptions__body) { + height: 100%; +} + +.custom-descriptions :deep(.el-descriptions__table) { + height: 100%; +} + +.custom-descriptions :deep(.el-descriptions__table tbody) { + height: 100%; + display: flex; + flex-direction: column; +} + +.custom-descriptions :deep(.el-descriptions__table tr) { + flex: 1; + display: flex; +} + +.custom-descriptions :deep(.el-descriptions__table td) { + flex: 1; + display: flex; + align-items: center; +} + +.custom-descriptions :deep(.el-descriptions__table .el-descriptions__label) { + display: flex; + align-items: center; + justify-content: flex-start; +} + +.custom-descriptions :deep(.el-descriptions__table .el-descriptions__content) { + display: flex; + align-items: center; + justify-content: flex-start; +} + +/* +.table-container { + position: relative; + height: 246px; + overflow: hidden; +} + +.table-container :deep(.el-table__body-wrapper) { + overflow: hidden; +} + +.table-container :deep(.el-table__body) { + animation: scrollTable 20s linear infinite; +} + +.table-container:hover :deep(.el-table__body) { + animation-play-state: paused; +} + +@keyframes scrollTable { + 0% { + transform: translateY(0); + } + 100% { + transform: translateY(-100%); + } +} + + +.table-container :deep(.el-table__header-wrapper) { + position: sticky; + top: 0; + background: white; + z-index: 10; +} +*/ +/* 鍦╯tyle閮ㄥ垎娣诲姞浠ヤ笅鏍峰紡 */ +.life-progress { + display: flex; + align-items: center; + gap: 8px; +} + +.life-text { + font-size: 12px; + color: #606266; + min-width: 40px; +} + +.health-summary { + padding: 10px 0; +} + +.summary-item { + text-align: center; + padding: 8px 0; +} + +.summary-value { + font-size: 24px; + font-weight: bold; + margin-bottom: 4px; +} + +.summary-label { + font-size: 12px; + color: #909399; +} + +:deep(.warning-row) { + --el-table-tr-bg-color: var(--el-color-danger-light-9); +} + +:deep(.warning-row-light) { + --el-table-tr-bg-color: var(--el-color-warning-light-9); +} + +:deep(.el-table .warning-row:hover > td) { + background-color: var(--el-color-danger-light-7) !important; +} + +:deep(.el-table .warning-row-light:hover > td) { + background-color: var(--el-color-warning-light-7) !important; +} +</style> diff --git a/src/views/qms/ai/index.vue b/src/views/qms/quality/index.vue similarity index 99% rename from src/views/qms/ai/index.vue rename to src/views/qms/quality/index.vue index d010fb5..01dfb28 100644 --- a/src/views/qms/ai/index.vue +++ b/src/views/qms/quality/index.vue @@ -6,7 +6,7 @@ <el-card shadow="hover" class="h-full"> <template #header> <div class="card-header"> - <span>璁惧鍋ュ悍搴﹁瘎鍒�</span> + <span>璐ㄩ噺鍋ュ悍搴﹁瘎鍒�</span> </div> </template> <!-- 杩欓噷灏嗘斁缃仴搴峰害鍒嗗竷鍥捐〃 --> -- Gitblit v1.9.3