兰宝车间质量管理系统-前端
zhuguifei
23 小时以前 12c98fc512070fc52d5956be1da9ce099b40f350
添加预测性维护两个页面
已添加3个文件
1371 ■■■■■ 文件已修改
src/assets/images/JUKI.png 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qms/ai/detail.vue 856 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/qms/ai/index.vue 515 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/images/JUKI.png
src/views/qms/ai/detail.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,856 @@
<template>
  <div class="device-detail-container">
    <el-page-header @back="goBack">
      <template #content>
        <span class="text-large font-600 mr-3"> SMT贴片机预测性维护详情</span>
      </template>
    </el-page-header>
    <el-row :gutter="16" class="mt-4">
      <!-- è®¾å¤‡å›¾ç‰‡å’Œè®¾å¤‡åŸºæœ¬ä¿¡æ¯åˆå¹¶ -->
      <el-col :span="10">
        <el-card class="mb-4" :style="{ height: '440px' }">
          <template #header>
            <div class="card-header">
              <span>设备信息</span>
            </div>
          </template>
          <el-row :gutter="16">
            <el-col :span="12">
              <div class="device-image-container">
                <img :src="deviceInfo.imageUrl" alt="设备图片" class="device-image" />
              </div>
            </el-col>
            <el-col :span="12">
              <el-descriptions border :column="1"  class="custom-descriptions">
                <el-descriptions-item label="设备名称">{{ deviceInfo.deviceName }}</el-descriptions-item>
                <el-descriptions-item label="设备类型">{{ deviceInfo.deviceType }}</el-descriptions-item>
                <el-descriptions-item label="设备编号">{{ deviceInfo.deviceId }}</el-descriptions-item>
                <el-descriptions-item label="安装日期">{{ deviceInfo.installDate }}</el-descriptions-item>
                <el-descriptions-item label="使用年限">{{ deviceInfo.serviceLife }}å¹´</el-descriptions-item>
                <el-descriptions-item label="当前状态">
                  <el-tag :type="getStatusTagType(deviceInfo.status)">{{ deviceInfo.status }}</el-tag>
                </el-descriptions-item>
              </el-descriptions>
            </el-col>
          </el-row>
        </el-card>
      </el-col>
      <!-- è®¾å¤‡å¥åº·çŠ¶æ€ä¸Žç»´æŠ¤å»ºè®® -->
      <el-col :span="14">
        <el-card class="mb-4" :style="{ height: '440px' }">
          <template #header>
            <div class="card-header">
              <span>设备健康状态与维护建议</span>
            </div>
          </template>
          <el-row :gutter="16">
            <el-col :span="8">
              <el-statistic
                title="整体健康度"
                :value="healthData.overallHealth"
                :precision="0"
                :suffix="'%'"
                :value-style="{ color: healthData.healthColor }"
              />
            </el-col>
            <el-col :span="8">
              <el-statistic
                title="预测剩余寿命"
                :value="healthData.predictedLife"
                :suffix="'天'"
              />
            </el-col>
            <el-col :span="8">
              <el-statistic
                title="故障风险等级"
                :value="healthData.riskLevel"
                :value-style="{ color: healthData.riskColor }"
              />
            </el-col>
          </el-row>
          <div class="mt-4">
            <el-progress
              :percentage="healthData.overallHealth"
              :color="healthData.healthColor"
              :show-text="false"
            />
          </div>
          <el-divider content-position="left">预测性维护建议</el-divider>
          <div class="table-container">
          <el-table
            ref="maintenanceTable"
            :data="displayMaintenanceData"
            height="246"
            size="large"
            stripe
          >
            <el-table-column prop="type" label="维护类型" />
            <el-table-column prop="content" label="维护内容" />
            <el-table-column prop="suggestedTime" label="建议时间" />
            <el-table-column prop="urgency" label="紧急程度">
              <template #default="{ row }">
                <el-tag :type="getUrgencyTagType(row.urgency)">{{ row.urgency }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="80">
              <template #default="{ row }">
                <el-button link type="primary" size="small" @click="handleMaintenance(row)">处理</el-button>
              </template>
            </el-table-column>
          </el-table>
          </div>
        </el-card>
      </el-col>
      <el-col :span="24">
        <el-card class="mb-4">
          <template #header>
            <div class="card-header">
              <span>设备数据</span>
            </div>
          </template>
          <el-row :gutter="16">
            <el-col :span="4">
              <el-statistic title="X轴总移动距离" :value="healthData.xAxisTravel" :precision="2" :suffix="'km'">
                <template #prefix>
                  <el-icon  style="vertical-align: -0.125em"><TrendCharts /></el-icon>
                </template>
              </el-statistic>
            </el-col>
            <el-col :span="4">
              <el-statistic title="Y轴总移动距离" :value="healthData.yAxisTravel" :precision="2" :suffix="'km'">
                <template #prefix>
                  <el-icon  style="vertical-align: -0.125em"><TrendCharts /></el-icon>
                </template>
              </el-statistic>
            </el-col>
            <el-col :span="4">
              <el-statistic title="卡带次数" :value="healthData.tapeJamCount" :suffix="'次'">
                <template #prefix>
                  <el-icon  style="vertical-align: -0.125em"><Warning /></el-icon>
                </template>
              </el-statistic>
            </el-col>
            <el-col :span="4">
              <el-statistic title="卡料次数" :value="healthData.materialJamCount" :suffix="'次'">
                <template #prefix>
                  <el-icon  style="vertical-align: -0.125em"><Warning /></el-icon>
                </template>
              </el-statistic>
            </el-col>
            <el-col :span="4">
              <el-statistic title="拼板数" :value="healthData.panelCount" :suffix="'块'">
                <template #prefix>
                  <el-icon  style="vertical-align: -0.125em"><Grid /></el-icon>
                </template>
              </el-statistic>
            </el-col>
            <el-col :span="4">
              <el-statistic title="出错停机时间" :value="healthData.downtime" :suffix="'秒'">
                <template #prefix>
                  <el-icon  style="vertical-align: -0.125em"><Clock /></el-icon>
                </template>
              </el-statistic>
            </el-col>
          </el-row>
        </el-card>
      </el-col>
    </el-row>
    <!-- å®žæ—¶æ•°æ®è¶‹åŠ¿å›¾ -->
    <el-card class="mb-4">
      <template #header>
        <div class="card-header">
          <span>实时数据趋势图</span>
        </div>
      </template>
      <el-row :gutter="16" class="mt-4">
        <el-col :span="8">
          <div id="ambientTemperatureHumidityChart" style="height: 300px;"></div>
        </el-col>
        <el-col :span="8">
          <div id="motorTemperatureChart" style="height: 300px;"></div>
        </el-col>
        <el-col :span="8">
          <div id="motorVibrationChart" style="height: 300px;"></div>
        </el-col>
      </el-row>
      <!-- è´´è£…头/吸嘴 -->
      <el-row :gutter="16" class="mt-4">
        <el-col :span="8">
          <div id="nozzleVacuumChart" style="height: 300px;"></div>
        </el-col>
        <el-col :span="8">
          <div id="nozzleFlowChart" style="height: 300px;"></div>
        </el-col>
        <el-col :span="8">
          <div id="placementSpeedChart" style="height: 300px;"></div>
        </el-col>
      </el-row>
    </el-card>
    <el-row :gutter="16">
      <!-- éƒ¨ä»¶å¯¿å‘½é¢„测 -->
      <el-col :span="12">
        <el-card class="mb-4" :style="{ height: '560px' }">
          <template #header>
            <div class="card-header">
              <span>部件寿命预测</span>
            </div>
          </template>
          <el-table
            :data="sparePartData"
            size="large"
            stripe
          >
            <el-table-column prop="name" label="部件名称" />
            <el-table-column prop="currentLife" label="理论寿命" />
            <el-table-column prop="remainingLife" label="预测剩余寿命" />
            <el-table-column prop="status" label="状态">
              <template #default="{ row }">
                <el-tag :type="getStatusTagType(row.status)">{{ row.status }}</el-tag>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
      </el-col>
      <!-- åŽ†å²ç»´æŠ¤è®°å½• -->
      <el-col :span="12">
        <el-card class="mb-4" :style="{ height: '560px' }">
          <template #header>
            <div class="card-header">
              <span>历史维护记录</span>
            </div>
          </template>
          <el-timeline>
            <el-timeline-item
              v-for="item in historyData"
              :key="item.id"
              :type="getTimelineItemType(item.color)"
              :timestamp="item.date"
            >
              {{ item.type }}: {{ item.description }}
            </el-timeline-item>
          </el-timeline>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>
<script lang="ts">
import { defineComponent, reactive, onMounted, onUnmounted, ref, nextTick } from 'vue';
import { useRouter } from 'vue-router';
import * as echarts from 'echarts';
import {
  TrendCharts,
  Warning,
  Grid,
  Clock
} from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import img from '@/assets/images/JUKI.png';
export default defineComponent({
  name: 'SmtMachineDetail',
  components: {
    TrendCharts,
    Warning,
    Grid,
    Clock
  },
  setup() {
    const router = useRouter();
    const goBack = () => {
      router.go(-1);
    };
    // æ¨¡æ‹Ÿæ•°æ®
    const deviceInfo = reactive({
      deviceName: 'SMT贴片机',
      deviceType: '贴片机',
      deviceId: 'GPC2012A101',
      installDate: '2012-06-08',
      serviceLife: 15,
      status: '运行中',
      statusColor: '#52c41a',
      imageUrl: img
    });
    const healthData = reactive({
      overallHealth: 82,
      healthColor: '#52c41a',
      predictedLife: 635,
      riskLevel: '低风险',
      riskColor: '#1a7ac4',
      xAxisTravel: 300.179,
      yAxisTravel: 233.39,
      tapeJamCount: 6,
      materialJamCount: 15,
      panelCount: 2480,
      downtime: 4.5,
    });
    const maintenanceData = reactive([
      {
        key: '1',
        type: '1号贴装系统维护',
        content: '吸嘴真空压力偏低',
        suggestedTime: '2025-07-05',
        urgency: '中等'
      },
      {
        key: '6',
        type: '4号贴装系统维护',
        content: 'T轴马达寿命告急',
        suggestedTime: '2025-07-05',
        urgency: '中等'
      },
      {
        key: '2',
        type: '运动系统保养',
        content: 'X/Y轴伺服电机润滑检查',
        suggestedTime: '2025-06-20',
        urgency: '低'
      },
      {
        key: '3',
        type: '供料系统检查',
        content: '飞达卡带/卡料次数清零',
        suggestedTime: '2025-06-10',
        urgency: '低'
      },
      {
        key: '4',
        type: '2号贴装系统维护',
        content: '真空压力不稳定',
        suggestedTime: '2025-07-05',
        urgency: '低'
      },
      {
        key: '5',
        type: '3号贴装系统维护',
        content: 'Z轴马达电流异常',
        suggestedTime: '2025-07-05',
        urgency: '低'
      },
      {
        key: '7',
        type: '5号贴装头维护',
        content: '真空压力不稳定',
        suggestedTime: '2025-07-05',
        urgency: '低'
      },
      {
        key: '8',
        type: '6号贴装头维护',
        content: '真空压力不稳定',
        suggestedTime: '2025-07-05',
        urgency: '低'
      },
    ]);
    const getUrgencyTagType = (urgency: string) => {
      switch (urgency) {
        case '高': return 'danger';
        case '中等': return 'warning';
        case '低': return 'success';
        default: return 'info';
      }
    };
    const getStatusTagType = (status: string) => {
      switch (status) {
        case '运行中': return 'success';
        case '预警': return 'warning';
        case '危险': return 'danger';
        case '良好': return 'success';
        default: return 'info';
      }
    };
    const getTimelineItemType = (color: string) => {
      switch (color) {
        case 'green': return 'success';
        case 'red': return 'danger';
        default: return 'primary';
      }
    };
    // const fetchDeviceData = async () => {
    //   try {
    //     const res = await getDeviceDataSmt();
    //     Object.assign(healthData, res);
    //   } catch (error) {
    //     console.error('获取设备数据失败:', error);
    //   }
    // };
    let deviceDataInterval: number | undefined;
    // fetchDeviceData();
    // deviceDataInterval = setInterval(fetchDeviceData, 3000);
    const sparePartData = reactive([
      {
        key: '1',
        name: '1号贴装系统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-07-22',
        type: '定期保养',
        description: '完成月度保养,丝杆导轨注油,抛料清理',
        color: 'green'
      },
      {
        id: '1',
        date: '2025-06-18',
        type: '定期保养',
        description: '完成月度保养,易损件更换',
        color: 'green'
      },
      {
        id: '1',
        date: '2025-05-15',
        type: '定期保养',
        description: '完成季度保养,检查运动系统润滑',
        color: 'green'
      },
      {
        id: '1',
        date: '2025-04-20',
        type: '定期保养',
        description: '完成月度保养,防尘过滤网清理,导轨注油',
        color: 'green'
      },
      {
        id: '1',
        date: '2025-03-16',
        type: '定期保养',
        description: '完成月度保养,真空值校准',
        color: 'green'
      },
      {
        id: '1',
        date: '2025-02-13',
        type: '定期保养',
        description: '完成月度保养,吸嘴检查更换,抛料清理,相机参数校准',
        color: 'green'
      },
      {
        id: '2',
        date: '2025-01-20',
        type: '故障维修',
        description: '修复吸嘴堵塞问题',
        color: 'red'
      },
      {
        id: '3',
        date: '2025-01-15',
        type: '定期保养',
        description: '完成年度保养,贴装系统校准,易损件更换,校准视觉系统',
        color: 'green'
      }
    ]);
    const handleMaintenance = (record: any) => {
      console.log('处理维护建议:', record);
      ElMessage.info(`处理维护建议: ${record.type}`);
    };
    const maintenanceTable = ref();
    // ç”¨äºŽæ˜¾ç¤ºçš„维护数据(支持无限滚动)
    const displayMaintenanceData = ref([...maintenanceData]);
    let scrollInterval: ReturnType<typeof setInterval> | undefined;
    // å¯åŠ¨è‡ªåŠ¨æ»šåŠ¨
    const startAutoScroll = () => {
      scrollInterval = setInterval(() => {
        if (displayMaintenanceData.value.length > 0) {
          // å°†ç¬¬ä¸€é¡¹ç§»åˆ°æœ€åŽ
          const firstItem = displayMaintenanceData.value.shift();
          if (firstItem) {
            displayMaintenanceData.value.push(firstItem);
          }
        }
      }, 1000); // æ¯3秒滚动一次
    };
    onMounted(() => {
      nextTick(() => {
        initCharts();
        startAutoScroll(); // å¯åŠ¨è‡ªåŠ¨æ»šåŠ¨
      });
    });
    onUnmounted(() => {
      if (deviceDataInterval) {
        clearInterval(deviceDataInterval);
      }
      if (scrollInterval) {
        clearInterval(scrollInterval);
      }
    });
    const initCharts = () => {
      // å›¾è¡¨åˆå§‹åŒ–代码(与之前相同)
      // åˆå§‹åŒ–图表
      const initChart = (chartId: string, title: string, seriesConfig: Array<{ name: string, data: any[], unit: string, baseValue: number, fluctuation: number, color: string }>) => {
        const chart = echarts.init(document.getElementById(chartId));
        const updateChart = () => {
          // ç”Ÿæˆæ–°çš„æ•°æ®ç‚¹
          const now = new Date();
          const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`;
          seriesConfig.forEach(s => {
            const newValue = (s.baseValue + (Math.random() * 2 - 1) * s.fluctuation).toFixed(2);
            s.data.push({
              time,
              value: newValue
            });
            if (s.data.length > 24) {
              s.data.shift();
            }
          });
          const option = {
            title: {
              text: title,
              left: 'center'
            },
            tooltip: {
              trigger: 'axis',
              formatter: (params) => {
                let result = `${params[0].axisValueLabel}<br/>`;
                params.forEach(param => {
                  result += `${param.marker} ${param.seriesName}: ${param.data}${seriesConfig[param.seriesIndex].unit}<br/>`;
                });
                return result;
              }
            },
            grid: {
              left: '8%', // å¢žåŠ å·¦ä¾§è¾¹è·
              right: '5%',
              bottom: '10%',
              containLabel: true
            },
            xAxis: {
              type: 'category',
              data: seriesConfig[0].data.map(item => item.time)
            },
            yAxis: seriesConfig.map((s, index) => ({
              type: 'value',
              name: s.name,
              position: index === 0 ? 'left' : 'right',
              axisLine: {
                show: true,
              },
              axisLabel: {
                formatter: (value) => `${value}${s.unit}`
              }
            })),
            series: seriesConfig.map((s, index) => ({
                name: s.name,
                data: s.data.map(item => parseFloat(item.value)),
                type: 'line',
                smooth: true,
                yAxisIndex: index,
                lineStyle: {
                  width: 2,
                  color: s.color
                },
                areaStyle: {
                  opacity: 0.8,
                  color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                    {
                      offset: 0,
                      color: 'rgba(84,112,198,0.3)'
                    },
                    {
                      offset: 1,
                      color: 'rgba(84,112,198,0)'
                    }
                  ])
                },
              }
            )),
          };
          chart.setOption(option);
        };
        seriesConfig.forEach(s => {
          s.data = [];
          // ç”Ÿæˆåˆå§‹æ•°æ®ç‚¹ï¼ˆ60个点,5分钟数据)
          for (let i = 0; i < 24; i++) {
            const now = new Date(Date.now() - (24 - i) * 5000); // ç”Ÿæˆè¿‡åŽ»5分钟的数据
            const time = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`;
            const newValue = (s.baseValue + (Math.random() * 2 - 1) * s.fluctuation).toFixed(2);
            s.data.push({
              time,
              value: newValue
            });
          }
        })
        // åˆå§‹æ¸²æŸ“
        updateChart();
        // æ¯5秒更新一次数据
        const intervalId = setInterval(updateChart, 5000);
        window.addEventListener('resize', () => {
          chart.resize();
        });
      };
      // åˆå§‹åŒ–各个图表
      initChart('motorTemperatureChart', '产量', [
        { name: '产量', data: [], unit: 'pcs/h', baseValue: 175, fluctuation: 25, color: '#5470C6' },
      ]);
      initChart('motorVibrationChart', '抛料率', [
        { name: '抛料率', data: [], unit: '%', baseValue: 0.15, fluctuation: 0.01, color: '#5470C6' },
      ]);
      initChart('nozzleVacuumChart', '吸嘴真空压力', [
        { name: '压力', data: [], unit: 'kPa', baseValue: -45, fluctuation: 5, color: '#5470C6' },
      ]);
      initChart('nozzleFlowChart', '吸嘴吹气压力', [
        { name: '压力', data: [], unit: 'kPa', baseValue: 20, fluctuation: 5, color: '#5470C6' },
      ]);
      initChart('placementSpeedChart', '贴装速度', [
        { name: '速度', data: [], unit: 'chips/h', baseValue: 9000, fluctuation: 1500, color: '#5470C6' },
      ]);
      const ambientTemperatureData: any[] = [];
      const ambientHumidityData: any[] = [];
      initChart('ambientTemperatureHumidityChart', '环境温湿度', [
        { name: '温度', data: ambientTemperatureData, unit: '°C', baseValue: 25, fluctuation: 1, color: '#5470C6' },
        { name: '湿度', data: ambientHumidityData, unit: '%', baseValue: 60, fluctuation: 5, color: '#91cc75' }
      ]);
    };
    return {
      deviceInfo,
      healthData,
      maintenanceData,
      displayMaintenanceData,
      sparePartData,
      historyData,
      goBack,
      getUrgencyTagType,
      getStatusTagType,
      getTimelineItemType,
      handleMaintenance,
      maintenanceTable,
    };
  }
});
</script>
<style scoped>
.device-detail-container {
  padding: 16px;
  background: #f0f2f5;
}
.page-header-content {
  display: flex;
  flex-direction: column;
}
.page-header-title {
  font-size: 18px;
  font-weight: bold;
}
.page-header-subtitle {
  font-size: 14px;
  color: #666;
  margin-top: 4px;
}
.card-header {
  font-weight: bold;
}
.device-image-container {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 330px;
  overflow: hidden;
}
.device-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}
.mt-4 {
  margin-top: 1rem;
}
.mb-4 {
  margin-bottom: 1rem;
}
.custom-descriptions {
  height: 357px; /* è®¾ç½®æ•´ä¸ªæè¿°åˆ—表的高度 */
}
.custom-descriptions :deep(.el-descriptions__body) {
  height: 100%;
}
.custom-descriptions :deep(.el-descriptions__table) {
  height: 100%;
}
.custom-descriptions :deep(.el-descriptions__table tbody) {
  height: 100%;
  display: flex;
  flex-direction: column;
}
.custom-descriptions :deep(.el-descriptions__table tr) {
  flex: 1;
  display: flex;
}
.custom-descriptions :deep(.el-descriptions__table td) {
  flex: 1;
  display: flex;
  align-items: center;
}
.custom-descriptions :deep(.el-descriptions__table .el-descriptions__label) {
  display: flex;
  align-items: center;
  justify-content: flex-start;
}
.custom-descriptions :deep(.el-descriptions__table .el-descriptions__content) {
  display: flex;
  align-items: center;
  justify-content: flex-start;
}
/*
.table-container {
  position: relative;
  height: 246px;
  overflow: hidden;
}
.table-container :deep(.el-table__body-wrapper) {
  overflow: hidden;
}
.table-container :deep(.el-table__body) {
  animation: scrollTable 20s linear infinite;
}
.table-container:hover :deep(.el-table__body) {
  animation-play-state: paused;
}
@keyframes scrollTable {
  0% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(-100%);
  }
}
.table-container :deep(.el-table__header-wrapper) {
  position: sticky;
  top: 0;
  background: white;
  z-index: 10;
}
*/
</style>
src/views/qms/ai/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,515 @@
<template>
  <div class="app-container">
    <div class="p-5">
      <!-- è®¾å¤‡å¥åº·çŠ¶æ€å¯è§†åŒ– -->
      <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
        <el-card shadow="hover" class="h-full">
          <template #header>
            <div class="card-header">
              <span>设备健康度评分</span>
            </div>
          </template>
          <!-- è¿™é‡Œå°†æ”¾ç½®å¥åº·åº¦åˆ†å¸ƒå›¾è¡¨ -->
          <div ref="chartRef" class="h-400 w-full"></div>
        </el-card>
        <el-card shadow="hover" class="h-full">
          <template #header>
            <div class="card-header">
              <span>关键指标</span>
            </div>
          </template>
          <div class="grid grid-cols-2 gap-2">
            <div class="p-2 bg-blue-50 rounded">
              <p class="text-sm text-gray-600">健康设备</p>
              <p class="text-xl 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>
        </el-card>
      </div>
      <!-- æ•…障预测与预警 -->
      <el-card shadow="hover" class="mb-6">
        <template #header>
          <div class="card-header">
            <span>故障预测与预警</span>
          </div>
        </template>
        <div class="grid grid-cols-1 gap-4">
          <!-- è¿™é‡Œå°†æ”¾ç½®é¢„警表格 -->
          <el-table
            :data="warningData"
            style="width: 100%"
            :border="true"
            stripe
          >
            <el-table-column prop="name" label="设备名称" />
            <el-table-column prop="component" label="关键部件" />
            <el-table-column prop="indicator" label="风险指标" />
            <el-table-column prop="value" label="当前值" />
            <el-table-column prop="threshold" label="阈值" />
            <el-table-column prop="status" label="状态">
              <template #default="{ row }">
                <el-tag :type="getStatusTagType(row.status)">
                  {{ row.status }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="maintenanceSuggestion" label="维护建议" />
            <el-table-column label="操作" width="180">
              <template #default="{ row }">
                <el-button link type="primary" @click="handleDetail(row)">详情</el-button>
                <el-button
                  link
                  type="primary"
                  @click="generateWorkOrder(row)"
                  :disabled="row.maintenanceSuggestion === '暂无建议'"
                >
                  ç”Ÿæˆå·¥å•
                </el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-card>
      <!-- å¤‡ä»¶ä¿¡æ¯ -->
      <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
        <!-- è®¾å¤‡éƒ¨ä»¶å¯¿å‘½é¢„测 -->
        <el-card shadow="hover" class="h-500">
          <template #header>
            <div class="card-header">
              <span>设备部件寿命预测</span>
            </div>
          </template>
          <el-table
            :data="lifePredictionData"
            style="width: 100%"
            :border="true"
            stripe
            height="410"
          >
            <el-table-column prop="deviceName" label="设备名称" />
            <el-table-column prop="name" label="部件名称" />
            <el-table-column prop="predictedLife" label="预测寿命 (天)" />
            <el-table-column prop="remainingDays" label="剩余寿命 (天)" />
            <el-table-column prop="lifeStatus" label="状态">
              <template #default="{ row }">
                <el-tag :type="getLifeStatusTagType(row.remainingDays)">
                  {{ getLifeStatus(row.remainingDays) }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="80">
              <template #default="{ row }">
                <el-button link type="primary" @click="showLifePredictionDetail(row)">详情</el-button>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
        <!-- å¤‡ä»¶åº“存与预警 -->
        <el-card shadow="hover" class="h-500">
          <template #header>
            <div class="card-header">
              <span>备件库存与预警</span>
            </div>
          </template>
          <el-table
            :data="sparePartData"
            style="width: 100%"
            :border="true"
            stripe
            height="410"
          >
            <el-table-column prop="name" label="备件名称" />
            <el-table-column prop="currentStock" label="当前库存" />
            <el-table-column prop="safetyStock" label="安全库存" />
            <el-table-column prop="predictedDemand" label="预测需求" />
            <el-table-column prop="stockStatus" label="库存状态">
              <template #default="{ row }">
                <el-tag :type="getStockStatusTagType(row.currentStock, row.safetyStock)">
                  {{ getStockStatus(row.currentStock, row.safetyStock) }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="80">
              <template #default="{ row }">
                <el-button link type="primary" @click="showSparePartDetail(row)">详情</el-button>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
      </div>
    </div>
  </div>
</template>
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import * as echarts from 'echarts';
const chartRef = ref<HTMLElement | null>(null);
const router = useRouter();
const healthData = {
  healthy: 85,
  warning: 12,
  critical: 3,
  maintained: 92,
  pendingWorkOrders: 5,
  sparePartWarnings: 2,
};
// æ¨¡æ‹Ÿé«˜é£Žé™©è®¾å¤‡æ•°æ®
const warningData = ref([
  {
    key: '1',
    name: 'SMT贴片机',
    type: 'SMT机器',
    component: 'å·¦3-Head',
    indicator: '真空压力',
    value: 32,
    threshold: 35,
    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: 28,
    threshold: 30,
    status: '低风险',
    maintenanceSuggestion: '建议检查液压系统密封件',
    maintenanceType: '检查维护',
  },
]);
// æ¨¡æ‹Ÿå¤‡ä»¶æ•°æ®
const sparePartData = ref([
  {
    key: '1',
    name: '传送带轴承',
    currentStock: 10,
    safetyStock: 15,
    predictedDemand: 8,
  },
  {
    key: '2',
    name: '主轴润滑油',
    currentStock: 2,
    safetyStock: 3,
    predictedDemand: 1,
  },
  {
    key: '3',
    name: '液压系统密封件',
    currentStock: 20,
    safetyStock: 25,
    predictedDemand: 5,
  },
  {
    key: '4',
    name: '电机碳刷',
    currentStock: 8,
    safetyStock: 10,
    predictedDemand: 2,
  },
  {
    key: '5',
    name: '喷码机油墨',
    currentStock: 1,
    safetyStock: 1,
    predictedDemand: 1,
  },
  {
    key: '6',
    name: '钢网清洗液',
    currentStock: 2,
    safetyStock: 1,
    predictedDemand: 1,
  },
  {
    key: '7',
    name: '十寸精密滤芯',
    currentStock: 20,
    safetyStock: 4,
    predictedDemand: 0,
  },
  {
    key: '8',
    name: '十寸精密滤芯',
    currentStock: 20,
    safetyStock: 4,
    predictedDemand: 0,
  },
  {
    key: '9',
    name: '十寸精密滤芯',
    currentStock: 20,
    safetyStock: 4,
    predictedDemand: 0,
  },
  {
    key: '10',
    name: '十寸精密滤芯',
    currentStock: 20,
    safetyStock: 4,
    predictedDemand: 0,
  },
]);
const lifePredictionData = ref([
  { key: '1', deviceName: 'SMT贴片机', componentName: '传送带', name: '真空发生器', predictedLife: 700, remainingDays: 83 },
  { key: '2', deviceName: '包装机', componentName: '传送带', name: '发热丝', predictedLife: 260, remainingDays: 61 },
  { key: '3', deviceName: '激光打标机', componentName: '传送带', name: '冷却水', predictedLife: 180, remainingDays: 43 },
  { key: '4', deviceName: '钢网清洗机', componentName: '传送带', name: '清洗液', predictedLife: 180, remainingDays: 95 },
  { key: '5', deviceName: '钢网清洗机', componentName: '传送带', name: '滤芯', predictedLife: 180, remainingDays: 95 },
  { key: '6', deviceName: '端子机', componentName: '传送带', name: '刀片', predictedLife: 180, remainingDays: 112 },
  { key: '7', deviceName: '电脑剥线机', componentName: '传送带', name: '刀片', predictedLife: 180, remainingDays: 107 },
  { key: '8', deviceName: 'CNC加工中心', componentName: '主轴', name: '刀筒夹', predictedLife: 1100, remainingDays: 130 },
  { key: '9', deviceName: '空压机', componentName: '安全阀', name: '安全阀', predictedLife: 365, remainingDays: 130 },
  { key: '10', deviceName: '喷码机', componentName: '传送带', name: '墨水', predictedLife: 365, remainingDays: 184 },
  { key: '11', deviceName: '注塑机', componentName: '液压系统', name: '液压油', predictedLife: 1100, remainingDays: 395 },
  { key: '12', deviceName: '焊接机器人', componentName: '焊枪', name: '烙铁芯', predictedLife: 1000, remainingDays: 512 }
]);
const getStatusTagType = (status) => {
  switch (status) {
    case '高风险': return 'danger';
    case '中风险': return 'warning';
    case '低风险': return 'primary';
    default: return 'success';
  }
};
const getLifeStatus = (remainingDays) => {
  if (remainingDays <= 90) {
    return '即将到期';
  } else if (remainingDays <= 150) {
    return '中期预警';
  } else {
    return '寿命充足';
  }
};
const getLifeStatusTagType = (remainingDays) => {
  if (remainingDays <= 90) {
    return 'warning';
  } else if (remainingDays <= 150) {
    return 'primary';
  } else {
    return 'success';
  }
};
const showLifePredictionDetail = (record) => {
  ElMessage.info(`备件名称: ${record.name}, é¢„测寿命: ${record.predictedLife}天, å‰©ä½™å¯¿å‘½: ${record.remainingDays}天`);
};
const getStockStatus = (currentStock, safetyStock) => {
  if (currentStock < safetyStock) {
    return '库存预警';
  } else {
    return '库存充足';
  }
};
const getStockStatusTagType = (currentStock, safetyStock) => {
  if (currentStock < safetyStock) {
    return 'warning';
  } else {
    return 'success';
  }
};
const showSparePartDetail = (record) => {
  ElMessage.info(`备件名称: ${record.name}, å½“前库存: ${record.currentStock}, å®‰å…¨åº“å­˜: ${record.safetyStock}, é¢„测需求: ${record.predictedDemand}`);
};
const generateWorkOrder = (record) => {
  // å·¥å•生成逻辑
  ElMessage.success(`已为 ${record.name} ç”Ÿæˆå·¥å•`);
};
const handleDetail = (record) => {
  router.push({ path: '/ai/detail', query: { deviceId: 15 } });
};
onMounted(() => {
  if (chartRef.value) {
    const myChart = echarts.init(chartRef.value);
    const option = {
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'shadow'
        }
      },
      xAxis: {
        type: 'category',
        data: ['10分', '9分', '8分', '7分', '6分', '5分', '4分', '3分', '2分', '1分'],
      },
      yAxis: {
        type: 'value',
        name: '设备数量',
        minInterval: 1
      },
      series: [
        {
          name: '设备数量',
          type: 'bar',
          barWidth: '60%',
          data: [
            { value: 48, itemStyle: { color: '#52c41a' } },
            { value: 53, itemStyle: { color: '#52c41a' } },
            { value: 37, itemStyle: { color: '#52c41a' } },
            { value: 29, itemStyle: { color: '#52c41a' } },
            { value: 21, itemStyle: { color: '#52c41a' } },
            { value: 8, itemStyle: { color: '#faad14' } },
            { value: 5, itemStyle: { color: '#faad14' } },
          ],
          label: {
            show: true,
            position: 'top',
            formatter: '{c}台'
          }
        }
      ]
    };
    myChart.setOption(option);
    window.addEventListener('resize', () => {
      myChart.resize();
    });
  }
});
</script>
<style scoped>
.app-container {
  padding: 0;
}
.h-500 {
  height: 500px;
}
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
.grid {
  display: grid;
}
.grid-cols-1 {
  grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-cols-2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.gap-4 {
  gap: 1rem;
}
.mb-6 {
  margin-bottom: 1.5rem;
}
.p-5 {
  padding: 1.25rem;
}
.w-full {
  width: 100%;
}
.h-400 {
  height: 400px;
}
.bg-blue-50 {
  background-color: #eff6ff;
}
.bg-yellow-50 {
  background-color: #fffbeb;
}
.bg-red-50 {
  background-color: #fef2f2;
}
.bg-green-50 {
  background-color: #f0fdf4;
}
.rounded {
  border-radius: 0.25rem;
}
.text-sm {
  font-size: 0.875rem;
}
.text-xl {
  font-size: 1.25rem;
}
.font-bold {
  font-weight: 700;
}
.text-gray-600 {
  color: #4b5563;
}
@media (min-width: 768px) {
  .md\:grid-cols-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
</style>