<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号AOI检测仪维护',
|
content: '相机镜头有灰尘,影响检测精度,需要清洁镜头',
|
suggestedTime: generateRelativeTime(2),
|
urgency: '低'
|
},
|
{
|
key: '4',
|
type: '4号锡膏印刷机维护',
|
content: '刮刀压力不均匀,需要调整或更换刮刀',
|
suggestedTime: generateRelativeTime(3),
|
urgency: '中等'
|
},
|
{
|
key: '5',
|
type: '5号SPI检测仪维护',
|
content: '激光测量模块需要重新校准',
|
suggestedTime: generateRelativeTime(5),
|
urgency: '中等'
|
},
|
{
|
key: '6',
|
type: '6号X-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号贴装系统T轴伺服电机',
|
currentLife: '15000小时',
|
remainingLife: '1451小时',
|
status: '预警'
|
},
|
{
|
key: '5',
|
name: '1号贴装系统Z轴伺服电机',
|
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号贴装系统T轴伺服电机',
|
currentLife: '15000小时',
|
remainingLife: '7540小时',
|
status: '良好'
|
},
|
{
|
key: '7',
|
name: '2号贴装系统Z轴伺服电机',
|
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 (正常范围: 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); // 可以根据实际情况设置为true或false
|
|
// 初始化各个图表
|
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;
|
}
|
*/
|
/* 在style部分添加以下样式 */
|
.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>
|