| | |
| | | <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 /> |
| | | * 分布式任务调度 SnailJob 高性能 高可靠 易扩展<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.2.1</span></p> |
| | | <p> |
| | | <el-tag type="danger">¥免费开源</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 /> |
| | | * 分布式消息队列 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.2.0</span></p> |
| | | <p> |
| | | <el-tag type="danger">¥免费开源</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"> |
| | | const goTarget = (url: string) => { |
| | | window.open(url, '__blank'); |
| | | import { onMounted, ref } from 'vue'; |
| | | import { useRouter } from 'vue-router'; |
| | | import { ElMessage } from 'element-plus'; |
| | | import * as echarts from 'echarts'; |
| | | |
| | | import q1 from '@/assets/images/quailty/q1.png'; |
| | | import q2 from '@/assets/images/quailty/q2.png'; |
| | | import q3 from '@/assets/images/quailty/q3.png'; |
| | | import q4 from '@/assets/images/quailty/q4.png'; |
| | | |
| | | import { queryQualityHealth, queryBatchList,queryPwbatchList ,queryNgrank} from '@/api/qms/index'; |
| | | import { BatchVO } from '@/api/qms/batch/types'; |
| | | import { listBatch } from '@/api/qms/batch'; |
| | | |
| | | const batchScore = ref(0); |
| | | const qualityRate = ref(0); |
| | | const checkNum = ref(0); |
| | | const ngCountNum = ref(0); |
| | | |
| | | const batchDate = ref(''); |
| | | |
| | | const healthChartRef = ref<HTMLElement | null>(null); |
| | | const batchRateTrendRef = ref<HTMLElement | null>(null); |
| | | const ngChartRef = ref<HTMLElement | null>(null); |
| | | const healthChart = ref(); |
| | | const batchRateChart = ref(); |
| | | const ngChart = ref(); |
| | | const router = useRouter(); |
| | | |
| | | const loading = ref(false); |
| | | const batchList = ref<BatchVO[]>([]); |
| | | const warnBatchList = ref<any>([]); |
| | | |
| | | |
| | | const gwList = [ |
| | | "微机电系统蚀刻机", |
| | | "晶圆键合机", |
| | | "传感器校准站", |
| | | "薄膜沉积设备", |
| | | "光刻机", |
| | | "离子注入机", |
| | | "化学机械抛光设备", |
| | | "热处理炉", |
| | | "封装测试站", |
| | | "激光修调设备", |
| | | "电性能测试台", |
| | | "光学检测仪", |
| | | "真空镀膜机", |
| | | "超声波清洗机", |
| | | "X射线检测仪" |
| | | ] |
| | | const ycList = [ |
| | | "蚀刻深度偏差", |
| | | "键合压力异常", |
| | | "校准数据偏移", |
| | | "沉积速率波动", |
| | | "光刻对准偏差", |
| | | "离子注入剂量异常", |
| | | "抛光厚度不均", |
| | | "温度稳定性异常", |
| | | "封装密封性不良", |
| | | "电阻值偏差", |
| | | "灵敏度异常", |
| | | "响应时间超限", |
| | | "信号漂移异常", |
| | | "线性度偏差", |
| | | "零点漂移异常", |
| | | "过载恢复异常", |
| | | "绝缘电阻不足", |
| | | "介质耐压不合格", |
| | | "频率响应异常", |
| | | "信噪比不达标" |
| | | ]; |
| | | |
| | | const cdList = [ |
| | | "高", "高", "中", "中", "高", |
| | | "中", "中", "高", "高", "中", |
| | | "高", "中", "中", "中", "低", |
| | | "中", "高", "高", "中", "低" |
| | | ]; |
| | | const getStatusTagType = (status: string) => { |
| | | switch (status) { |
| | | case '高': return 'danger'; |
| | | case '中': return 'warning'; |
| | | case '低': return 'info'; |
| | | } |
| | | }; |
| | | |
| | | function changeBatchDate(date: Date){ |
| | | getBatch(date ? date : new Date()) |
| | | |
| | | } |
| | | async function queryWarnBatchList(){ |
| | | const queryParams = { |
| | | pageNum: 1, |
| | | pageSize: 6 |
| | | }; |
| | | const res:any = await listBatch(queryParams); |
| | | |
| | | if(res && res.rows){ |
| | | warnBatchList.value = []; |
| | | res.rows.forEach((item,index) => { |
| | | item.gw = gwList[index]; |
| | | item.yc = ycList[index]; |
| | | item.cd = cdList[index]; |
| | | item.batchTime = item.batchTime.substring(0,10); |
| | | warnBatchList.value.push(item); |
| | | }) |
| | | } |
| | | |
| | | } |
| | | |
| | | const shortcuts = [ |
| | | { |
| | | text: '今天', |
| | | value: new Date(), |
| | | }, |
| | | { |
| | | text: '昨天', |
| | | value: () => { |
| | | const date = new Date() |
| | | date.setTime(date.getTime() - 3600 * 1000 * 24) |
| | | return date |
| | | }, |
| | | }, |
| | | { |
| | | text: '一周前', |
| | | value: () => { |
| | | const date = new Date() |
| | | date.setTime(date.getTime() - 3600 * 1000 * 24 * 7) |
| | | return date |
| | | }, |
| | | }, |
| | | ] |
| | | |
| | | const disabledDate = (time: Date) => { |
| | | return time.getTime() > Date.now() |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | const goodProductsRate = (row) => { |
| | | if (row.num > 0 && row.okNum > 0) { |
| | | return Math.round((row.okNum / row.num) * 100); |
| | | } |
| | | }; |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | const generateWorkOrder = (record) => { |
| | | // 工单生成逻辑 |
| | | ElMessage.success(`已为 ${record.name} 生成工单`); |
| | | }; |
| | | |
| | | const handleDetail = (record) => { |
| | | router.push({ path: '/quality/detail', query: { id: record.id } }); |
| | | }; |
| | | |
| | | const initHealthChart = () => { |
| | | if (healthChartRef.value) { |
| | | healthChart.value = echarts.init(healthChartRef.value); |
| | | const option = { |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }, |
| | | xAxis: { |
| | | type: 'category', |
| | | data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] |
| | | }, |
| | | yAxis: { |
| | | type: 'value', |
| | | name: '得分', |
| | | minInterval: 1 |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '质量数据', |
| | | type: 'bar', |
| | | barWidth: '50%', |
| | | data: [ |
| | | { value: 9.8, itemStyle: { color: '#52c41a' } }, |
| | | { value: 9.9, itemStyle: { color: '#52c41a' } }, |
| | | { value: 9.7, itemStyle: { color: '#52c41a' } }, |
| | | { value: 9.8, itemStyle: { color: '#52c41a' } }, |
| | | { value: 9.9, itemStyle: { color: '#52c41a' } }, |
| | | { value: 9.8, itemStyle: { color: '#52c41a' } }, |
| | | { value: 9.9, itemStyle: { color: '#52c41a' } }, |
| | | { value: 9.8, itemStyle: { color: '#52c41a' } }, |
| | | { value: 9.8, itemStyle: { color: '#52c41a' } } |
| | | ], |
| | | label: { |
| | | show: true, |
| | | position: 'top', |
| | | formatter: '{c}分' |
| | | } |
| | | } |
| | | ] |
| | | }; |
| | | healthChart.value.setOption(option); |
| | | |
| | | window.addEventListener('resize', () => { |
| | | healthChart.value.resize(); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const initBatchRateChart = () => { |
| | | if (batchRateTrendRef.value) { |
| | | batchRateChart.value = echarts.init(batchRateTrendRef.value); |
| | | const option = { |
| | | color: ['#FFBF00'], |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'cross', |
| | | label: { |
| | | backgroundColor: '#6a7985' |
| | | } |
| | | } |
| | | }, |
| | | |
| | | xAxis: [ |
| | | { |
| | | splitLine: { show: false }, |
| | | type: 'category', |
| | | boundaryGap: false, |
| | | data: ['08-01', '08-02', '08-03', '08-04', '08-05', '08-06', '08-07'] |
| | | } |
| | | ], |
| | | yAxis: [ |
| | | { |
| | | splitLine: { show: false }, |
| | | type: 'value', |
| | | name: '检测合格率', |
| | | min: 80, // 设置最小值为90 |
| | | max: 100, // 设置最大值为100 |
| | | interval: 5, // 设置刻度间隔为1 |
| | | axisLabel: { |
| | | formatter: '{value}%' // 添加百分比符号 |
| | | } |
| | | } |
| | | ], |
| | | series: [ |
| | | { |
| | | name: '', |
| | | type: 'line', |
| | | stack: 'Total', |
| | | smooth: true, |
| | | lineStyle: { |
| | | width: 0 |
| | | }, |
| | | showSymbol: false, |
| | | areaStyle: { |
| | | opacity: 0.8, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { |
| | | offset: 0, |
| | | color: 'rgb(128, 255, 165)' |
| | | }, |
| | | { |
| | | offset: 1, |
| | | color: 'rgb(1, 191, 236)' |
| | | } |
| | | ]) |
| | | }, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | data: [99, 98, 95, 99, 92, 98, 96], |
| | | |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | option && batchRateChart.value.setOption(option); |
| | | |
| | | |
| | | window.addEventListener('resize', () => { |
| | | batchRateChart.value.resize(); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const initNgChart = () => { |
| | | if (ngChartRef.value) { |
| | | ngChart.value = echarts.init(ngChartRef.value); |
| | | const option = { |
| | | |
| | | tooltip: { |
| | | trigger: 'axis', |
| | | axisPointer: { |
| | | type: 'shadow' |
| | | } |
| | | }, |
| | | grid: { |
| | | left: '120px', // 增加左侧边距,为标签留出更多空间 |
| | | top: '0px', |
| | | }, |
| | | xAxis: { |
| | | type: 'value', |
| | | boundaryGap: [0, 0.01], |
| | | splitLine: { show: false } |
| | | }, |
| | | yAxis: { |
| | | splitLine: { show: false }, |
| | | type: 'category', |
| | | data: [ |
| | | '静态消耗电流2', |
| | | '回差', |
| | | '短路保护消耗电流(黑)2', |
| | | '动态消耗电流2', |
| | | '距离', |
| | | '压降(黑)2' |
| | | ] |
| | | }, |
| | | series: [ |
| | | { |
| | | name: '', |
| | | type: 'bar', |
| | | barWidth: '60%', |
| | | data: [100, 90, 80, 70, 60, 10], |
| | | itemStyle: { |
| | | color: '#faad14' // 修改柱状图颜色 |
| | | }, |
| | | label: { |
| | | show: true, |
| | | position: 'right', |
| | | formatter: '{c}次' |
| | | } |
| | | } |
| | | ] |
| | | }; |
| | | |
| | | option && ngChart.value.setOption(option); |
| | | |
| | | |
| | | |
| | | window.addEventListener('resize', () => { |
| | | ngChart.value.resize(); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | const getHealth = async () => { |
| | | const res: any = await queryQualityHealth(); |
| | | if (res && res.yAxis) { |
| | | const xAxisData = []; |
| | | const minValue = res.yAxis.reduce((min, item) => { |
| | | return min === null ? item.value : Math.min(min, item.value); |
| | | }, null); |
| | | res.yAxis.forEach((item, index) => { |
| | | if (item.value >= 9.5) { |
| | | item.itemStyle = { color: '#52c41a' }; |
| | | } else if (item.value < 9.5 && item.value >= 9.0) { |
| | | item.itemStyle = { color: '#faad14' }; |
| | | } else { |
| | | item.itemStyle = { color: '#f5222d' }; |
| | | } |
| | | if (item.value == minValue) { |
| | | item.itemStyle = { color: '#faad14' }; |
| | | } |
| | | xAxisData.push(item); |
| | | }); |
| | | updateHealthData(res.xAxis, xAxisData); |
| | | } |
| | | }; |
| | | |
| | | const getBatch = async (date = new Date()) => { |
| | | loading.value = true; |
| | | |
| | | const res: any = await queryBatchList({ |
| | | pageNum: 1, |
| | | pageSize: 10, |
| | | params: { |
| | | startTime: formatDate(date) + ' 00:00:00', |
| | | endTime: formatDate(date) + ' 23:59:59' |
| | | } |
| | | }); |
| | | if (!res || !res.rows) { |
| | | return; |
| | | } |
| | | batchList.value = res.rows; |
| | | |
| | | checkNum.value = res.rows.reduce((sum, item) => sum + (item.num || 0), 0); |
| | | |
| | | ngCountNum.value = res.rows.reduce((sum, item) => sum + (item.ngNum || 0), 0); |
| | | // 计算oknum总和 |
| | | const totalOkNum = res.rows.reduce((sum, item) => sum + (item.okNum || 0), 0); |
| | | // 计算良品率 (避免除以0的情况) |
| | | const yieldRate = checkNum.value > 0 ? (totalOkNum / checkNum.value) * 100 : 0; |
| | | qualityRate.value = Number(yieldRate.toFixed(2)); |
| | | batchScore.value = Number(((yieldRate/10 )-0.1).toFixed(1)); |
| | | if(batchScore.value == -0.1){ |
| | | batchScore.value = 0; |
| | | } |
| | | loading.value = false; |
| | | }; |
| | | |
| | | const getPwbatch = async () => { |
| | | const res: any = await queryPwbatchList(); |
| | | if (res && res.yAxis) { |
| | | updatePwbatchData(res.xAxis, res.yAxis); |
| | | } |
| | | }; |
| | | |
| | | |
| | | const getNgrank = async () => { |
| | | const res: any = await queryNgrank(); |
| | | if (res && res.yAxis) { |
| | | updateNgrankData(res.xAxis, res.yAxis); |
| | | } |
| | | }; |
| | | |
| | | const formatDate = (date) => { |
| | | const year = date.getFullYear(); |
| | | const month = String(date.getMonth() + 1).padStart(2, '0'); |
| | | const day = String(date.getDate()).padStart(2, '0'); |
| | | return `${year}-${month}-${day}`; |
| | | }; |
| | | |
| | | function updateHealthData(xAxis, yAxis) { |
| | | healthChart.value.setOption({ |
| | | xAxis: { |
| | | type: 'category', |
| | | data: xAxis |
| | | }, |
| | | series: [ |
| | | { |
| | | data: yAxis |
| | | } |
| | | ] |
| | | }); |
| | | } |
| | | |
| | | |
| | | function updatePwbatchData(xAxis, yAxis) { |
| | | batchRateChart.value.setOption({ |
| | | xAxis: { |
| | | type: 'category', |
| | | data: xAxis |
| | | }, |
| | | series: [ |
| | | { |
| | | data: yAxis |
| | | } |
| | | ] |
| | | }); |
| | | } |
| | | |
| | | |
| | | function updateNgrankData(xAxis, yAxis) { |
| | | ngChart.value.setOption({ |
| | | yAxis: { |
| | | type: 'category', |
| | | data: xAxis, |
| | | inverse: true, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: yAxis |
| | | } |
| | | ] |
| | | }); |
| | | } |
| | | |
| | | onMounted(() => { |
| | | queryWarnBatchList(); |
| | | initHealthChart(); |
| | | initBatchRateChart(); |
| | | initNgChart(); |
| | | getHealth(); |
| | | getBatch(); |
| | | getPwbatch(); |
| | | getNgrank(); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .home { |
| | | blockquote { |
| | | padding: 10px 20px; |
| | | margin: 0 0 20px; |
| | | font-size: 17.5px; |
| | | border-left: 5px solid #eee; |
| | | } |
| | | hr { |
| | | margin-top: 20px; |
| | | margin-bottom: 20px; |
| | | border: 0; |
| | | border-top: 1px solid #eee; |
| | | } |
| | | .col-item { |
| | | margin-bottom: 20px; |
| | | } |
| | | .h-500 { |
| | | height: 500px; |
| | | } |
| | | |
| | | ul { |
| | | padding: 0; |
| | | margin: 0; |
| | | .card-header { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | font-weight: bold; |
| | | } |
| | | |
| | | .grid { |
| | | display: grid; |
| | | } |
| | | |
| | | .grid-cols-1 { |
| | | grid-template-columns: repeat(1, minmax(0, 1fr)); |
| | | } |
| | | |
| | | .grid-cols-2 { |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | } |
| | | |
| | | .gap-4 { |
| | | gap: 1rem; |
| | | } |
| | | |
| | | .mb-6 { |
| | | margin-bottom: 1.5rem; |
| | | } |
| | | |
| | | .p-5 { |
| | | padding: 1.25rem; |
| | | } |
| | | |
| | | .w-full { |
| | | width: 100%; |
| | | } |
| | | |
| | | .h-400 { |
| | | height: 400px; |
| | | } |
| | | |
| | | .h-420 { |
| | | height: 420px; |
| | | } |
| | | |
| | | .bg-blue-50 { |
| | | background-color: #eff6ff; |
| | | } |
| | | |
| | | .bg-yellow-50 { |
| | | background-color: #fffbeb; |
| | | } |
| | | |
| | | .bg-red-50 { |
| | | background-color: #fef2f2; |
| | | } |
| | | |
| | | .bg-green-50 { |
| | | background-color: #f0fdf4; |
| | | } |
| | | |
| | | .rounded { |
| | | border-radius: 0.25rem; |
| | | } |
| | | |
| | | .text-sm { |
| | | font-size:14px; |
| | | } |
| | | |
| | | .text-xl { |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .font-bold { |
| | | font-weight: 700; |
| | | } |
| | | |
| | | .text-gray-600 { |
| | | color: #4b5563; |
| | | } |
| | | |
| | | @media (min-width: 768px) { |
| | | .md\:grid-cols-2 { |
| | | grid-template-columns: repeat(2, minmax(0, 1fr)); |
| | | } |
| | | } |
| | | |
| | | font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; |
| | | font-size: 13px; |
| | | color: #676a6c; |
| | | overflow-x: hidden; |
| | | .statistic-footer { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | font-size: 12px; |
| | | color: var(--el-text-color-regular); |
| | | margin-top: 16px; |
| | | } |
| | | |
| | | ul { |
| | | list-style-type: none; |
| | | } |
| | | .statistic-footer .footer-item { |
| | | font-size: 14px; |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | h4 { |
| | | margin-top: 0px; |
| | | } |
| | | .statistic-footer .footer-item span:last-child { |
| | | display: inline-flex; |
| | | align-items: center; |
| | | margin-left: 4px; |
| | | } |
| | | |
| | | h2 { |
| | | margin-top: 10px; |
| | | font-size: 26px; |
| | | font-weight: 100; |
| | | } |
| | | .green { |
| | | color: var(--el-color-success); |
| | | } |
| | | |
| | | p { |
| | | margin-top: 10px; |
| | | |
| | | b { |
| | | font-weight: 700; |
| | | } |
| | | } |
| | | |
| | | .update-log { |
| | | ol { |
| | | display: block; |
| | | list-style-type: decimal; |
| | | margin-block-start: 1em; |
| | | margin-block-end: 1em; |
| | | margin-inline-start: 0; |
| | | margin-inline-end: 0; |
| | | padding-inline-start: 40px; |
| | | } |
| | | } |
| | | .red { |
| | | color: var(--el-color-error); |
| | | } |
| | | </style> |