From 5a0ab7dbeaba63e7d370e9fa4586f1664e1da775 Mon Sep 17 00:00:00 2001 From: baoshiwei <baoshiwei@shlanbao.cn> Date: 星期三, 09 七月 2025 08:53:39 +0800 Subject: [PATCH] feat(eims-ui): 新增预测性维护主界面和cnc加工中心、空压机、smt贴片机、注塑机等详情界面 --- eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/injection-molding-machine-detail.vue | 549 +++++++++++ eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/index.vue | 409 ++++++++ eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/air-compressor-detail.vue | 513 ++++++++++ eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/cnc-machining-center-detail.vue | 505 ++++++++++ eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/device-detail.vue | 439 ++++++++ eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/smt-machine-detail.vue | 509 ++++++++++ 6 files changed, 2,924 insertions(+), 0 deletions(-) diff --git a/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/air-compressor-detail.vue b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/air-compressor-detail.vue new file mode 100644 index 0000000..e6dea20 --- /dev/null +++ b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/air-compressor-detail.vue @@ -0,0 +1,513 @@ +<template> + <div class="device-detail-container"> + <a-page-header + title="绌哄帇鏈洪娴嬫�х淮鎶よ鎯�" + :sub-title="deviceInfo.deviceName" + @back="() => $router.go(-1)" + /> + + <a-row :gutter="16" class="mt-4"> + <!-- 璁惧鍩烘湰淇℃伅 --> + <a-col :span="8"> + <a-card title="璁惧鍩烘湰淇℃伅" class="mb-4" :style="{ height: '440px' }"> + <a-descriptions bordered :column="1"> + <a-descriptions-item label="璁惧鍚嶇О">{{ deviceInfo.deviceName }}</a-descriptions-item> + <a-descriptions-item label="璁惧绫诲瀷">{{ deviceInfo.deviceType }}</a-descriptions-item> + <a-descriptions-item label="璁惧缂栧彿">{{ deviceInfo.deviceId }}</a-descriptions-item> + <a-descriptions-item label="瀹夎鏃ユ湡">{{ deviceInfo.installDate }}</a-descriptions-item> + <a-descriptions-item label="浣跨敤骞撮檺">{{ deviceInfo.serviceLife }}骞�</a-descriptions-item> + <a-descriptions-item label="褰撳墠鐘舵��"> + <a-tag :color="deviceInfo.statusColor">{{ deviceInfo.status }}</a-tag> + </a-descriptions-item> + </a-descriptions> + </a-card> + </a-col> + + <!-- 璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿 --> + <a-col :span="16"> + <a-card title="璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿" class="mb-4" :style="{ height: '440px' }"> + <a-row :gutter="16"> + <a-col :span="8"> + <a-statistic + title="鏁翠綋鍋ュ悍搴�" + :value="healthData.overallHealth" + :precision="0" + suffix="%" + :value-style="{ color: healthData.healthColor }" + /> + </a-col> + <a-col :span="8"> + <a-statistic + title="棰勬祴鍓╀綑瀵垮懡" + :value="healthData.predictedLife" + suffix="澶�" + /> + </a-col> + <a-col :span="8"> + <a-statistic + title="鏁呴殰椋庨櫓绛夌骇" + :value="healthData.riskLevel" + :value-style="{ color: healthData.riskColor }" + /> + </a-col> + </a-row> + <div class="mt-4"> + <a-progress + :percent="healthData.overallHealth" + :stroke-color="healthData.healthColor" + :show-info="false" + /> + </div> + <a-divider orientation="left">棰勬祴鎬х淮鎶ゅ缓璁�</a-divider> + <a-table + :columns="maintenanceColumns" + :data-source="maintenanceData" + :pagination="false" + size="small" + > + <template #urgency="{ text }"> + <a-tag :color="getUrgencyColor(text)">{{ text }}</a-tag> + </template> + <template #action="{ record }"> + <a-button type="link" size="small" @click="handleMaintenance(record)">澶勭悊</a-button> + </template> + </a-table> + </a-card> + </a-col> + </a-row> + + <!-- 瀹炴椂鏁版嵁瓒嬪娍鍥� --> + <a-card title="瀹炴椂鏁版嵁瓒嬪娍鍥�" class="mb-4"> + <a-row :gutter="16"> + <a-col :span="8"> + <div id="exhaustPressureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="exhaustTemperatureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="hostVibrationChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="mainBearingTemperatureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="mainMotorCurrentChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="oilTemperatureChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="oilPressureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="oilLevelChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="inletPressureChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="inletTemperatureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="airFlowChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="dryerDewPointChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="coolingWaterTemperatureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="coolingWaterFlowChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="ambientTemperatureHumidityChart" style="height: 300px;"></div> + </a-col> + </a-row> + </a-card> + <a-row :gutter="16"> + <!-- 澶囦欢瀵垮懡棰勬祴 --> + <a-col :span="12"> + <a-card title="澶囦欢瀵垮懡棰勬祴" class="mb-4"> + <a-table + :columns="sparePartColumns" + :data-source="sparePartData" + :pagination="false" + size="small" + > + <template #status="{ text }"> + <a-tag :color="getStatusColor(text)">{{ text }}</a-tag> + </template> + </a-table> + </a-card> + </a-col> + + <!-- 鍘嗗彶缁存姢璁板綍 --> + <a-col :span="12"> + <a-card title="鍘嗗彶缁存姢璁板綍" class="mb-4"> + <a-timeline> + <a-timeline-item v-for="item in historyData" :key="item.id" :color="item.color"> + {{ item.date }} - {{ item.type }}: {{ item.description }} + </a-timeline-item> + </a-timeline> + </a-card> + </a-col> + </a-row> + + </div> +</template> + +<script lang="ts"> +import { defineComponent, reactive, onMounted, onUnmounted } from 'vue'; +import * as echarts from 'echarts'; + +export default defineComponent({ + name: 'AirCompressorDetail', + setup() { + // 妯℃嫙鏁版嵁 + const deviceInfo = reactive({ + deviceName: '绌哄帇鏈� #AC-001', + deviceType: '绌哄帇鏈�', + deviceId: 'AC-2024-001', + installDate: '2023-05-10', + serviceLife: 10, + status: '杩愯涓�', + statusColor: '#52c41a' + }); + + const healthData = reactive({ + overallHealth: 92, + healthColor: '#52c41a', + predictedLife: 3000, + riskLevel: '浣庨闄�', + riskColor: '#52c41a' + }); + + const maintenanceColumns = [ + { + title: '缁存姢绫诲瀷', + dataIndex: 'type', + key: 'type' + }, + { + title: '缁存姢鍐呭', + dataIndex: 'content', + key: 'content' + }, + { + title: '寤鸿鏃堕棿', + dataIndex: 'suggestedTime', + key: 'suggestedTime' + }, + { + title: '绱ф�ョ▼搴�', + dataIndex: 'urgency', + key: 'urgency', + slots: { customRender: 'urgency' } + }, + { + title: '鎿嶄綔', + key: 'action', + slots: { customRender: 'action' } + } + ]; + + const maintenanceData = reactive([ + { + key: '1', + type: '渚嬭淇濆吇', + content: '妫�鏌ヤ富鏈虹揣鍥轰欢', + suggestedTime: '2024-07-01', + urgency: '涓瓑' + }, + { + key: '2', + type: '娑︽粦绯荤粺缁存姢', + content: '鏇存崲娑︽粦娌�', + suggestedTime: '2024-09-01', + urgency: '楂�' + }, + { + key: '3', + type: '绌烘皵绯荤粺缁存姢', + content: '鏇存崲绌烘护鑺�', + suggestedTime: '2024-08-15', + urgency: '浣�' + } + ]); + + const getUrgencyColor = (urgency: string) => { + switch (urgency) { + case '楂�': + return 'red'; + case '涓瓑': + return 'orange'; + case '浣�': + return 'green'; + default: + return 'default'; + } + }; + + const sparePartColumns = [ + { + title: '澶囦欢鍚嶇О', + dataIndex: 'name', + key: 'name' + }, + { + title: '褰撳墠瀵垮懡', + dataIndex: 'currentLife', + key: 'currentLife', + customRender: ({ text }: { text: number }) => `${text}灏忔椂` + }, + { + title: '棰勬祴鍓╀綑瀵垮懡', + dataIndex: 'remainingLife', + key: 'remainingLife', + customRender: ({ text }: { text: number }) => `${text}灏忔椂` + }, + { + title: '鐘舵��', + dataIndex: 'status', + key: 'status', + slots: { customRender: 'status' } + } + ]; + + const sparePartData = reactive([ + { + key: '1', + name: '绌烘护鑺�', + currentLife: 1000, + remainingLife: 200, + status: '棰勮' + }, + { + key: '2', + name: '娌规护鑺�', + currentLife: 1500, + remainingLife: 500, + status: '鑹ソ' + }, + { + key: '3', + name: '娌规皵鍒嗙鍣�', + currentLife: 2000, + remainingLife: 300, + status: '棰勮' + } + ]); + + const historyData = reactive([ + { + id: '1', + date: '2024-06-10', + type: '渚嬭淇濆吇', + description: '鏇存崲绌烘护鑺�佹补婊よ姱', + color: 'green' + }, + { + id: '2', + date: '2024-03-05', + type: '鏁呴殰缁翠慨', + description: '涓绘満杞存壙娑︽粦鑴傝ˉ鍏�', + color: 'red' + }, + { + id: '3', + date: '2023-12-01', + type: '骞村害淇濆吇', + description: '鍏ㄩ潰妫�鏌ヨ澶囪繍琛岀姸鎬�', + color: 'green' + } + ]); + + const getStatusColor = (status: string) => { + switch (status) { + case '鑹ソ': + return '#52c41a'; + case '棰勮': + return '#faad14'; + case '鍗遍櫓': + return '#f5222d'; + default: + return '#d9d9d9'; + } + }; + + const handleMaintenance = (record: any) => { + console.log('澶勭悊缁存姢寤鸿:', record); + // 瀹為檯椤圭洰涓繖閲屼細璋冪敤API澶勭悊缁存姢寤鸿 + }; + + return { + deviceInfo, + healthData, + maintenanceColumns, + maintenanceData, + sparePartColumns, + sparePartData, + historyData, + getStatusColor, + getUrgencyColor, + handleMaintenance + }; + }, + + mounted() { + // 鍒濆鍖栧浘琛� + const initChart = (chartId: string, title: string, chartData: any[], unit: string, baseValue: number, fluctuation: number) => { + const chart = echarts.init(document.getElementById(chartId)); + + // 棰勫厛鐢熸垚60涓暟鎹偣 + for (let i = 0; i < 60; i++) { + const now = new Date(Date.now() - (60 - 1 - i) * 5000); // 鍊掓帹鏃堕棿 + const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; + const newValue = (baseValue + (Math.random() * fluctuation * 2 - fluctuation)).toFixed(2); + chartData.push({ + time, + value: newValue + }); + } + + const updateChart = () => { + // 鐢熸垚鏂扮殑鏁版嵁鐐� + const now = new Date(); + const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; + const newValue = (baseValue + (Math.random() * fluctuation * 2 - fluctuation)).toFixed(2); + + // 娣诲姞鏂版暟鎹偣锛屾渶澶氫繚鐣�60涓偣锛�5鍒嗛挓鏁版嵁锛� + chartData.push({ + time, + value: newValue + }); + if (chartData.length > 60) { + chartData.shift(); + } + + const option = { + title: { + text: title, + left: 'center' + }, + tooltip: { + trigger: 'axis', + formatter: (params) => { + return `${params[0].axisValueLabel}<br/>${params[0].marker} ${params[0].seriesName}: ${params[0].data}${unit}`; + } + }, + grid: { + left: '8%', // 澧炲姞宸︿晶杈硅窛 + right: '5%', + bottom: '10%', + containLabel: true + }, + xAxis: { + type: 'category', + data: chartData.map(item => item.time) + }, + yAxis: { + type: 'value', + axisLabel: { + formatter: (value) => `${value}${unit}` + } + }, + series: [ + { + data: chartData.map(item => parseFloat(item.value)), + type: 'line', + smooth: true, + lineStyle: { + width: 2, + color: '#5470C6' // Default 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); + }; + + // 鍒濆娓叉煋 + updateChart(); + + // 姣�5绉掓洿鏂颁竴娆℃暟鎹� + const intervalId = setInterval(updateChart, 5000); + + window.addEventListener('resize', () => { + chart.resize(); + }); + onUnmounted(() => { + clearInterval(intervalId); + }); + + }; + + + initChart('exhaustPressureChart', '鎺掓皵鍘嬪姏', [], 'bar', 7.5, 0.2); + initChart('exhaustTemperatureChart', '鎺掓皵娓╁害', [], '掳C', 85, 2); + initChart('hostVibrationChart', '涓绘満鎸姩', [], 'mm/s', 3.5, 0.5); + initChart('mainBearingTemperatureChart', '涓昏酱鎵挎俯搴�', [], '掳C', 70, 3); + initChart('mainMotorCurrentChart', '涓荤數鏈虹數娴�', [], 'A', 120, 5); + initChart('oilTemperatureChart', '娌规俯', [], '掳C', 60, 2); + initChart('oilPressureChart', '娌瑰帇', [], 'bar', 4.0, 0.1); + initChart('oilLevelChart', '娌逛綅', [], '%', 80, 5); + initChart('inletPressureChart', '杩涙皵鍘嬪姏', [], 'bar', 1.0, 0.05); + initChart('inletTemperatureChart', '杩涙皵娓╁害', [], '掳C', 25, 2); + initChart('airFlowChart', '绌烘皵娴侀噺', [], 'm鲁/min', 15, 1); + initChart('dryerDewPointChart', '骞茬嚗鍣ㄩ湶鐐�', [], '掳C', 3, 1); + initChart('coolingWaterTemperatureChart', '鍐峰嵈姘存俯搴�', [], '掳C', 30, 2); + initChart('coolingWaterFlowChart', '鍐峰嵈姘存祦閲�', [], 'L/min', 10, 0.5); + initChart('ambientTemperatureHumidityChart', '鐜娓╂箍搴�', [], '掳C', 28, 2); + } + +}); +</script> + +<style scoped> +.device-detail-container { + padding: 16px; + background: #f0f2f5; +} + +.mt-4 { + margin-top: 16px; +} + +.mb-4 { + margin-bottom: 16px; +} +</style> + + +<style scoped> +.air-compressor-detail { + padding: 16px; + background-color: #f0f2f5; +} +.header-card { + margin-bottom: 16px; +} +</style> diff --git a/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/cnc-machining-center-detail.vue b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/cnc-machining-center-detail.vue new file mode 100644 index 0000000..eb36187 --- /dev/null +++ b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/cnc-machining-center-detail.vue @@ -0,0 +1,505 @@ +<template> + <div class="device-detail-container"> + <a-page-header + title="CNC鍔犲伐涓績棰勬祴鎬х淮鎶よ鎯�" + :sub-title="deviceInfo.deviceName" + @back="() => $router.go(-1)" + /> + + <a-row :gutter="16" class="mt-4"> + <!-- 璁惧鍩烘湰淇℃伅 --> + <a-col :span="8"> + <a-card title="璁惧鍩烘湰淇℃伅" class="mb-4" :style="{ height: '440px' }"> + <a-descriptions bordered :column="1"> + <a-descriptions-item label="璁惧鍚嶇О">{{ deviceInfo.deviceName }}</a-descriptions-item> + <a-descriptions-item label="璁惧绫诲瀷">{{ deviceInfo.deviceType }}</a-descriptions-item> + <a-descriptions-item label="璁惧缂栧彿">{{ deviceInfo.deviceId }}</a-descriptions-item> + <a-descriptions-item label="瀹夎鏃ユ湡">{{ deviceInfo.installDate }}</a-descriptions-item> + <a-descriptions-item label="浣跨敤骞撮檺">{{ deviceInfo.serviceLife }}骞�</a-descriptions-item> + <a-descriptions-item label="褰撳墠鐘舵��"> + <a-tag :color="deviceInfo.statusColor">{{ deviceInfo.status }}</a-tag> + </a-descriptions-item> + </a-descriptions> + </a-card> + </a-col> + + <!-- 璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿 --> + <a-col :span="16"> + <a-card title="璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿" class="mb-4" :style="{ height: '440px' }"> + <a-row :gutter="16"> + <a-col :span="8"> + <a-statistic + title="鏁翠綋鍋ュ悍搴�" + :value="healthData.overallHealth" + :precision="0" + suffix="%" + :value-style="{ color: healthData.healthColor }" + /> + </a-col> + <a-col :span="8"> + <a-statistic + title="棰勬祴鍓╀綑瀵垮懡" + :value="healthData.predictedLife" + suffix="澶�" + /> + </a-col> + <a-col :span="8"> + <a-statistic + title="鏁呴殰椋庨櫓绛夌骇" + :value="healthData.riskLevel" + :value-style="{ color: healthData.riskColor }" + /> + </a-col> + </a-row> + <div class="mt-4"> + <a-progress + :percent="healthData.overallHealth" + :stroke-color="healthData.healthColor" + :show-info="false" + /> + </div> + <a-divider orientation="left">棰勬祴鎬х淮鎶ゅ缓璁�</a-divider> + <a-table + :columns="maintenanceColumns" + :data-source="maintenanceData" + :pagination="false" + size="small" + > + <template #urgency="{ text }"> + <a-tag :color="getUrgencyColor(text)">{{ text }}</a-tag> + </template> + <template #action="{ record }"> + <a-button type="link" size="small" @click="handleMaintenance(record)">澶勭悊</a-button> + </template> + </a-table> + </a-card> + </a-col> + </a-row> + + <!-- 瀹炴椂鏁版嵁瓒嬪娍鍥� --> + <a-card title="瀹炴椂鏁版嵁瓒嬪娍鍥�" class="mb-4"> + <a-row :gutter="16"> + <a-col :span="8"> + <div id="spindleVibrationChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="spindleTemperatureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="spindleCurrentChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="spindleSpeedChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="servoMotorCurrentChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="servoMotorTemperatureChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="axisMotionSmoothnessChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="guideRailTemperatureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="guideRailResistanceNoiseChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="hydraulicPressureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="hydraulicFlowChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="hydraulicOilTemperatureChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="airSourcePressureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="coolantFlowChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="coolantTemperatureChart" style="height: 300px;"></div> + </a-col> + </a-row> + + </a-card> + <a-row :gutter="16"> + <!-- 澶囦欢瀵垮懡棰勬祴 --> + <a-col :span="12"> + <a-card title="澶囦欢瀵垮懡棰勬祴" class="mb-4"> + <a-table + :columns="sparePartColumns" + :data-source="sparePartData" + :pagination="false" + size="small" + > + <template #status="{ text }"> + <a-tag :color="getStatusColor(text)">{{ text }}</a-tag> + </template> + </a-table> + </a-card> + </a-col> + + <!-- 鍘嗗彶缁存姢璁板綍 --> + <a-col :span="12"> + <a-card title="鍘嗗彶缁存姢璁板綍" class="mb-4"> + <a-timeline> + <a-timeline-item v-for="item in historyData" :key="item.id" :color="item.color"> + {{ item.date }} - {{ item.type }}: {{ item.description }} + </a-timeline-item> + </a-timeline> + </a-card> + </a-col> + </a-row> + + </div> +</template> + +<script lang="ts"> +import { defineComponent, reactive, onMounted, onUnmounted } from 'vue'; +import * as echarts from 'echarts'; + +export default defineComponent({ + name: 'CNCMachiningCenterDetail', + setup() { + // 妯℃嫙鏁版嵁 + const deviceInfo = reactive({ + deviceName: 'CNC鍔犲伐涓績 #CNC-001', + deviceType: 'CNC鍔犲伐涓績', + deviceId: 'CNC-2024-001', + installDate: '2023-08-20', + serviceLife: 15, + status: '杩愯涓�', + statusColor: '#52c41a' + }); + + const healthData = reactive({ + overallHealth: 95, + healthColor: '#52c41a', + predictedLife: 4500, + riskLevel: '浣庨闄�', + riskColor: '#52c41a' + }); + + const maintenanceColumns = [ + { + title: '缁存姢绫诲瀷', + dataIndex: 'type', + key: 'type' + }, + { + title: '缁存姢鍐呭', + dataIndex: 'content', + key: 'content' + }, + { + title: '寤鸿鏃堕棿', + dataIndex: 'suggestedTime', + key: 'suggestedTime' + }, + { + title: '绱ф�ョ▼搴�', + dataIndex: 'urgency', + key: 'urgency', + slots: { customRender: 'urgency' } + }, + { + title: '鎿嶄綔', + key: 'action', + slots: { customRender: 'action' } + } + ]; + + const maintenanceData = reactive([ + { + key: '1', + type: '渚嬭淇濆吇', + content: '妫�鏌ヤ富杞存鼎婊戠郴缁�', + suggestedTime: '2024-07-15', + urgency: '涓瓑' + }, + { + key: '2', + type: '浼烘湇绯荤粺缁存姢', + content: '妫�鏌杞翠己鏈嶇數鏈�', + suggestedTime: '2024-09-01', + urgency: '浣�' + }, + { + key: '3', + type: '瀵艰建缁存姢', + content: '琛ュ厖瀵艰建娑︽粦娌�', + suggestedTime: '2024-08-01', + urgency: '楂�' + } + ]); + + const getUrgencyColor = (urgency: string) => { + switch (urgency) { + case '楂�': + return 'red'; + case '涓瓑': + return 'orange'; + case '浣�': + return 'green'; + default: + return 'default'; + } + }; + + const sparePartColumns = [ + { + title: '澶囦欢鍚嶇О', + dataIndex: 'name', + key: 'name' + }, + { + title: '褰撳墠瀵垮懡', + dataIndex: 'currentLife', + key: 'currentLife', + customRender: ({ text }: { text: number }) => `${text}灏忔椂` + }, + { + title: '棰勬祴鍓╀綑瀵垮懡', + dataIndex: 'remainingLife', + key: 'remainingLife', + customRender: ({ text }: { text: number }) => `${text}灏忔椂` + }, + { + title: '鐘舵��', + dataIndex: 'status', + key: 'status', + slots: { customRender: 'status' } + } + ]; + + const sparePartData = reactive([ + { + key: '1', + name: '涓昏酱杞存壙', + currentLife: 8000, + remainingLife: 500, + status: '棰勮' + }, + { + key: '2', + name: 'X杞翠笣鏉�', + currentLife: 12000, + remainingLife: 2000, + status: '鑹ソ' + }, + { + key: '3', + name: '娑插帇娌�', + currentLife: 3000, + remainingLife: 200, + status: '棰勮' + } + ]); + + const historyData = reactive([ + { + id: '1', + date: '2024-05-20', + type: '渚嬭淇濆吇', + description: '涓昏酱娑︽粦绯荤粺妫�鏌�', + color: 'green' + }, + { + id: '2', + date: '2024-02-10', + type: '鏁呴殰缁翠慨', + description: 'Y杞翠己鏈嶇數鏈烘姤璀﹀鐞�', + color: 'red' + }, + { + id: '3', + date: '2023-11-01', + type: '骞村害淇濆吇', + description: '鍏ㄩ潰妫�鏌ヨ澶囪繍琛岀姸鎬�', + color: 'green' + } + ]); + + const getStatusColor = (status: string) => { + switch (status) { + case '鑹ソ': + return '#52c41a'; + case '棰勮': + return '#faad14'; + case '鍗遍櫓': + return '#f5222d'; + default: + return '#d9d9d9'; + } + }; + + const handleMaintenance = (record: any) => { + console.log('澶勭悊缁存姢寤鸿:', record); + // 瀹為檯椤圭洰涓繖閲屼細璋冪敤API澶勭悊缁存姢寤鸿 + }; + + return { + deviceInfo, + healthData, + maintenanceColumns, + maintenanceData, + sparePartColumns, + sparePartData, + historyData, + getStatusColor, + getUrgencyColor, + handleMaintenance + }; + }, + + mounted() { + // 鍒濆鍖栧浘琛� + const initChart = (chartId: string, title: string, chartData: any[], unit: string, baseValue: number, fluctuation: number) => { + const chart = echarts.init(document.getElementById(chartId)); + + const updateChart = () => { + // 鐢熸垚鏂扮殑鏁版嵁鐐� + const now = new Date(); + const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; + const newValue = (baseValue + (Math.random() * 2 - 1) * fluctuation).toFixed(2); + + // 娣诲姞鏂版暟鎹偣锛屾渶澶氫繚鐣�60涓偣锛�5鍒嗛挓鏁版嵁锛� + chartData.push({ + time, + value: newValue + }); + if (chartData.length > 60) { + chartData.shift(); + } + + const option = { + title: { + text: title, + left: 'center' + }, + tooltip: { + trigger: 'axis', + formatter: (params) => { + return `${params[0].axisValueLabel}<br/>${params[0].marker} ${params[0].seriesName}: ${params[0].data}${unit}`; + } + }, + grid: { + left: '8%', // 澧炲姞宸︿晶杈硅窛 + right: '5%', + bottom: '10%', + containLabel: true + }, + xAxis: { + type: 'category', + data: chartData.map(item => item.time) + }, + yAxis: { + type: 'value', + axisLabel: { + formatter: (value) => `${value}${unit}` + } + }, + series: [ + { + data: chartData.map(item => parseFloat(item.value)), + type: 'line', + smooth: true, + lineStyle: { + width: 2, + color: '#5470C6' // Default 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); + }; + + // 鐢熸垚鍒濆鏁版嵁鐐癸紙60涓偣锛�5鍒嗛挓鏁版嵁锛� + for (let i = 0; i < 60; i++) { + const now = new Date(Date.now() - (60 - i) * 5000); // 鐢熸垚杩囧幓5鍒嗛挓鐨勬暟鎹� + const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; + const newValue = (baseValue + (Math.random() * 2 - 1) * fluctuation).toFixed(2); + chartData.push({ + time, + value: newValue + }); + } + + // 鍒濆娓叉煋 + updateChart(); + + // 姣�5绉掓洿鏂颁竴娆℃暟鎹� + const intervalId = setInterval(updateChart, 5000); + + window.addEventListener('resize', () => { + chart.resize(); + }); + onUnmounted(() => { + clearInterval(intervalId); + }); + + }; + + + + + initChart('spindleVibrationChart', '涓昏酱鎸姩', [], 'mm/s', 3.0, 0.5); + initChart('spindleTemperatureChart', '涓昏酱娓╁害', [], '掳C', 50, 3); + initChart('spindleCurrentChart', '涓昏酱鐢垫祦', [], 'A', 150, 10); + initChart('spindleSpeedChart', '涓昏酱杞��', [], 'RPM', 8000, 100); + initChart('servoMotorCurrentChart', '浼烘湇鐢垫満鐢垫祦', [], 'A', 50, 5); + initChart('servoMotorTemperatureChart', '浼烘湇鐢垫満娓╁害', [], '掳C', 45, 3); + initChart('axisMotionSmoothnessChart', '杞磋繍鍔ㄥ钩绋虫��', [], '', 0.8, 0.1); + initChart('guideRailTemperatureChart', '瀵艰建娓╁害', [], '掳C', 35, 2); + initChart('guideRailResistanceNoiseChart', '瀵艰建杩愬姩闃诲姏/鍣煶', [], 'dB', 10, 2); + initChart('hydraulicPressureChart', '娑插帇绯荤粺鍘嬪姏', [], 'bar', 150, 5); + initChart('hydraulicFlowChart', '娑插帇绯荤粺娴侀噺', [], 'L/min', 20, 2); + initChart('hydraulicOilTemperatureChart', '娑插帇娌规俯搴�', [], '掳C', 55, 3); + initChart('airSourcePressureChart', '姘旀簮鍘嬪姏', [], 'bar', 0.7, 0.05); + initChart('coolantFlowChart', '鍐峰嵈娑叉祦閲�', [], 'L/min', 30, 3); + initChart('coolantTemperatureChart', '鍐峰嵈娑叉俯搴�', [], '掳C', 28, 2); + } + +}); +</script> + +<style scoped> +.device-detail-container { + padding: 16px; + background: #f0f2f5; +} + +.mt-4 { + margin-top: 16px; +} + +.mb-4 { + margin-bottom: 16px; +} +</style> \ No newline at end of file diff --git a/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/device-detail.vue b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/device-detail.vue new file mode 100644 index 0000000..70a9732 --- /dev/null +++ b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/device-detail.vue @@ -0,0 +1,439 @@ +<template> + <div class="device-detail-container"> + <a-page-header + title="璁惧棰勬祴鎬х淮鎶よ鎯�" + :sub-title="deviceInfo.deviceName" + @back="() => $router.go(-1)" + /> + + <a-row :gutter="16" class="mt-4"> + <!-- 璁惧鍩烘湰淇℃伅 --> + <a-col :span="8"> + <a-card title="璁惧鍩烘湰淇℃伅" class="mb-4" :style="{ height: '440px' }"> + <a-descriptions bordered :column="1"> + <a-descriptions-item label="璁惧鍚嶇О">{{ deviceInfo.deviceName }}</a-descriptions-item> + <a-descriptions-item label="璁惧绫诲瀷">{{ deviceInfo.deviceType }}</a-descriptions-item> + <a-descriptions-item label="璁惧缂栧彿">{{ deviceInfo.deviceId }}</a-descriptions-item> + <a-descriptions-item label="瀹夎鏃ユ湡">{{ deviceInfo.installDate }}</a-descriptions-item> + <a-descriptions-item label="浣跨敤骞撮檺">{{ deviceInfo.serviceLife }}骞�</a-descriptions-item> + <a-descriptions-item label="褰撳墠鐘舵��"> + <a-tag :color="deviceInfo.statusColor">{{ deviceInfo.status }}</a-tag> + </a-descriptions-item> + </a-descriptions> + </a-card> + </a-col> + + <!-- 璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿 --> + <a-col :span="16"> + <a-card title="璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿" class="mb-4" :style="{ height: '440px' }"> + <a-row :gutter="16"> + <a-col :span="8"> + <a-statistic + title="鏁翠綋鍋ュ悍搴�" + :value="healthData.overallHealth" + :precision="0" + suffix="%" + :value-style="{ color: healthData.healthColor }" + /> + </a-col> + <a-col :span="8"> + <a-statistic + title="棰勬祴鍓╀綑瀵垮懡" + :value="healthData.predictedLife" + suffix="澶�" + /> + </a-col> + <a-col :span="8"> + <a-statistic + title="鏁呴殰椋庨櫓绛夌骇" + :value="healthData.riskLevel" + :value-style="{ color: healthData.riskColor }" + /> + </a-col> + </a-row> + <div class="mt-4"> + <a-progress + :percent="healthData.overallHealth" + :stroke-color="healthData.healthColor" + :show-info="false" + /> + </div> + <a-divider orientation="left">棰勬祴鎬х淮鎶ゅ缓璁�</a-divider> + <a-table + :columns="maintenanceColumns" + :data-source="maintenanceData" + :pagination="false" + size="small" + > + <template #urgency="{ text }"> + <a-tag :color="getUrgencyColor(text)">{{ text }}</a-tag> + </template> + <template #action="{ record }"> + <a-button type="link" size="small" @click="handleMaintenance(record)">澶勭悊</a-button> + </template> + </a-table> + </a-card> + </a-col> + </a-row> + + <!-- 瀹炴椂鏁版嵁瓒嬪娍鍥� --> + <a-card title="瀹炴椂鏁版嵁瓒嬪娍鍥�" class="mb-4"> + <a-row :gutter="16"> + <a-col :span="8"> + <div id="temperatureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="vibrationChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="currentChart" style="height: 300px;"></div> + </a-col> + </a-row> + </a-card> + <a-row :gutter="16"> + <!-- 澶囦欢瀵垮懡棰勬祴 --> + <a-col :span="12"> + <a-card title="澶囦欢瀵垮懡棰勬祴" class="mb-4"> + <a-table + :columns="sparePartColumns" + :data-source="sparePartData" + :pagination="false" + size="small" + > + <template #status="{ text }"> + <a-tag :color="getStatusColor(text)">{{ text }}</a-tag> + </template> + </a-table> + </a-card> + </a-col> + + <!-- 鍘嗗彶缁存姢璁板綍 --> + <a-col :span="12"> + <a-card title="鍘嗗彶缁存姢璁板綍" class="mb-4"> + <a-timeline> + <a-timeline-item v-for="item in historyData" :key="item.id" :color="item.color"> + {{ item.date }} - {{ item.type }}: {{ item.description }} + </a-timeline-item> + </a-timeline> + </a-card> + </a-col> + </a-row> + + </div> +</template> + +<script lang="ts"> +import { defineComponent, reactive, onMounted, onUnmounted } from 'vue'; +import * as echarts from 'echarts'; + +export default defineComponent({ + name: 'DeviceDetail', + setup() { + // 妯℃嫙鏁版嵁 + const deviceInfo = reactive({ + deviceName: 'CNC鍔犲伐涓績', + deviceType: '鏁版帶鏈哄簥', + deviceId: 'CNC-2023-001', + installDate: '2023-01-15', + serviceLife: 10, + status: '杩愯涓�', + statusColor: '#52c41a' + }); + + const healthData = reactive({ + overallHealth: 85, + healthColor: '#52c41a', + predictedLife: 1825, + riskLevel: '涓闄�', + riskColor: '#faad14' + }); + + const maintenanceColumns = [ + { + title: '缁存姢绫诲瀷', + dataIndex: 'type', + key: 'type' + }, + { + title: '缁存姢鍐呭', + dataIndex: 'content', + key: 'content' + }, + { + title: '寤鸿鏃堕棿', + dataIndex: 'suggestedTime', + key: 'suggestedTime' + }, + { + title: '绱ф�ョ▼搴�', + dataIndex: 'urgency', + key: 'urgency' + }, + { + title: '鎿嶄綔', + key: 'action', + slots: { customRender: 'action' } + }, + { + title: '绱ф�ョ▼搴�', + dataIndex: 'urgency', + key: 'urgency', + slots: { customRender: 'urgency' } + }, + { + title: '鎿嶄綔', + key: 'action', + slots: { customRender: 'action' } + } + ]; + + const maintenanceData = reactive([ + { + key: '1', + type: '娑︽粦淇濆吇', + content: '涓昏酱娑︽粦绯荤粺闇�瑕佹洿鎹㈡鼎婊戞补', + suggestedTime: '2024-03-15', + urgency: '涓瓑' + }, + { + key: '2', + type: '鏍″噯妫�鏌�', + content: 'X杞村杞ㄩ渶瑕侀噸鏂版牎鍑�', + suggestedTime: '2024-04-01', + urgency: '楂�' + } + ]); + + const getUrgencyColor = (urgency: string) => { + switch (urgency) { + case '楂�': + return 'red'; + case '涓瓑': + return 'orange'; + case '浣�': + return 'green'; + default: + return 'default'; + } + }; + + const sparePartColumns = [ + { + title: '澶囦欢鍚嶇О', + dataIndex: 'name', + key: 'name' + }, + { + title: '褰撳墠瀵垮懡', + dataIndex: 'currentLife', + key: 'currentLife', + customRender: ({ text }: { text: number }) => `${text}澶ー + }, + { + title: '棰勬祴鍓╀綑瀵垮懡', + dataIndex: 'remainingLife', + key: 'remainingLife', + customRender: ({ text }: { text: number }) => `${text}澶ー + }, + { + title: '鐘舵��', + dataIndex: 'status', + key: 'status', + slots: { customRender: 'status' } + } + ]; + + const sparePartData = reactive([ + { + key: '1', + name: '涓昏酱杞存壙', + currentLife: 365, + remainingLife: 120, + status: '鑹ソ' + }, + { + key: '2', + name: 'X杞村杞�', + currentLife: 730, + remainingLife: 30, + status: '棰勮' + }, + { + key: '3', + name: '鍒�鍏峰す澶�', + currentLife: 180, + remainingLife: 90, + status: '鑹ソ' + } + ]); + + const historyData = reactive([ + { + id: '1', + date: '2024-01-10', + type: '瀹氭湡淇濆吇', + description: '瀹屾垚瀛e害淇濆吇锛屾洿鎹㈡鼎婊戞补鍜岃繃婊ゅ櫒', + color: 'green' + }, + { + id: '2', + date: '2023-10-15', + type: '鏁呴殰缁翠慨', + description: '淇涓昏酱鐢垫満杩囩儹闂', + color: 'red' + }, + { + id: '3', + date: '2023-07-20', + type: '瀹氭湡淇濆吇', + description: '瀹屾垚鍗婂勾淇濆吇锛屾牎鍑嗗悇杞寸簿搴�', + color: 'green' + } + ]); + + const getStatusColor = (status: string) => { + switch (status) { + case '鑹ソ': + return '#52c41a'; + case '棰勮': + return '#faad14'; + case '鍗遍櫓': + return '#f5222d'; + default: + return '#d9d9d9'; + } + }; + + const handleMaintenance = (record: any) => { + console.log('澶勭悊缁存姢寤鸿:', record); + // 瀹為檯椤圭洰涓繖閲屼細璋冪敤API澶勭悊缁存姢寤鸿 + }; + + return { + deviceInfo, + healthData, + maintenanceColumns, + maintenanceData, + sparePartColumns, + sparePartData, + historyData, + getStatusColor, + getUrgencyColor, + handleMaintenance + }; + }, + + mounted() { + // 鍒濆鍖栧浘琛� + const initChart = (chartId: string, title: string, chartData: any[], unit: string, baseValue: number, fluctuation: number) => { + const chart = echarts.init(document.getElementById(chartId)); + + // 棰勭敓鎴�60涓暟鎹偣 + for (let i = 0; i < 60; i++) { + const now = new Date(Date.now() - (60 - 1 - i) * 5000); // 5绉掗棿闅� + const value = +(Math.random() * fluctuation + baseValue - fluctuation / 2).toFixed(2); + chartData.push({ + time: `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`, + value: value + }); + } + + const updateChart = () => { + // 鐢熸垚鏂扮殑鏁版嵁鐐� + const now = new Date(); + const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; + const newValue = +(Math.random() * fluctuation + baseValue - fluctuation / 2).toFixed(2); + + // 娣诲姞鏂版暟鎹偣锛屾渶澶氫繚鐣�60涓偣锛�5鍒嗛挓鏁版嵁锛� + chartData.push({ + time, + value: newValue + }); + if (chartData.length > 60) { + chartData.shift(); + } + + const option = { + title: { + text: title, + left: 'center' + }, + tooltip: { + trigger: 'axis', + formatter: (params: any) => { + return `${params[0].axisValueLabel}<br/>${params[0].marker} ${params[0].seriesName}: ${params[0].data}${unit}`; + } + }, + xAxis: { + type: 'category', + data: chartData.map(item => item.time) + }, + yAxis: { + type: 'value', + axisLabel: { + formatter: (value: number) => { + return `${value}${unit}`; + } + } + }, + series: [ + { + data: chartData.map(item => parseFloat(item.value)), + type: 'line', + smooth: true, + lineStyle: { + width: 2, + color: chartId === 'temperatureChart' ? '#5470C6' : chartId === 'vibrationChart' ? '#91CC75' : '#FAC858' // 鏍规嵁鍥捐〃ID璁剧疆棰滆壊 + }, + areaStyle: { + opacity: 0.8, + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: chartId === 'temperatureChart' ? 'rgba(84,112,198,0.3)' : chartId === 'vibrationChart' ? 'rgba(145,204,117,0.3)' : 'rgba(250,200,88,0.3)' + }, + { + offset: 1, + color: chartId === 'temperatureChart' ? 'rgba(84,112,198,0)' : chartId === 'vibrationChart' ? 'rgba(145,204,117,0)' : 'rgba(250,200,88,0)' + } + ]) + }, + } + ] + }; + chart.setOption(option); + }; + + // 鍒濆娓叉煋 + updateChart(); + + // 姣�5绉掓洿鏂颁竴娆℃暟鎹� + const intervalId = setInterval(updateChart, 5000); + + window.addEventListener('resize', () => { + chart.resize(); + }); + onUnmounted(() => { + clearInterval(intervalId); + }); + + }; + + const temperatureData: any[] = reactive([]); + const vibrationData: any[] = reactive([]); + const currentData: any[] = reactive([]); + + initChart('temperatureChart', '娓╁害瓒嬪娍', temperatureData, '掳C', 60, 5); + initChart('vibrationChart', '鎸姩瓒嬪娍', vibrationData, 'mm/s', 10, 2); + initChart('currentChart', '鐢垫祦瓒嬪娍', currentData, 'A', 50, 10); + } + +}); +</script> + +<style scoped> +.device-detail-container { + padding: 16px; + background: #f0f2f5; +} +</style> \ No newline at end of file diff --git a/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/index.vue b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/index.vue new file mode 100644 index 0000000..4a04417 --- /dev/null +++ b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/index.vue @@ -0,0 +1,409 @@ +<template> + <Page :auto-content-height="true" :padding="false" class="p-0" content-class="flex flex-col w-full gap-4"> + <div class="p-5"> + + <!-- 璁惧鍋ュ悍鐘舵�佸彲瑙嗗寲 --> + <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6"> + <Card title="璁惧鍋ュ悍搴﹀垎甯�" > + <!-- 杩欓噷灏嗘斁缃仴搴峰害鍒嗗竷鍥捐〃 --> + <div ref="chartRef" class="h-64 w-full"></div> + </Card> + <Card title="鍏抽敭鎸囨爣" > + <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 font-bold">85%</p> + </div> + <div class="p-2 bg-yellow-50 rounded"> + <p class="text-sm text-gray-600">棰勮璁惧</p> + <p class="text-xl font-bold">12%</p> + </div> + <div class="p-2 bg-red-50 rounded"> + <p class="text-sm text-gray-600">鏁呴殰璁惧</p> + <p class="text-xl font-bold">3%</p> + </div> + <div class="p-2 bg-green-50 rounded"> + <p class="text-sm text-gray-600">缁存姢瀹屾垚</p> + <p class="text-xl font-bold">92%</p> + </div> + <div class="p-2 bg-blue-50 rounded"> + <p class="text-sm text-gray-600">寰呭鐞嗗伐鍗�</p> + <p class="text-xl font-bold">{{ healthData.pendingWorkOrders }}</p> + </div> + <div class="p-2 bg-yellow-50 rounded"> + <p class="text-sm text-gray-600">澶囦欢搴撳瓨棰勮</p> + <p class="text-xl font-bold">{{ healthData.sparePartWarnings }}</p> + </div> + </div> + </Card> + </div> + + <!-- 鏁呴殰棰勬祴涓庨璀� --> + <Card title="鏁呴殰棰勬祴涓庨璀�" class="mb-6"> + <div class="grid grid-cols-1 gap-4"> + + <!-- 杩欓噷灏嗘斁缃璀﹁〃鏍� --> + <a-table :columns="warningColumns" :data-source="warningData" :pagination="false" class="w-full"> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'status'"> + <a-tag :color="getStatusColor(record.status)"> + {{ record.status }} + </a-tag> + </template> + <template v-else-if="column.key === 'action'"> + <a-button type="link" @click="handleDetail(record)">璇︽儏</a-button> + <a-button type="link" @click="generateWorkOrder(record)" :disabled="record.maintenanceSuggestion === '鏆傛棤寤鸿'">鐢熸垚宸ュ崟</a-button> + </template> + </template> + </a-table> + </div> + + </Card> + + <!-- 澶囦欢淇℃伅 --> + <div class="grid grid-cols-2 gap-4 mb-6"> + <!-- 澶囦欢瀵垮懡棰勬祴 --> + <Card title="澶囦欢瀵垮懡棰勬祴"> + <a-table :columns="lifePredictionColumns" :data-source="lifePredictionData" :pagination="false" class="w-full"> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'lifeStatus'"> + <a-tag :color="getLifeStatusColor(record.remainingDays)"> + {{ getLifeStatus(record.remainingDays) }} + </a-tag> + </template> + <template v-else-if="column.key === 'action'"> + <a-button type="link" @click="showLifePredictionDetail(record)">璇︽儏</a-button> + </template> + </template> + </a-table> + </Card> + + <!-- 澶囦欢搴撳瓨涓庨璀� --> + <Card title="澶囦欢搴撳瓨涓庨璀�"> + <a-table :columns="sparePartColumns" :data-source="sparePartData" :pagination="false" class="w-full"> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'stockStatus'"> + <a-tag :color="getStockStatusColor(record.currentStock, record.safetyStock)"> + {{ getStockStatus(record.currentStock, record.safetyStock) }} + </a-tag> + </template> + <template v-else-if="column.key === 'action'"> + <a-button type="link" @click="showSparePartDetail(record)">璇︽儏</a-button> + </template> + </template> + </a-table> + </Card> + </div> + + + </div> + </Page> +</template> + +<script setup lang="ts"> +import { onMounted, ref } from 'vue'; +import { Page } from '@vben/common-ui'; +import { Card, Table, Tag, Button, Form, Input, Textarea, Select, message } from 'ant-design-vue'; +import * as echarts from 'echarts'; + +const chartRef = ref<HTMLElement | null>(null); + +const healthData = { + healthy: 85, + warning: 12, + critical: 3, + maintained: 92, + pendingWorkOrders: 5, // 妯℃嫙寰呭鐞嗗伐鍗曟暟 + sparePartWarnings: 2, // 妯℃嫙澶囦欢搴撳瓨棰勮鏁� +}; + +// 楂橀闄╄澶囬璀︽暟鎹� +const warningColumns = [ + { title: '璁惧鍚嶇О', dataIndex: 'name', key: 'name' }, + + { title: '鍏抽敭閮ㄤ欢', dataIndex: 'component', key: 'component' }, + { title: '椋庨櫓鎸囨爣', dataIndex: 'indicator', key: 'indicator' }, + { title: '褰撳墠鍊�', dataIndex: 'value', key: 'value' }, + { title: '闃堝��', dataIndex: 'threshold', key: 'threshold' }, + { title: '鐘舵��', dataIndex: 'status', key: 'status' }, + { title: '缁存姢寤鸿', dataIndex: 'maintenanceSuggestion', key: 'maintenanceSuggestion' }, + { title: '鎿嶄綔', key: 'action' }, +]; + +// 妯℃嫙楂橀闄╄澶囨暟鎹� +const warningData = ref([ + { + key: '1', + name: 'SMT璐寸墖鏈�', + type: 'SMT鏈哄櫒', + component: '浼犻�佸甫', + indicator: '鎸姩骞呭害', + value: 0.8, + threshold: 0.5, + status: '楂橀闄�', + maintenanceSuggestion: '寤鸿鏇存崲浼犻�佸甫杞存壙', + maintenanceType: '棰勯槻鎬х淮鎶�', + }, + { + key: '2', + name: 'CNC鍔犲伐涓績', + type: 'CNC', + component: '涓昏酱', + indicator: '娓╁害', + value: 85, + threshold: 80, + status: '涓闄�', + maintenanceSuggestion: '寤鸿瀵逛富杞磋繘琛屾鼎婊戜繚鍏�', + maintenanceType: '娑︽粦缁存姢', + }, + { + key: '3', + name: '娉ㄥ鏈�', + type: '娉ㄥ璁惧', + component: '娑插帇绯荤粺', + indicator: '鍘嬪姏', + value: 120, + threshold: 150, + status: '浣庨闄�', + maintenanceSuggestion: '寤鸿妫�鏌ユ恫鍘嬬郴缁熷瘑灏佷欢', + maintenanceType: '妫�鏌ョ淮鎶�', + }, + { + key: '4', + name: '绌哄帇鏈�', + type: '绌烘皵鍘嬬缉鏈�', + component: '鐢垫満', + indicator: '鐢垫祦', + value: 49, + threshold: 50, + status: '浣庨闄�', + maintenanceSuggestion: '鏆傛棤寤鸿', + maintenanceType: '鏃�', + }, +]); + +// 澶囦欢搴撳瓨鏁版嵁 +const sparePartColumns = [ + { title: '澶囦欢鍚嶇О', dataIndex: 'name', key: 'name' }, + { title: '褰撳墠搴撳瓨', dataIndex: 'currentStock', key: 'currentStock' }, + { title: '瀹夊叏搴撳瓨', dataIndex: 'safetyStock', key: 'safetyStock' }, + { title: '棰勬祴闇�姹�', dataIndex: 'predictedDemand', key: 'predictedDemand' }, + { title: '搴撳瓨鐘舵��', dataIndex: 'stockStatus', key: 'stockStatus' }, +]; + +// 妯℃嫙澶囦欢鏁版嵁 +const sparePartData = ref([ + { + key: '1', + name: '浼犻�佸甫杞存壙', + currentStock: 10, + safetyStock: 5, + predictedDemand: 3, + }, + { + key: '2', + name: '涓昏酱娑︽粦娌�', + currentStock: 2, + safetyStock: 3, + predictedDemand: 1, + }, + { + key: '3', + name: '娑插帇绯荤粺瀵嗗皝浠�', + currentStock: 20, + safetyStock: 10, + predictedDemand: 5, + }, + { + key: '4', + name: '鐢垫満纰冲埛', + currentStock: 8, + safetyStock: 10, + predictedDemand: 2, + }, +]); +const getStatusColor = (status) => { + switch (status) { + case '楂橀闄�': return 'red'; + case '涓闄�': return 'orange'; + case '浣庨闄�': return 'yellow'; + default: return 'green'; + } +}; + + + +// 宸ュ崟琛ㄥ崟鏁版嵁 +const workOrderForm = ref({ + type: '', + equipment: '', + content: '', + priority: 'medium', +}); + +const selectedMaintenance = ref(null); + +const lifePredictionColumns = [ + { title: '璁惧鍚嶇О', dataIndex: 'deviceName', key: 'deviceName' }, + { title: '閮ㄤ欢鍚嶇О', dataIndex: 'componentName', key: 'componentName' }, + { title: '澶囦欢鍚嶇О', dataIndex: 'name', key: 'name' }, + { title: '棰勬祴瀵垮懡 (澶�)', dataIndex: 'predictedLife', key: 'predictedLife' }, + { title: '鍓╀綑瀵垮懡 (澶�)', dataIndex: 'remainingDays', key: 'remainingDays' }, + { title: '鐘舵��', dataIndex: 'lifeStatus', key: 'lifeStatus' }, + { title: '鎿嶄綔', dataIndex: 'action', key: 'action' }, +]; + +const lifePredictionData = ref([ + { key: '1', deviceName: 'SMT璐寸墖鏈�', componentName: '浼犻�佸甫', name: '杞存壙A', predictedLife: 365, remainingDays: 120 }, + { key: '2', deviceName: 'CNC鍔犲伐涓績', componentName: '涓昏酱', name: '榻胯疆B', predictedLife: 730, remainingDays: 30 }, + { key: '3', deviceName: '娉ㄥ鏈�', componentName: '娑插帇绯荤粺', name: '婊よ姱C', predictedLife: 180, remainingDays: 90 }, + { key: '4', deviceName: '绌哄帇鏈�', componentName: '鐢垫満', name: '浼犳劅鍣―', predictedLife: 500, remainingDays: 10 }, + { key: '5', deviceName: '鐒婃帴鏈哄櫒浜�', componentName: '鐒婃灙', name: '鐒婂槾E', predictedLife: 240, remainingDays: 60 } +]); + +const getLifeStatus = (remainingDays) => { + if (remainingDays <= 30) { + return '鍗冲皢鍒版湡'; + } else if (remainingDays <= 90) { + return '涓湡棰勮'; + } else { + return '瀵垮懡鍏呰冻'; + } +}; + +const getLifeStatusColor = (remainingDays) => { + if (remainingDays <= 30) { + return 'red'; + } else if (remainingDays <= 90) { + return 'orange'; + } else { + return 'green'; + } +}; + +const showLifePredictionDetail = (record) => { + message.info(`澶囦欢鍚嶇О: ${record.name}, 棰勬祴瀵垮懡: ${record.predictedLife}澶�, 鍓╀綑瀵垮懡: ${record.remainingDays}澶ー); +}; + + + +const getStockStatus = (currentStock, safetyStock) => { + if (currentStock <= safetyStock) { + return '搴撳瓨棰勮'; + } else { + return '搴撳瓨鍏呰冻'; + } +}; + +const getStockStatusColor = (currentStock, safetyStock) => { + if (currentStock <= safetyStock) { + return 'red'; + } else { + return 'green'; + } +}; + +const showSparePartDetail = (record) => { + message.info(`澶囦欢鍚嶇О: ${record.name}, 褰撳墠搴撳瓨: ${record.currentStock}, 瀹夊叏搴撳瓨: ${record.safetyStock}, 棰勬祴闇�姹�: ${record.predictedDemand}`); +}; + +const generateWorkOrder = (record) => { + selectedMaintenance.value = record; + workOrderForm.value = { + type: record.maintenanceType, + equipment: record.name, + content: record.maintenanceSuggestion, + priority: 'medium', + }; +}; + +const submitWorkOrder = () => { + console.log('鎻愪氦宸ュ崟:', workOrderForm.value); + // 杩欓噷鍙互娣诲姞宸ュ崟鎻愪氦閫昏緫 + message.success('宸ュ崟鎻愪氦鎴愬姛'); + selectedMaintenance.value = null; + workOrderForm.value = { + type: '', + equipment: '', + content: '', + priority: 'medium', + }; +}; + +import { useRouter } from 'vue-router'; + +const router = useRouter(); + +const handleDetail = (record) => { + // 鏍规嵁璁惧鍚嶇О涓嶅悓璺宠浆涓嶅悓鐨勮鎯呴〉闈� + if (record.name === 'SMT璐寸墖鏈�') { + console.log('璁惧ID111:', record.name) + router.push({ path: '/predictive/smt-detail', query: { deviceId: record.key } }); + } else if (record.name === 'CNC鍔犲伐涓績') { + router.push({ path: '/predictive/predictive-detail', query: { deviceId: record.key } }); + } else if (record.name === '娉ㄥ鏈�') { + router.push({ path: '/predictive/injection-detail', query: { deviceId: record.key } }); + } else { + router.push({ path: '/predictive/air-compressor-detail', query: { deviceId: record.key } }); + } +}; + +onMounted(() => { + if (chartRef.value) { + const myChart = echarts.init(chartRef.value); + const option = { + tooltip: { + trigger: 'axis', + axisPointer: { + type: 'shadow' + } + }, + xAxis: { + type: 'category', + data: ['90%-100%', '80%-90%', '70%-80%', '60%-70%', '50%-60%', '40%-50%', '30%-40%', '20%-30%', '10%-20%', '0%-10%'], + axisLabel: { + rotate: 45 + } + }, + yAxis: { + type: 'value', + name: '璁惧鏁伴噺', + minInterval: 1 + }, + series: [ + { + name: '璁惧鏁伴噺', + type: 'bar', + barWidth: '60%', + data: [ + { value: 15, itemStyle: { color: '#52c41a' } }, + { value: 20, itemStyle: { color: '#52c41a' } }, + { value: 25, itemStyle: { color: '#faad14' } }, + { value: 15, itemStyle: { color: '#faad14' } }, + { value: 10, itemStyle: { color: '#faad14' } }, + { value: 5, itemStyle: { color: '#ff4d4f' } }, + { value: 5, itemStyle: { color: '#ff4d4f' } }, + { value: 3, itemStyle: { color: '#ff4d4f' } }, + { value: 1, itemStyle: { color: '#ff4d4f' } }, + { value: 1, itemStyle: { color: '#ff4d4f' } } + ], + label: { + show: true, + position: 'top' + } + } + ] + }; + myChart.setOption(option); + + // 鍝嶅簲寮忚皟鏁村浘琛ㄥぇ灏� + window.addEventListener('resize', () => { + myChart.resize(); + }); + } +}); +</script> + +<style scoped> +/* 鍙互鍦ㄨ繖閲屾坊鍔犵粍浠舵牱寮� */ +</style> diff --git a/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/injection-molding-machine-detail.vue b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/injection-molding-machine-detail.vue new file mode 100644 index 0000000..61b3576 --- /dev/null +++ b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/injection-molding-machine-detail.vue @@ -0,0 +1,549 @@ +<script lang="ts"> +import { defineComponent, onUnmounted, reactive } from 'vue'; + +import * as echarts from 'echarts'; + +export default defineComponent({ + name: 'InjectionMoldingMachineDetail', + setup() { + // 妯℃嫙鏁版嵁 + const deviceInfo = reactive({ + deviceName: '娉ㄥ鏈� #IM-001', + deviceType: '娉ㄥ鏈�', + deviceId: 'IM-2024-001', + installDate: '2023-08-20', + serviceLife: 15, + status: '杩愯涓�', + statusColor: '#52c41a' + }); + + const healthData = reactive({ + overallHealth: 88, + healthColor: '#faad14', + predictedLife: 1500, + riskLevel: '涓闄�', + riskColor: '#faad14' + }); + + const maintenanceColumns = [ + { + title: '缁存姢绫诲瀷', + dataIndex: 'type', + key: 'type' + }, + { + title: '缁存姢鍐呭', + dataIndex: 'content', + key: 'content' + }, + { + title: '寤鸿鏃堕棿', + dataIndex: 'suggestedTime', + key: 'suggestedTime' + }, + { + title: '绱ф�ョ▼搴�', + dataIndex: 'urgency', + key: 'urgency', + slots: { customRender: 'urgency' } + }, + { + title: '鎿嶄綔', + key: 'action', + slots: { customRender: 'action' } + } + ]; + + const maintenanceData = reactive([ + { + key: '1', + type: '渚嬭淇濆吇', + content: '妫�鏌ユ恫鍘嬫补姹℃煋搴�', + suggestedTime: '2024-07-15', + urgency: '涓瓑' + }, + { + key: '2', + type: '鏁呴殰棰勮', + content: '涓荤數鏈鸿酱鎵跨(鎹熼璀�', + suggestedTime: '2024-08-01', + urgency: '楂�' + }, + { + key: '3', + type: '鍐峰嵈绯荤粺缁存姢', + content: '妫�鏌ュ喎鍗存按娴侀噺', + suggestedTime: '2024-07-01', + urgency: '浣�' + } + ]); + + const getUrgencyColor = (urgency: string) => { + switch (urgency) { + case '涓瓑': { + return 'orange'; + } + case '浣�': { + return 'green'; + } + case '楂�': { + return 'red'; + } + default: { + return 'default'; + } + } + }; + + const sparePartColumns = [ + { + title: '澶囦欢鍚嶇О', + dataIndex: 'name', + key: 'name' + }, + { + title: '褰撳墠瀵垮懡', + dataIndex: 'currentLife', + key: 'currentLife', + customRender: ({ text }: { text: number }) => `${text}灏忔椂` + }, + { + title: '棰勬祴鍓╀綑瀵垮懡', + dataIndex: 'remainingLife', + key: 'remainingLife', + customRender: ({ text }: { text: number }) => `${text}灏忔椂` + }, + { + title: '鐘舵��', + dataIndex: 'status', + key: 'status', + slots: { customRender: 'status' } + } + ]; + + const sparePartData = reactive([ + { + key: '1', + name: '娑插帇娉�', + currentLife: 18_500, + remainingLife: 1500, + status: '棰勮' + }, + { + key: '2', + name: '鍔犵儹鍦�', + currentLife: 12_000, + remainingLife: 3000, + status: '鑹ソ' + }, + { + key: '3', + name: '铻烘潌鏂欑瓛', + currentLife: 25_000, + remainingLife: 5000, + status: '鑹ソ' + } + ]); + + const historyData = reactive([ + { + id: '1', + date: '2023-10-20', + type: '渚嬭淇濆吇', + description: '鏇存崲娑插帇娌规护鑺�', + color: 'green' + }, + { + id: '2', + date: '2023-08-15', + type: '鏁呴殰缁翠慨', + description: '娉ㄥ皠鍗曞厓鍗℃粸锛屾竻鐞嗗紓鐗�', + color: 'red' + }, + { + id: '3', + date: '2023-06-01', + type: '骞村害淇濆吇', + description: '鍏ㄩ潰妫�鏌ヨ澶囪繍琛岀姸鎬�', + color: 'blue' + } + ]); + + const getStatusColor = (status: string) => { + switch (status) { + case '鍗遍櫓': { + return '#f5222d'; + } + case '鑹ソ': { + return '#52c41a'; + } + case '棰勮': { + return '#faad14'; + } + default: { + return '#d9d9d9'; + } + } + }; + + const handleMaintenance = (record: any) => { + console.log('澶勭悊缁存姢寤鸿:', record); + // 瀹為檯椤圭洰涓繖閲屼細璋冪敤API澶勭悊缁存姢寤鸿 + }; + + return { + deviceInfo, + healthData, + maintenanceColumns, + maintenanceData, + sparePartColumns, + sparePartData, + historyData, + getStatusColor, + getUrgencyColor, + handleMaintenance + }; + }, + + mounted() { + // 鍒濆鍖栧浘琛� + const initChart = (chartId: string, title: string, chartData: any[], unit: string, baseValue: number, fluctuation: number) => { + const chart = echarts.init(document.getElementById(chartId)); + + // 纭繚鍒濆鏁版嵁鐐硅冻澶熸樉绀轰竴涓畬鏁寸殑瓒嬪娍 + if (chartData.length === 0) { + const now = new Date(); + for (let i = 0; i < 60; i++) { // 鐢熸垚60涓偣锛屼唬琛�5鍒嗛挓鐨勬暟鎹� + const time = new Date(now.getTime() - (59 - i) * 5000); // 姣忎釜鐐归棿闅�5绉� + chartData.push({ + time: time.toLocaleTimeString(), + value: (baseValue + Math.random() * fluctuation * 2 - fluctuation).toFixed(2), + }); + } + } + + const updateChart = () => { + // 鐢熸垚鏂扮殑鏁版嵁鐐� + const now = new Date(); + const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; + const newValue = (baseValue + Math.random() * fluctuation * 2 - fluctuation).toFixed(2); + + // 娣诲姞鏂版暟鎹偣锛屾渶澶氫繚鐣�60涓偣锛�5鍒嗛挓鏁版嵁锛� + chartData.push({ + time, + value: newValue + }); + if (chartData.length > 60) { + chartData.shift(); + } + + const option = { + title: { + text: title, + left: 'center' + }, + tooltip: { + trigger: 'axis', + formatter: (params) => { + return `${params[0].axisValueLabel}<br/>${params[0].marker} ${params[0].seriesName}: ${params[0].data}${unit}`; + } + }, + grid: { + left: '8%', // 澧炲姞宸︿晶杈硅窛 + right: '5%', + bottom: '10%', + containLabel: true + }, + xAxis: { + type: 'category', + data: chartData.map(item => item.time) + }, + yAxis: { + type: 'value', + axisLabel: { + formatter: (value) => `${value}${unit}` + } + }, + series: [ + { + data: chartData.map(item => Number.parseFloat(item.value)), + type: 'line', + smooth: true, + lineStyle: { + width: 2, + color: '#5470C6' // Default 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); + }; + + // 鍒濆娓叉煋 + updateChart(); + + // 姣�5绉掓洿鏂颁竴娆℃暟鎹� + const intervalId = setInterval(updateChart, 5000); + + window.addEventListener('resize', () => { + chart.resize(); + }); + onUnmounted(() => { + clearInterval(intervalId); + }); + + }; + + + const hydraulicOilTemperatureData: any[] = []; + const hydraulicOilPressureData: any[] = []; + const mainMotorCurrentData: any[] = []; + const screwSpeedData: any[] = []; + const moldClampingForceData: any[] = []; + const injectionPressureData: any[] = []; + const injectionSpeedData: any[] = []; + const barrelTemperatureData: any[] = []; + const coolingWaterTemperatureData: any[] = []; + const coolingWaterFlowData: any[] = []; + const ejectorPositionData: any[] = []; + const cycleTimeData: any[] = []; + + initChart('hydraulicOilTemperatureChart', '娑插帇娌规俯', hydraulicOilTemperatureData, '掳C', 50, 2); + initChart('hydraulicOilPressureChart', '娑插帇娌瑰帇', hydraulicOilPressureData, 'MPa', 150, 5); + initChart('mainMotorCurrentChart', '涓荤數鏈虹數娴�', mainMotorCurrentData, 'A', 80, 3); + initChart('screwSpeedChart', '铻烘潌杞��', screwSpeedData, 'rpm', 100, 10); + initChart('moldClampingForceChart', '閿佹ā鍔�', moldClampingForceData, 'kN', 1000, 50); + initChart('injectionPressureChart', '娉ㄥ皠鍘嬪姏', injectionPressureData, 'MPa', 1200, 80); + initChart('injectionSpeedChart', '娉ㄥ皠閫熷害', injectionSpeedData, 'mm/s', 150, 10); + initChart('barrelTemperatureChart', '鏂欑瓛娓╁害', barrelTemperatureData, '掳C', 200, 5); + initChart('coolingWaterTemperatureChart', '鍐峰嵈姘存俯搴�', coolingWaterTemperatureData, '掳C', 25, 2); + initChart('coolingWaterFlowChart', '鍐峰嵈姘存祦閲�', coolingWaterFlowData, 'L/min', 30, 3); + initChart('ejectorPositionChart', '椤跺嚭浣嶇疆', ejectorPositionData, 'mm', 50, 5); + initChart('cycleTimeChart', '寰幆鏃堕棿', cycleTimeData, 's', 30, 2); + } +}); +</script> + +<template> + <div class="device-detail-container"> + <a-page-header + :sub-title="deviceInfo.deviceName" + title="娉ㄥ鏈洪娴嬫�х淮鎶よ鎯�" + @back="() => $router.go(-1)" + /> + + <a-row :gutter="16" class="mt-4"> + <!-- 璁惧鍩烘湰淇℃伅 --> + <a-col :span="8"> + <a-card :style="{ height: '440px' }" class="mb-4" title="璁惧鍩烘湰淇℃伅"> + <a-descriptions :column="1" bordered> + <a-descriptions-item label="璁惧鍚嶇О">{{ deviceInfo.deviceName }}</a-descriptions-item> + <a-descriptions-item label="璁惧绫诲瀷">{{ deviceInfo.deviceType }}</a-descriptions-item> + <a-descriptions-item label="璁惧缂栧彿">{{ deviceInfo.deviceId }}</a-descriptions-item> + <a-descriptions-item label="瀹夎鏃ユ湡">{{ deviceInfo.installDate }}</a-descriptions-item> + <a-descriptions-item label="浣跨敤骞撮檺">{{ deviceInfo.serviceLife }}骞�</a-descriptions-item> + <a-descriptions-item label="褰撳墠鐘舵��"> + <a-tag :color="deviceInfo.statusColor">{{ deviceInfo.status }}</a-tag> + </a-descriptions-item> + </a-descriptions> + </a-card> + </a-col> + + <!-- 璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿 --> + <a-col :span="16"> + <a-card :style="{ height: '440px' }" class="mb-4" title="璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿"> + <a-row :gutter="16"> + <a-col :span="8"> + <a-statistic + :precision="0" + :value="healthData.overallHealth" + :value-style="{ color: healthData.healthColor }" + suffix="%" + title="鏁翠綋鍋ュ悍搴�" + /> + </a-col> + <a-col :span="8"> + <a-statistic + :value="healthData.predictedLife" + suffix="澶�" + title="棰勬祴鍓╀綑瀵垮懡" + /> + </a-col> + <a-col :span="8"> + <a-statistic + :value="healthData.riskLevel" + :value-style="{ color: healthData.riskColor }" + title="鏁呴殰椋庨櫓绛夌骇" + /> + </a-col> + </a-row> + <div class="mt-4"> + <a-progress + :percent="healthData.overallHealth" + :show-info="false" + :stroke-color="healthData.healthColor" + /> + </div> + <a-divider orientation="left">棰勬祴鎬х淮鎶ゅ缓璁�</a-divider> + <a-table + :columns="maintenanceColumns" + :data-source="maintenanceData" + :pagination="false" + size="small" + > + <template #urgency="{ text }"> + <a-tag :color="getUrgencyColor(text)">{{ text }}</a-tag> + </template> + <template #action="{ record }"> + <a-button size="small" type="link" @click="handleMaintenance(record)">澶勭悊</a-button> + </template> + </a-table> + </a-card> + </a-col> + </a-row> + + <!-- 瀹炴椂鏁版嵁瓒嬪娍鍥� --> + <a-card class="mb-4" title="瀹炴椂鏁版嵁瓒嬪娍鍥�"> + <a-row :gutter="16"> + <a-col :span="8"> + <div id="hydraulicOilTemperatureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="hydraulicOilPressureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="mainMotorCurrentChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="screwSpeedChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="moldClampingForceChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="injectionPressureChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="injectionSpeedChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="barrelTemperatureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="coolingWaterTemperatureChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="coolingWaterFlowChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="ejectorPositionChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="cycleTimeChart" style="height: 300px;"></div> + </a-col> + </a-row> + </a-card> + + <a-row :gutter="16"> + <!-- 澶囦欢瀵垮懡棰勬祴 --> + <a-col :span="12"> + <a-card class="mb-4" title="澶囦欢瀵垮懡棰勬祴"> + <a-table + :columns="sparePartColumns" + :data-source="sparePartData" + :pagination="false" + size="small" + > + <template #status="{ text }"> + <a-tag :color="getStatusColor(text)">{{ text }}</a-tag> + </template> + </a-table> + </a-card> + </a-col> + + <!-- 鍘嗗彶缁存姢璁板綍 --> + <a-col :span="12"> + <a-card class="mb-4" title="鍘嗗彶缁存姢璁板綍"> + <a-timeline> + <a-timeline-item v-for="item in historyData" :key="item.id" :color="item.color"> + {{ item.date }} - {{ item.type }}: {{ item.description }} + </a-timeline-item> + </a-timeline> + </a-card> + </a-col> + </a-row> + </div> +</template> + +<style scoped> +.device-detail-container { + padding: 16px; + background: #f0f2f5; +} + +.mt-4 { + margin-top: 16px; +} + +.mb-4 { + margin-bottom: 16px; +} +</style> + + +<style scoped> +.injection-molding-machine-detail { + padding: 16px; + background-color: #f0f2f5; +} + +.header-card { + margin-bottom: 16px; +} + +.device-info h2 { + font-size: 24px; + margin-bottom: 10px; +} + +.device-info p { + margin-bottom: 5px; +} + +.health-status h3 { + font-size: 20px; + margin-bottom: 10px; +} + +.ant-statistic-content { + font-size: 20px !important; +} + +.ant-list-item-meta-title { + font-size: 16px; +} + +.ant-list-item-meta-description { + font-size: 14px; + color: rgba(0, 0, 0, 0.65); +} +</style> diff --git a/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/smt-machine-detail.vue b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/smt-machine-detail.vue new file mode 100644 index 0000000..5d391ab --- /dev/null +++ b/eims-ui/apps/web-antd/src/views/eims/predictive-maintenance/smt-machine-detail.vue @@ -0,0 +1,509 @@ +<template> + <div class="device-detail-container"> + <a-page-header + title="SMT璐寸墖鏈洪娴嬫�х淮鎶よ鎯�" + :sub-title="deviceInfo.deviceName" + @back="() => $router.go(-1)" + /> + + <a-row :gutter="16" class="mt-4"> + <!-- 璁惧鍩烘湰淇℃伅 --> + <a-col :span="8"> + <a-card title="璁惧鍩烘湰淇℃伅" class="mb-4" :style="{ height: '440px' }"> + <a-descriptions bordered :column="1"> + <a-descriptions-item label="璁惧鍚嶇О">{{ deviceInfo.deviceName }}</a-descriptions-item> + <a-descriptions-item label="璁惧绫诲瀷">{{ deviceInfo.deviceType }}</a-descriptions-item> + <a-descriptions-item label="璁惧缂栧彿">{{ deviceInfo.deviceId }}</a-descriptions-item> + <a-descriptions-item label="瀹夎鏃ユ湡">{{ deviceInfo.installDate }}</a-descriptions-item> + <a-descriptions-item label="浣跨敤骞撮檺">{{ deviceInfo.serviceLife }}骞�</a-descriptions-item> + <a-descriptions-item label="褰撳墠鐘舵��"> + <a-tag :color="deviceInfo.statusColor">{{ deviceInfo.status }}</a-tag> + </a-descriptions-item> + </a-descriptions> + </a-card> + </a-col> + + <!-- 璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿 --> + <a-col :span="16"> + <a-card title="璁惧鍋ュ悍鐘舵�佷笌缁存姢寤鸿" class="mb-4" :style="{ height: '440px' }"> + <a-row :gutter="16"> + <a-col :span="8"> + <a-statistic + title="鏁翠綋鍋ュ悍搴�" + :value="healthData.overallHealth" + :precision="0" + suffix="%" + :value-style="{ color: healthData.healthColor }" + /> + </a-col> + <a-col :span="8"> + <a-statistic + title="棰勬祴鍓╀綑瀵垮懡" + :value="healthData.predictedLife" + suffix="澶�" + /> + </a-col> + <a-col :span="8"> + <a-statistic + title="鏁呴殰椋庨櫓绛夌骇" + :value="healthData.riskLevel" + :value-style="{ color: healthData.riskColor }" + /> + </a-col> + </a-row> + <div class="mt-4"> + <a-progress + :percent="healthData.overallHealth" + :stroke-color="healthData.healthColor" + :show-info="false" + /> + </div> + <a-divider orientation="left">棰勬祴鎬х淮鎶ゅ缓璁�</a-divider> + <a-table + :columns="maintenanceColumns" + :data-source="maintenanceData" + :pagination="false" + size="small" + > + <template #urgency="{ text }"> + <a-tag :color="getUrgencyColor(text)">{{ text }}</a-tag> + </template> + <template #action="{ record }"> + <a-button type="link" size="small" @click="handleMaintenance(record)">澶勭悊</a-button> + </template> + </a-table> + </a-card> + </a-col> + </a-row> + + <!-- 瀹炴椂鏁版嵁瓒嬪娍鍥� --> + <a-card title="瀹炴椂鏁版嵁瓒嬪娍鍥�" class="mb-4"> + <!-- 杩愬姩绯荤粺 --> + <a-row :gutter="16"> + <a-col :span="8"> + <div id="xAxisMotorCurrentChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="yAxisMotorCurrentChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="zAxisMotorCurrentChart" style="height: 300px;"></div> + </a-col> + </a-row> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="motorTemperatureChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="motorVibrationChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="beltTensionChart" style="height: 300px;"></div> + </a-col> + </a-row> + + <!-- 璐磋澶�/鍚稿槾 --> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="nozzleVacuumChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="nozzleFlowChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="placementSpeedChart" style="height: 300px;"></div> + </a-col> + </a-row> + + <!-- 瑙嗚绯荤粺 --> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="placementAccuracyChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="visionAlignmentChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="lightIntensityChart" style="height: 300px;"></div> + </a-col> + </a-row> + + <!-- 鐜鍙傛暟 --> + <a-row :gutter="16" class="mt-4"> + <a-col :span="8"> + <div id="feederMotorCurrentChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="esdChart" style="height: 300px;"></div> + </a-col> + <a-col :span="8"> + <div id="ambientTemperatureHumidityChart" style="height: 300px;"></div> + </a-col> + </a-row> + </a-card> + <a-row :gutter="16"> + <!-- 澶囦欢瀵垮懡棰勬祴 --> + <a-col :span="12"> + <a-card title="澶囦欢瀵垮懡棰勬祴" class="mb-4"> + <a-table + :columns="sparePartColumns" + :data-source="sparePartData" + :pagination="false" + size="small" + > + <template #status="{ text }"> + <a-tag :color="getStatusColor(text)">{{ text }}</a-tag> + </template> + </a-table> + </a-card> + </a-col> + + <!-- 鍘嗗彶缁存姢璁板綍 --> + <a-col :span="12"> + <a-card title="鍘嗗彶缁存姢璁板綍" class="mb-4"> + <a-timeline> + <a-timeline-item v-for="item in historyData" :key="item.id" :color="item.color"> + {{ item.date }} - {{ item.type }}: {{ item.description }} + </a-timeline-item> + </a-timeline> + </a-card> + </a-col> + </a-row> + + </div> +</template> + +<script lang="ts"> +import { defineComponent, reactive, onMounted, onUnmounted } from 'vue'; +import * as echarts from 'echarts'; + +export default defineComponent({ + name: 'SmtMachineDetail', + setup() { + // 妯℃嫙鏁版嵁 + const deviceInfo = reactive({ + deviceName: 'SMT璐寸墖鏈�-001', + deviceType: '璐寸墖鏈�', + deviceId: 'SMT-2024-001', + installDate: '2024-01-01', + serviceLife: 8, + status: '杩愯涓�', + statusColor: '#52c41a' + }); + + const healthData = reactive({ + overallHealth: 92, + healthColor: '#52c41a', + predictedLife: 2500, + riskLevel: '浣庨闄�', + riskColor: '#52c41a' + }); + + const maintenanceColumns = [ + { + title: '缁存姢绫诲瀷', + dataIndex: 'type', + key: 'type' + }, + { + title: '缁存姢鍐呭', + dataIndex: 'content', + key: 'content' + }, + { + title: '寤鸿鏃堕棿', + dataIndex: 'suggestedTime', + key: 'suggestedTime' + }, + { + title: '绱ф�ョ▼搴�', + dataIndex: 'urgency', + key: 'urgency', + slots: { customRender: 'urgency' } + }, + { + title: '鎿嶄綔', + key: 'action', + slots: { customRender: 'action' } + } + ]; + + const maintenanceData = reactive([ + { + key: '1', + type: '杩愬姩绯荤粺淇濆吇', + content: 'X/Y杞翠己鏈嶇數鏈烘鼎婊戞鏌�', + suggestedTime: '2024-03-20', + urgency: '涓瓑' + }, + { + key: '2', + type: '璐磋澶寸淮鎶�', + content: '鍚稿槾鐪熺┖鍘嬪姏鏍″噯', + suggestedTime: '2024-04-05', + urgency: '楂�' + }, + { + key: '3', + type: '渚涙枡绯荤粺妫�鏌�', + content: '椋炶揪鍗″甫/鍗℃枡娆℃暟娓呴浂', + suggestedTime: '2024-03-10', + urgency: '浣�' + } + ]); + + const getUrgencyColor = (urgency: string) => { + switch (urgency) { + case '楂�': + return 'red'; + case '涓瓑': + return 'orange'; + case '浣�': + return 'green'; + default: + return 'default'; + } + }; + + const sparePartColumns = [ + { + title: '澶囦欢鍚嶇О', + dataIndex: 'name', + key: 'name' + }, + { + title: '褰撳墠瀵垮懡', + dataIndex: 'currentLife', + key: 'currentLife', + customRender: ({ text }: { text: number }) => `${text}灏忔椂` + }, + { + title: '棰勬祴鍓╀綑瀵垮懡', + dataIndex: 'remainingLife', + key: 'remainingLife', + customRender: ({ text }: { text: number }) => `${text}灏忔椂` + }, + { + title: '鐘舵��', + dataIndex: 'status', + key: 'status', + slots: { customRender: 'status' } + } + ]; + + const sparePartData = reactive([ + { + key: '1', + name: 'X杞翠己鏈嶇數鏈�', + currentLife: 5000, + remainingLife: 1500, + status: '鑹ソ' + }, + { + key: '2', + name: '鍚稿槾', + currentLife: 1000, + remainingLife: 50, + status: '棰勮' + }, + { + key: '3', + name: '椋炶揪', + currentLife: 8000, + remainingLife: 2000, + status: '鑹ソ' + } + ]); + + const historyData = reactive([ + { + id: '1', + date: '2024-02-15', + type: '瀹氭湡淇濆吇', + description: '瀹屾垚瀛e害淇濆吇锛屾鏌ヨ繍鍔ㄧ郴缁熸鼎婊�', + color: 'green' + }, + { + id: '2', + date: '2024-01-20', + type: '鏁呴殰缁翠慨', + description: '淇鍚稿槾鍫靛闂', + color: 'red' + }, + { + id: '3', + date: '2023-12-01', + type: '瀹氭湡淇濆吇', + description: '瀹屾垚骞村害淇濆吇锛屾牎鍑嗚瑙夌郴缁�', + color: 'green' + } + ]); + + const getStatusColor = (status: string) => { + switch (status) { + case '鑹ソ': + return '#52c41a'; + case '棰勮': + return '#faad14'; + case '鍗遍櫓': + return '#f5222d'; + default: + return '#d9d9d9'; + } + }; + + const handleMaintenance = (record: any) => { + console.log('澶勭悊缁存姢寤鸿:', record); + // 瀹為檯椤圭洰涓繖閲屼細璋冪敤API澶勭悊缁存姢寤鸿 + }; + + return { + deviceInfo, + healthData, + maintenanceColumns, + maintenanceData, + sparePartColumns, + sparePartData, + historyData, + getStatusColor, + getUrgencyColor, + handleMaintenance + }; + }, + + mounted() { + // 鍒濆鍖栧浘琛� + const initChart = (chartId: string, title: string, chartData: any[], unit: string, baseValue: number, fluctuation: number) => { + const chart = echarts.init(document.getElementById(chartId)); + + const updateChart = () => { + // 鐢熸垚鏂扮殑鏁版嵁鐐� + const now = new Date(); + const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; + const newValue = (baseValue + (Math.random() * 2 - 1) * fluctuation).toFixed(2); + + // 娣诲姞鏂版暟鎹偣锛屾渶澶氫繚鐣�60涓偣锛�5鍒嗛挓鏁版嵁锛� + chartData.push({ + time, + value: newValue + }); + if (chartData.length > 60) { + chartData.shift(); + } + + const option = { + title: { + text: title, + left: 'center' + }, + tooltip: { + trigger: 'axis', + formatter: (params) => { + return `${params[0].axisValueLabel}<br/>${params[0].marker} ${params[0].seriesName}: ${params[0].data}${unit}`; + } + }, + grid: { + left: '8%', // 澧炲姞宸︿晶杈硅窛 + right: '5%', + bottom: '10%', + containLabel: true + }, + xAxis: { + type: 'category', + data: chartData.map(item => item.time) + }, + yAxis: { + type: 'value', + axisLabel: { + formatter: (value) => `${value}${unit}` + } + }, + series: [ + { + data: chartData.map(item => parseFloat(item.value)), + type: 'line', + smooth: true, + lineStyle: { + width: 2, + color: chartId === 'motorCurrentChart' ? '#5470C6' : chartId === 'motorTemperatureChart' ? '#91CC75' : '#FAC858' // 鏍规嵁鍥捐〃ID璁剧疆棰滆壊 + }, + areaStyle: { + opacity: 0.8, + color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { + offset: 0, + color: chartId === 'motorCurrentChart' ? 'rgba(84,112,198,0.3)' : chartId === 'motorTemperatureChart' ? 'rgba(145,204,117,0.3)' : 'rgba(250,200,88,0.3)' + }, + { + offset: 1, + color: chartId === 'motorCurrentChart' ? 'rgba(84,112,198,0)' : chartId === 'motorTemperatureChart' ? 'rgba(145,204,117,0)' : 'rgba(250,200,88,0)' + } + ]) + }, + } + ] + }; + chart.setOption(option); + }; + + // 鐢熸垚鍒濆鏁版嵁鐐癸紙60涓偣锛�5鍒嗛挓鏁版嵁锛� + for (let i = 0; i < 60; i++) { + const now = new Date(Date.now() - (60 - i) * 5000); // 鐢熸垚杩囧幓5鍒嗛挓鐨勬暟鎹� + const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`; + const newValue = (baseValue + (Math.random() * 2 - 1) * fluctuation).toFixed(2); + chartData.push({ + time, + value: newValue + }); + } + + // 鍒濆娓叉煋 + updateChart(); + + // 姣�5绉掓洿鏂颁竴娆℃暟鎹� + const intervalId = setInterval(updateChart, 5000); + + window.addEventListener('resize', () => { + chart.resize(); + }); + onUnmounted(() => { + clearInterval(intervalId); + }); + + }; + + // 杩愬姩绯荤粺鐩戞祴 + initChart('xAxisMotorCurrentChart', 'X杞寸數鏈虹數娴�', [], 'A', 10, 1); + initChart('yAxisMotorCurrentChart', 'Y杞寸數鏈虹數娴�', [], 'A', 10, 1); + initChart('zAxisMotorCurrentChart', 'Z杞寸數鏈虹數娴�', [], 'A', 8, 0.8); + initChart('motorTemperatureChart', '鐢垫満娓╁害', [], '掳C', 45, 2); + initChart('motorVibrationChart', '鐢垫満鎸姩', [], 'mm/s', 5, 0.5); + initChart('beltTensionChart', '鐨甫寮犲姏', [], 'N', 50, 5); + + // 璐磋澶�/鍚稿槾鐩戞祴 + initChart('nozzleVacuumChart', '鍚稿槾鐪熺┖鍘嬪姏', [], 'kPa', 80, 5); + initChart('nozzleFlowChart', '鍚稿槾娴侀噺', [], 'L/min', 5, 0.5); + initChart('placementSpeedChart', '璐磋閫熷害', [], 'mm/s', 100, 10); + initChart('placementAccuracyChart', '璐磋绮惧害', [], '渭m', 50, 5); + + // 瑙嗚绯荤粺鐩戞祴 + initChart('visionAlignmentChart', '瑙嗚瀵逛綅绮惧害', [], '渭m', 30, 3); + initChart('lightIntensityChart', '鍏夋簮浜害', [], 'lux', 5000, 500); + + // 渚涙枡绯荤粺鐩戞祴 + initChart('feederMotorCurrentChart', '渚涙枡鐢垫満鐢垫祦', [], 'A', 5, 0.5); + + // 鐜鐩戞祴 + initChart('esdChart', '闈欑數鐩戞祴', [], 'kV', 0.1, 0.05); + initChart('ambientTemperatureHumidityChart', '鐜娓╂箍搴�', [], '掳C', 25, 2); + } + +}); +</script> + +<style scoped> +.device-detail-container { + padding: 16px; + background: #f0f2f5; +} +</style> \ No newline at end of file -- Gitblit v1.9.3