兰宝车间质量管理系统-前端
zhuguifei
11 小时以前 26cd65ace0c8787b5ff6feff3e6270fb371e1a9c
src/views/index.vue
@@ -1,172 +1,905 @@
<template>
  <div class="app-container home">
    <el-row :gutter="20">
      <el-col :sm="24" :lg="12" style="padding-left: 20px">
        <h2>RuoYi-Vue-Plus多租户管理系统</h2>
        <p>
          RuoYi-Vue-Plus 是基于 RuoYi-Vue 针对 分布式集群 场景升级(不兼容原框架)
          <br />
          * 前端开发框架 Vue3、TS、Element Plus<br />
          * 后端开发框架 Spring Boot<br />
          * 容器框架 Undertow 基于 Netty 的高性能容器<br />
          * 权限认证框架 Sa-Token 支持多终端认证系统<br />
          * 关系数据库 MySQL 适配 8.X 最低 5.7<br />
          * 缓存数据库 Redis 适配 6.X 最低 4.X<br />
          * 数据库框架 Mybatis-Plus 快速 CRUD 增加开发效率<br />
          * 数据库框架 p6spy 更强劲的 SQL 分析<br />
          * 多数据源框架 dynamic-datasource 支持主从与多种类数据库异构<br />
          * 序列化框架 Jackson 统一使用 jackson 高效可靠<br />
          * Redis客户端 Redisson 性能强劲、API丰富<br />
          * 分布式限流 Redisson 全局、请求IP、集群ID 多种限流<br />
          * 分布式锁 Lock4j 注解锁、工具锁 多种多样<br />
          * 分布式幂等 Lock4j 基于分布式锁实现<br />
          * 分布式链路追踪 SkyWalking 支持链路追踪、网格分析、度量聚合、可视化<br />
          * 分布式任务调度 PowerJob 高性能 高可靠 易扩展<br />
          * 文件存储 Minio 本地存储<br />
          * 文件存储 七牛、阿里、腾讯 云存储<br />
          * 监控框架 SpringBoot-Admin 全方位服务监控<br />
          * 校验框架 Validation 增强接口安全性 严谨性<br />
          * Excel框架 Alibaba EasyExcel 性能优异 扩展性强<br />
          * 文档框架 SpringDoc、javadoc 无注解零入侵基于java注释<br />
          * 工具类框架 Hutool、Lombok 减少代码冗余 增加安全性<br />
          * 代码生成器 适配MP、SpringDoc规范化代码 一键生成前后端代码<br />
          * 部署方式 Docker 容器编排 一键部署业务集群<br />
          * 国际化 SpringMessage Spring标准国际化方案<br />
        </p>
        <p><b>当前版本:</b> <span>v5.1.0</span></p>
        <p>
          <el-tag type="danger">&yen;免费开源</el-tag>
        </p>
        <p>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Vue-Plus')">访问码云</el-button>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Vue-Plus')">访问GitHub</el-button>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-vue-plus/changlog')"
            >更新日志</el-button
          >
        </p>
      </el-col>
    <div class="p-5">
      <div style="display: flex" class="gap-4">
      <el-col :sm="24" :lg="12" style="padding-left: 20px">
        <h2>RuoYi-Cloud-Plus多租户微服务管理系统</h2>
        <p>
          RuoYi-Cloud-Plus 微服务通用权限管理系统 重写 RuoYi-Cloud 全方位升级(不兼容原框架)
          <br />
          * 前端开发框架 Vue3、TS、Element UI<br />
          * 后端开发框架 Spring Boot<br />
          * 微服务开发框架 Spring Cloud、Spring Cloud Alibaba<br />
          * 容器框架 Undertow 基于 XNIO 的高性能容器<br />
          * 权限认证框架 Sa-Token、Jwt 支持多终端认证系统<br />
          * 关系数据库 MySQL 适配 8.X 最低 5.7<br />
          * 关系数据库 Oracle 适配 11g 12c<br />
          * 关系数据库 PostgreSQL 适配 13 14<br />
          * 关系数据库 SQLServer 适配 2017 2019<br />
          * 缓存数据库 Redis 适配 6.X 最低 5.X<br />
          * 分布式注册中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
          * 分布式配置中心 Alibaba Nacos 采用2.X 基于GRPC通信高性能<br />
          * 服务网关 Spring Cloud Gateway 响应式高性能网关<br />
          * 负载均衡 Spring Cloud Loadbalancer 负载均衡处理<br />
          * RPC远程调用 Apache Dubbo 原生态使用体验、高性能<br />
          * 分布式限流熔断 Alibaba Sentinel 无侵入、高扩展<br />
          * 分布式事务 Alibaba Seata 无侵入、高扩展 支持 四种模式<br />
          * 分布式消息队列 Spring Cloud Stream 门面框架兼容各种MQ集成<br />
          * 分布式消息队列 Apache Kafka 高性能高速度<br />
          * 分布式消息队列 Apache RocketMQ 高可用功能多样<br />
          * 分布式消息队列 RabbitMQ 支持各种扩展插件功能多样性<br />
          * 分布式搜索引擎 ElasticSearch 业界知名<br />
          * 分布式链路追踪 Apache SkyWalking 链路追踪、网格分析、度量聚合、可视化<br />
          * 分布式日志中心 ELK 业界成熟解决方案<br />
          * 分布式监控 Prometheus、Grafana 全方位性能监控<br />
          * 其余与 Vue 版本一致<br />
        </p>
        <p><b>当前版本:</b> <span>v2.1.0</span></p>
        <p>
          <el-tag type="danger">&yen;免费开源</el-tag>
        </p>
        <p>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://gitee.com/dromara/RuoYi-Cloud-Plus')">访问码云</el-button>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://github.com/dromara/RuoYi-Cloud-Plus')">访问GitHub</el-button>
          <el-button type="primary" icon="Cloudy" plain @click="goTarget('https://plus-doc.dromara.org/#/ruoyi-cloud-plus/changlog')"
            >更新日志</el-button
          >
        </p>
      </el-col>
    </el-row>
    <el-divider />
        <el-card style="width: 100%;flex: 1;" class="mb-4">
       <div style="display: flex">
         <div>
           <el-statistic  :value="batchScore" :suffix="'分'" :precision="1" :value-style="{fontSize: '30px',fontWeight: 'bold'}">
             <template #title>
               <div style="display: inline-flex; align-items: center;font-size: 18px">
                 综合质量评分
                 <el-tooltip effect="dark" content="" placement="top">
                   <el-icon style="margin-left: 4px" :size="12">
                     <Warning />
                   </el-icon>
                 </el-tooltip>
               </div>
             </template>
           </el-statistic>
           <div class="statistic-footer">
             <div class="footer-item">
               <span>较昨天</span>
               <span :class="{ 'green': batchScore > 9.4, 'red': batchScore <= 9.4 }">
                <el-icon v-if="batchScore > 9.4">
                  <CaretTop />
                </el-icon>
                <el-icon v-else>
                  <CaretBottom />
                </el-icon>
              </span>
             </div>
           </div>
         </div>
         <div style="flex: 1;display: flex;justify-content: center;align-items: center">
           <el-image style="width: 60px; height: 60px" :src="q1" fit="contain" />
         </div>
       </div>
        </el-card>
        <el-card style="flex: 1" class="mb-4">
          <div style="display: flex">
            <div>
              <el-statistic :value="qualityRate" :suffix="'%'" :value-style="{fontSize: '30px',fontWeight: 'bold'}">
                <template #title>
                  <div style="display: inline-flex; align-items: center;font-size: 18px">
                    良品率
                    <el-tooltip effect="dark" content="" placement="top">
                      <el-icon style="margin-left: 4px" :size="12">
                        <Warning />
                      </el-icon>
                    </el-tooltip>
                  </div>
                </template>
              </el-statistic>
              <div class="statistic-footer">
                <div class="footer-item">
                  <span>较昨天</span>
                  <span :class="{ 'green': qualityRate >= 95, 'red': qualityRate < 95 }">
                <el-icon v-if="qualityRate >= 95">
                  <CaretTop />
                </el-icon>
                <el-icon v-else>
                  <CaretBottom />
                </el-icon>
              </span>
                </div>
              </div>
            </div>
            <div style="flex: 1;display: flex;justify-content: center;align-items: center">
              <el-image style="width: 60px; height: 60px" :src="q2" fit="contain" />
            </div>
          </div>
        </el-card>
        <el-card style="flex: 1" class="mb-4">
          <div style="display: flex">
            <div>
              <el-statistic :value="checkNum" :suffix="'pcs'" :value-style="{fontSize: '30px',fontWeight: 'bold'}">
                <template #title>
                  <div style="display: inline-flex; align-items: center;font-size: 18px">
                    检测数量
                    <el-tooltip effect="dark" content="" placement="top">
                      <el-icon style="margin-left: 4px" :size="12">
                        <Warning />
                      </el-icon>
                    </el-tooltip>
                  </div>
                </template>
              </el-statistic>
              <div class="statistic-footer">
                <div class="footer-item">
                  <span>较昨天</span>
                  <span :class="{ 'green': batchScore > 1, 'red': batchScore <= 1 }">
                <el-icon v-if="batchScore > 1">
                  <CaretTop />
                </el-icon>
                <el-icon v-else>
                  <CaretBottom />
                </el-icon>
              </span>
                </div>
              </div>
            </div>
            <div style="flex: 1;display: flex;justify-content: center;align-items: center">
              <el-image style="width: 60px; height: 60px" :src="q3" fit="contain" />
            </div>
          </div>
        </el-card>
        <el-card style="flex: 1" class="mb-4">
          <div style="display: flex">
            <div>
              <el-statistic :value="ngCountNum" :suffix="'pcs'"  :value-style="{fontSize: '30px',fontWeight: 'bold'}">
                <template #title>
                  <div style="display: inline-flex; align-items: center;font-size: 18px">
                    异常数量
                    <el-tooltip effect="dark" content="" placement="top">
                      <el-icon style="margin-left: 4px" :size="12">
                        <Warning />
                      </el-icon>
                    </el-tooltip>
                  </div>
                </template>
              </el-statistic>
              <div class="statistic-footer">
                <div class="footer-item">
                  <span>较昨天</span>
                  <span :class="{ 'green': ngCountNum < 50, 'red': ngCountNum  >= 50 }">
                <el-icon v-if="ngCountNum >= 50">
                  <CaretTop />
                </el-icon>
                <el-icon v-else>
                  <CaretBottom />
                </el-icon>
              </span>
                  <span style="margin-left: 10px">预测异常率:</span>
                  <span  class="red" v-if="qualityRate > 80"> {{(((100-qualityRate)/ 3)+0.1).toFixed(1) }}% </span>
                  <span v-else > 3.7%</span>
                </div>
              </div>
            </div>
            <div style="flex: 1;display: flex;justify-content: center;align-items: center">
              <el-image style="width: 60px; height: 60px" :src="q4" fit="contain" />
            </div>
          </div>
        </el-card>
      </div>
      <!-- 设备健康状态可视化 -->
      <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
        <el-card shadow="hover" class="h-420" >
          <template #header>
            <div class="card-header">
              <span>健康度评分</span>
            </div>
          </template>
          <div ref="healthChartRef" class="h-400 w-full"></div>
        </el-card>
        <el-card shadow="hover" class="h-420">
          <template #header>
            <div class="card-header">
              <span>质量预警信息</span>
            </div>
          </template>
<!--          <div class="grid grid-cols-2 gap-2">-->
<!--            <div class="p-2 bg-blue-50 rounded">-->
<!--              <p class="text-sm text-gray-600">齐纳保护电压(黑)</p>-->
<!--              <p class="text-xl">39.00 V ≤ result ≤ 49.00 V</p>-->
<!--            </div>-->
<!--            <div class="p-2 bg-yellow-50 rounded">-->
<!--              <p class="text-sm text-gray-600">距离</p>-->
<!--              <p class="text-xl">4.75 mm ≤ result ≤ 5.15 mm</p>-->
<!--            </div>-->
<!--            <div class="p-2 bg-red-50 rounded">-->
<!--              <p class="text-sm text-gray-600">压降(黑)1</p>-->
<!--              <p class="text-xl">0.30 V ≤ result ≤ 3.00 V</p>-->
<!--            </div>-->
<!--            <div class="p-2 bg-green-50 rounded">-->
<!--              <p class="text-sm text-gray-600">回差</p>-->
<!--              <p class="text-xl">0.05 mm ≤ result ≤ 1.00 mm</p>-->
<!--            </div>-->
<!--            <div class="p-2 bg-blue-50 rounded">-->
<!--              <p class="text-sm text-gray-600">漏电流(黑)1</p>-->
<!--              <p class="text-xl">result ≤ 800.00 uA</p>-->
<!--            </div>-->
<!--            <div class="p-2 bg-yellow-50 rounded">-->
<!--              <p class="text-sm text-gray-600"></p>-->
<!--              <p class="text-xl font-bold"></p>-->
<!--            </div>-->
<!--          </div>-->
          <el-table size="large"   :data="warnBatchList" style="width: 100%" :border="true" stripe>
            <el-table-column prop="batchTime" label="预警时间" />
            <el-table-column prop="batchCode" label="批次号"  width="140" />
            <el-table-column prop="gw" label="设备/工位"  />
            <el-table-column prop="yc" label="异常类型"   />
            <el-table-column prop="cd" label="严重程度">
              <template #default="{ row }">
                <el-tag :type="getStatusTagType(row.cd)">{{ row.cd }}</el-tag>
              </template>
            </el-table-column>
          </el-table>
        </el-card>
      </div>
      <el-card shadow="hover" class="mb-6">
        <template #header>
          <div class="card-header">
            <span>检测批次</span>
            <el-date-picker
              v-model="batchDate"
              type="date"
              placeholder="选择日期"
              :disabled-date="disabledDate"
              :shortcuts="shortcuts"
              @change="changeBatchDate"
            />
          </div>
        </template>
        <div class="grid grid-cols-1 gap-4">
          <!-- 这里将放置预警表格 -->
          <el-table v-loading="loading" :data="batchList" style="width: 100%" :border="true" stripe>
            <el-table-column prop="batchCode" label="批次号" width="180" />
            <el-table-column prop="prodModel" label="产品型号" width="180" />
            <el-table-column prop="num" label="检测数量" width="120" />
            <el-table-column prop="okNum" label="良品数量" width="120" />
            <el-table-column prop="ngNum" label="不良数量" width="120" />
            <el-table-column prop="lpv" label="良品率" width="120">
              <template #default="{ row }">
                <el-tag :type="goodProductsRate(row) >= 95 ? 'success' : 'warning'"> {{ goodProductsRate(row) }} % </el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="whjy" label="维护建议">
              <template #default="{ row }">
                <el-tag :type="goodProductsRate(row) >= 95 ? 'success' : 'warning'">
                  {{ goodProductsRate(row) >= 95 ? '良品率指标已优异,请关注设备综合效率' : '预警,检查工艺参数是否符合标准,优化关键参数' }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作" width="180">
              <template #default="{ row }">
                <el-button link type="primary" @click="handleDetail(row)">详情</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-card>
      <!-- 备件信息 -->
      <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6">
        <!-- 批次合格率趋势 -->
        <el-card shadow="hover" class="h-420">
          <template #header>
            <div class="card-header">
              <span>批次合格率趋势</span>
            </div>
          </template>
          <div ref="batchRateTrendRef" class="h-400 w-full"></div>
        </el-card>
        <el-card shadow="hover" class="h-420">
          <template #header>
            <div class="card-header">
              <span>近一周NG项TOP5</span>
            </div>
          </template>
          <div ref="ngChartRef" class="h-400 w-full"></div>
        </el-card>
      </div>
    </div>
  </div>
</template>
<script setup name="Index" lang="ts">
import { initWebSocket } from '@/utils/websocket';
import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import * as echarts from 'echarts';
import q1 from '@/assets/images/quailty/q1.png';
import q2 from '@/assets/images/quailty/q2.png';
import q3 from '@/assets/images/quailty/q3.png';
import q4 from '@/assets/images/quailty/q4.png';
import { queryQualityHealth, queryBatchList,queryPwbatchList ,queryNgrank} from '@/api/qms/index';
import { BatchVO } from '@/api/qms/batch/types';
import { listBatch } from '@/api/qms/batch';
const batchScore = ref(0);
const qualityRate = ref(0);
const checkNum = ref(0);
const ngCountNum = ref(0);
const batchDate = ref('');
const healthChartRef = ref<HTMLElement | null>(null);
const batchRateTrendRef = ref<HTMLElement | null>(null);
const ngChartRef = ref<HTMLElement | null>(null);
const healthChart = ref();
const batchRateChart = ref();
const ngChart = ref();
const router = useRouter();
const loading = ref(false);
const batchList = ref<BatchVO[]>([]);
const warnBatchList = ref<any>([]);
const gwList = [
  "微机电系统蚀刻机",
  "晶圆键合机",
  "传感器校准站",
  "薄膜沉积设备",
  "光刻机",
  "离子注入机",
  "化学机械抛光设备",
  "热处理炉",
  "封装测试站",
  "激光修调设备",
  "电性能测试台",
  "光学检测仪",
  "真空镀膜机",
  "超声波清洗机",
  "X射线检测仪"
]
const ycList =  [
  "蚀刻深度偏差",
  "键合压力异常",
  "校准数据偏移",
  "沉积速率波动",
  "光刻对准偏差",
  "离子注入剂量异常",
  "抛光厚度不均",
  "温度稳定性异常",
  "封装密封性不良",
  "电阻值偏差",
  "灵敏度异常",
  "响应时间超限",
  "信号漂移异常",
  "线性度偏差",
  "零点漂移异常",
  "过载恢复异常",
  "绝缘电阻不足",
  "介质耐压不合格",
  "频率响应异常",
  "信噪比不达标"
];
const cdList = [
  "高", "高", "中", "中", "高",
  "中", "中", "高", "高", "中",
  "高", "中", "中", "中", "低",
  "中", "高", "高", "中", "低"
];
const getStatusTagType = (status: string) => {
  switch (status) {
    case '高': return 'danger';
    case '中': return 'warning';
    case '低': return 'info';
  }
};
function changeBatchDate(date: Date){
  getBatch(date ? date : new Date())
}
async function queryWarnBatchList(){
  const queryParams = {
    pageNum: 1,
    pageSize: 6
  };
  const res:any = await listBatch(queryParams);
  if(res && res.rows){
    warnBatchList.value = [];
    res.rows.forEach((item,index) => {
      item.gw = gwList[index];
      item.yc = ycList[index];
      item.cd = cdList[index];
      item.batchTime = item.batchTime.substring(0,10);
      warnBatchList.value.push(item);
    })
  }
}
const shortcuts = [
  {
    text: '今天',
    value: new Date(),
  },
  {
    text: '昨天',
    value: () => {
      const date = new Date()
      date.setTime(date.getTime() - 3600 * 1000 * 24)
      return date
    },
  },
  {
    text: '一周前',
    value: () => {
      const date = new Date()
      date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)
      return date
    },
  },
]
const disabledDate = (time: Date) => {
  return time.getTime() > Date.now()
}
const goodProductsRate = (row) => {
  if (row.num > 0 && row.okNum > 0) {
    return Math.round((row.okNum / row.num) * 100);
  }
};
const generateWorkOrder = (record) => {
  // 工单生成逻辑
  ElMessage.success(`已为 ${record.name} 生成工单`);
};
const handleDetail = (record) => {
  router.push({ path: '/quality/detail', query: { id: record.id } });
};
const initHealthChart = () => {
  if (healthChartRef.value) {
    healthChart.value = echarts.init(healthChartRef.value);
    const option = {
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'shadow'
        }
      },
      xAxis: {
        type: 'category',
        data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
      },
      yAxis: {
        type: 'value',
        name: '得分',
        minInterval: 1
      },
      series: [
        {
          name: '质量数据',
          type: 'bar',
          barWidth: '50%',
          data: [
            { value: 9.8, itemStyle: { color: '#52c41a' } },
            { value: 9.9, itemStyle: { color: '#52c41a' } },
            { value: 9.7, itemStyle: { color: '#52c41a' } },
            { value: 9.8, itemStyle: { color: '#52c41a' } },
            { value: 9.9, itemStyle: { color: '#52c41a' } },
            { value: 9.8, itemStyle: { color: '#52c41a' } },
            { value: 9.9, itemStyle: { color: '#52c41a' } },
            { value: 9.8, itemStyle: { color: '#52c41a' } },
            { value: 9.8, itemStyle: { color: '#52c41a' } }
          ],
          label: {
            show: true,
            position: 'top',
            formatter: '{c}分'
          }
        }
      ]
    };
    healthChart.value.setOption(option);
    window.addEventListener('resize', () => {
      healthChart.value.resize();
    });
  }
};
const initBatchRateChart = () => {
  if (batchRateTrendRef.value) {
    batchRateChart.value = echarts.init(batchRateTrendRef.value);
    const option = {
      color: ['#FFBF00'],
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'cross',
          label: {
            backgroundColor: '#6a7985'
          }
        }
      },
      xAxis: [
        {
          splitLine: { show: false },
          type: 'category',
          boundaryGap: false,
          data: ['08-01', '08-02', '08-03', '08-04', '08-05', '08-06', '08-07']
        }
      ],
      yAxis: [
        {
          splitLine: { show: false },
          type: 'value',
          name: '检测合格率',
          min: 80, // 设置最小值为90
          max: 100, // 设置最大值为100
          interval: 5, // 设置刻度间隔为1
          axisLabel: {
            formatter: '{value}%' // 添加百分比符号
          }
        }
      ],
      series: [
        {
          name: '',
          type: 'line',
          stack: 'Total',
          smooth: true,
          lineStyle: {
            width: 0
          },
          showSymbol: false,
          areaStyle: {
            opacity: 0.8,
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              {
                offset: 0,
                color: 'rgb(128, 255, 165)'
              },
              {
                offset: 1,
                color: 'rgb(1, 191, 236)'
              }
            ])
          },
          emphasis: {
            focus: 'series'
          },
          data: [99, 98, 95, 99, 92, 98, 96],
        }
      ]
    };
    option && batchRateChart.value.setOption(option);
    window.addEventListener('resize', () => {
      batchRateChart.value.resize();
    });
  }
};
const initNgChart = () => {
  if (ngChartRef.value) {
    ngChart.value = echarts.init(ngChartRef.value);
    const option = {
      tooltip: {
        trigger: 'axis',
        axisPointer: {
          type: 'shadow'
        }
      },
      grid: {
        left: '120px', // 增加左侧边距,为标签留出更多空间
        top: '0px',
      },
      xAxis: {
        type: 'value',
        boundaryGap: [0, 0.01],
        splitLine: { show: false }
      },
      yAxis: {
        splitLine: { show: false },
        type: 'category',
        data: [
          '静态消耗电流2',
          '回差',
          '短路保护消耗电流(黑)2',
          '动态消耗电流2',
          '距离',
          '压降(黑)2'
        ]
      },
      series: [
        {
          name: '',
          type: 'bar',
          barWidth: '60%',
          data: [100, 90, 80, 70, 60, 10],
          itemStyle: {
            color: '#faad14'  // 修改柱状图颜色
          },
          label: {
            show: true,
            position: 'right',
            formatter: '{c}次'
          }
        }
      ]
    };
    option && ngChart.value.setOption(option);
    window.addEventListener('resize', () => {
      ngChart.value.resize();
    });
  }
};
const getHealth = async () => {
  const res: any = await queryQualityHealth();
  if (res && res.yAxis) {
    const xAxisData = [];
    const minValue = res.yAxis.reduce((min, item) => {
      return min === null ? item.value : Math.min(min, item.value);
    }, null);
    res.yAxis.forEach((item, index) => {
      if (item.value >= 9.5) {
        item.itemStyle = { color: '#52c41a' };
      } else if (item.value < 9.5 && item.value >= 9.0) {
        item.itemStyle = { color: '#faad14' };
      } else {
        item.itemStyle = { color: '#f5222d' };
      }
      if (item.value == minValue) {
        item.itemStyle = { color: '#faad14' };
      }
      xAxisData.push(item);
    });
    updateHealthData(res.xAxis, xAxisData);
  }
};
const getBatch = async (date = new Date()) => {
  loading.value = true;
  const res: any = await queryBatchList({
    pageNum: 1,
    pageSize: 10,
    params: {
      startTime: formatDate(date) + ' 00:00:00',
      endTime: formatDate(date) + ' 23:59:59'
    }
  });
  if (!res || !res.rows) {
    return;
  }
  batchList.value = res.rows;
  checkNum.value = res.rows.reduce((sum, item) => sum + (item.num || 0), 0);
  ngCountNum.value = res.rows.reduce((sum, item) => sum + (item.ngNum || 0), 0);
  // 计算oknum总和
  const totalOkNum = res.rows.reduce((sum, item) => sum + (item.okNum || 0), 0);
  // 计算良品率 (避免除以0的情况)
  const yieldRate = checkNum.value > 0 ? (totalOkNum / checkNum.value) * 100 : 0;
  qualityRate.value = Number(yieldRate.toFixed(2));
  batchScore.value =  Number(((yieldRate/10 )-0.1).toFixed(1));
  if(batchScore.value == -0.1){
    batchScore.value = 0;
  }
  loading.value = false;
};
const getPwbatch = async () => {
  const res: any = await queryPwbatchList();
  if (res && res.yAxis) {
    updatePwbatchData(res.xAxis, res.yAxis);
  }
};
const getNgrank = async () => {
  const res: any = await queryNgrank();
  if (res && res.yAxis) {
    updateNgrankData(res.xAxis, res.yAxis);
  }
};
const formatDate = (date) => {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
};
function updateHealthData(xAxis, yAxis) {
  healthChart.value.setOption({
    xAxis: {
      type: 'category',
      data: xAxis
    },
    series: [
      {
        data: yAxis
      }
    ]
  });
}
function updatePwbatchData(xAxis, yAxis) {
  batchRateChart.value.setOption({
    xAxis: {
      type: 'category',
      data: xAxis
    },
    series: [
      {
        data: yAxis
      }
    ]
  });
}
function updateNgrankData(xAxis, yAxis) {
  ngChart.value.setOption({
    yAxis: {
      type: 'category',
      data: xAxis,
      inverse: true,
    },
    series: [
      {
        data: yAxis
      }
    ]
  });
}
onMounted(() => {
  initWebSocket("ws://" + window.location.host + import.meta.env.VITE_APP_BASE_API + "/resource/websocket");
  queryWarnBatchList();
  initHealthChart();
  initBatchRateChart();
  initNgChart();
  getHealth();
  getBatch();
  getPwbatch();
  getNgrank();
});
const goTarget = (url:string) => {
  window.open(url, '__blank')
}
</script>
<style scoped lang="scss">
.home {
  blockquote {
    padding: 10px 20px;
    margin: 0 0 20px;
    font-size: 17.5px;
    border-left: 5px solid #eee;
  }
  hr {
    margin-top: 20px;
    margin-bottom: 20px;
    border: 0;
    border-top: 1px solid #eee;
  }
  .col-item {
    margin-bottom: 20px;
  }
.h-500 {
  height: 500px;
}
  ul {
    padding: 0;
    margin: 0;
.card-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-weight: bold;
}
.grid {
  display: grid;
}
.grid-cols-1 {
  grid-template-columns: repeat(1, minmax(0, 1fr));
}
.grid-cols-2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.gap-4 {
  gap: 1rem;
}
.mb-6 {
  margin-bottom: 1.5rem;
}
.p-5 {
  padding: 1.25rem;
}
.w-full {
  width: 100%;
}
.h-400 {
  height: 400px;
}
.h-420 {
  height: 420px;
}
.bg-blue-50 {
  background-color: #eff6ff;
}
.bg-yellow-50 {
  background-color: #fffbeb;
}
.bg-red-50 {
  background-color: #fef2f2;
}
.bg-green-50 {
  background-color: #f0fdf4;
}
.rounded {
  border-radius: 0.25rem;
}
.text-sm {
  font-size:14px;
}
.text-xl {
  font-size: 14px;
}
.font-bold {
  font-weight: 700;
}
.text-gray-600 {
  color: #4b5563;
}
@media (min-width: 768px) {
  .md\:grid-cols-2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
  font-family: "open sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 13px;
  color: #676a6c;
  overflow-x: hidden;
.statistic-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  font-size: 12px;
  color: var(--el-text-color-regular);
  margin-top: 16px;
}
  ul {
    list-style-type: none;
  }
.statistic-footer .footer-item {
  font-size: 14px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}
  h4 {
    margin-top: 0px;
  }
.statistic-footer .footer-item span:last-child {
  display: inline-flex;
  align-items: center;
  margin-left: 4px;
}
  h2 {
    margin-top: 10px;
    font-size: 26px;
    font-weight: 100;
  }
.green {
  color: var(--el-color-success);
}
  p {
    margin-top: 10px;
    b {
      font-weight: 700;
    }
  }
  .update-log {
    ol {
      display: block;
      list-style-type: decimal;
      margin-block-start: 1em;
      margin-block-end: 1em;
      margin-inline-start: 0;
      margin-inline-end: 0;
      padding-inline-start: 40px;
    }
  }
.red {
  color: var(--el-color-error);
}
</style>