import React, { useRef, useEffect, useState, useCallback, useMemo, } from "react"; import { withRouter } from "next/router"; import moment from "moment"; // import { Liquid, DualAxes } from "@ant-design/plots"; import _ from "lodash"; import Chart from "../../../components/screen/Chart"; import * as echarts from "echarts"; import Footer from "../../../components/screen/Footer"; import Header from "../../../components/screen/Header"; import Row from "../../../components/screen/Row"; import Card from "../../../components/screen/Card"; import Table from "../../../components/screen/Table"; import TableProgress from "../../../components/screen/TableProgress"; import io from "../../../utils/socket.io.js"; const gutter = 20; const bottom = 20; const cardBg = "rgba(6, 36, 109, 0.2)"; const socketEmit = "timePerformanceGoodProduct"; const socketScreenType = "QualityDashBoardType"; // 调试方式: local 本地完整数据调试, server 接口调试, dev 开发期调试 const dataType = "server"; const getQueryVariable = (variable) => { let query = window.location.search.substring(1); let vars = query.split("&"); for (let i = 0; i < vars.length; i++) { let pair = vars[i].split("="); if (pair[0] == variable) { return pair[1]; } } return false; }; const useSocket = ({ socketEmit, socketScreenType, dataType }) => { const { config: { ioSocketUrl } = {} } = global; const [pageData, setPageData] = useState({}); const socket = useRef(); const update = useCallback((data) => { if (dataType === "local") data = localData; console.log("data", data); const { todayQuality } = data; // 今日合格率 let todayPassRate = todayQuality.qualityRate; // 今日是否合格 let todayIsPass = todayPassRate >= 0.7; // 本月不良率TOP5 const { monthDefectRateTop5 = {} } = data; let monthUnPass = []; Object.keys(monthDefectRateTop5).forEach((name) => { monthUnPass.push({ name, value: monthDefectRateTop5[name] }); }); // 合格率趋势 const { qualityTrend = [] } = data; let passTrend = { category: [], data: [] }; try { let category = []; let proData = [[], []]; qualityTrend.forEach((item, dateIndex) => { category.unshift(item.date); ["zhhgl", "ztl"].forEach((type, typeIndex) => { Object.keys(item[type]).forEach((proName) => { let value = item[type][proName]; if (!proData[typeIndex].find((i) => i.name === proName)) { proData[typeIndex].push({ name: proName, data: [] }); } let proIndex = proData[typeIndex].findIndex( (i) => i.name === proName ); proData[typeIndex][proIndex].data[ -1 * dateIndex + qualityTrend.length - 1 ] = value; }); }); }); // 折线图 所有值为0,则消失(过滤这条产品数据) proData[1] = proData[1].filter(({ data }) => { let total = 0; data.forEach(val => total += val); return total > 0; }) let typeData = [ { key: "zhhgl", name: "综合合格率", data: proData[0], }, { key: "ztl", name: "一次性合格率", data: proData[1], }, ]; passTrend = { category, data: typeData }; } catch (e) { console.log("qualityTrend error", e, qualityTrend); } // 近三天不良率 const { threeDayReject = [] } = data; let unPassRate = threeDayReject; // 任务提醒 const { taskRemind = [] } = data; let taskReminder = taskRemind; setPageData({ todayPassRate, todayIsPass, monthUnPass, passTrend, unPassRate, taskReminder, }); }, []); const getData = useCallback(() => { let workCenterCode = getQueryVariable("workCenterCode"); let date = getQueryVariable("date"); if (!workCenterCode) return; let query = { workCenterCode, screenType: socketScreenType, }; if (date) query.date = date; console.log("fetch", socketEmit, { query }); if (dataType === "server") { socket.current.emit(socketEmit, "channel", { query }); } else if (dataType === "local") { update(); } }, [update]); useEffect(() => { if (dataType === "dev") setPageData(devData); if (dataType === "local") getData(); if (dataType === "server") { socket.current = io.connect(ioSocketUrl, {transports:['websocket']}); socket.current.on("connect", function () { console.log("msg页面连接成功!"); getData(); }); socket.current.on(socketEmit, function (res) { try { res = JSON.parse(res); } catch (e) { console.log(e); } update(res.data); }); } }, [getData, update]); return [pageData, getData]; }; function QualityBoard(props) { const { config: { qualityBoard = {} } = {} } = global; const { threeDayRejectTableNext = 4000, taskRemindTableNext = 4000} = qualityBoard; const [pageData, getPageData] = useSocket({ socketEmit, socketScreenType, dataType, }); // 今日合格率 const todayPassRateOpt = useMemo( () => chartConfig("todayPassRateConfig").init(pageData), [pageData] ); // 今日是否合格 const todayIsPass = useMemo(() => !!pageData.todayIsPass, [pageData]); // 本月不良率TOP5 const monthUnPassOpt = useMemo( () => chartConfig("monthUnPassConfig").init(pageData), [pageData] ); // 合格率趋势 const passTrend = useMemo( () => chartConfig("passTrendConfig").init(pageData), [pageData] ); // 近三天不良率 const unPassRateTableRef = useRef(); const unPassRateTableColumns = useMemo( () => [ { title: "日期", code: "date", render: (value) => (value && moment(value).format("MM-DD")) || "", }, { title: "产品名称", code: "materialName" }, { title: "产品等级", code: "productLevel" }, { title: "生产批次", code: "scpc" }, { title: "产量", code: "cl" }, { title: "检验批次", code: "jypc" }, { title: "不良批次", code: "blpc" }, { title: "不良率", width: 176, code: "bll", render: (value) => { if (isNaN(value)) return value; if (parseFloat(value) === 0) return value; return }, }, ], [] ); useEffect(() => { const { unPassRate = [] } = pageData; unPassRateTableRef.current.setData(unPassRate); let getNextPage = () => setTimeout(() => { unPassRateTableRef.current.nextPage(); timmer = getNextPage(); }, threeDayRejectTableNext); let timmer = getNextPage(); return () => { clearTimeout(timmer); }; }, [pageData]); // 任务提醒 const taskReminderTableRef = useRef(); // 8小时内含8小时数值显示绿色,8小时以上数值显示橙色,24小时以上数值显示红色。 const getColor = useCallback((ms = 0) => { if (ms > 24 * 60 * 60) return "#FF005A"; if (ms > 8 * 60 * 60) return "#F5A623"; if (ms <= 8 * 60 * 60) return "#58BC17"; }, []); const billstatusStyle = useCallback( (ms) => { return { fontSize: "16px", color: "#fff", lineHeight: "28px", padding: "0 8px", display: "inline-block", background: getColor(ms), }; }, [getColor] ); const taskReminderTableColumns = useMemo( () => [ { title: "任务状态", code: "billstatus", render: (value, config, rowData) => ( {value} ), }, { title: "任务名称", code: "templatename" }, { title: "产品名称", code: "materialname" }, { title: "产品等级", code: "materialdesc" }, { title: "批次号", code: "workordercode" }, { title: "工序", code: "processname" }, { title: "任务产生时间", code: "createtime", render: (value) => (value && moment(value).format("YYYY-MM-DD HH:mm")) || "", }, { title: "持续时长", code: "cxsc", render: (value, config, rowData) => ( {value} ), }, ], [billstatusStyle, getColor] ); useEffect(() => { const { taskReminder = [] } = pageData; taskReminderTableRef.current.setData(taskReminder); let getNextPage = () => setTimeout(() => { taskReminderTableRef.current.nextPage(); timmer = getNextPage(); }, taskRemindTableNext); let timmer = getNextPage(); return () => { clearTimeout(timmer); }; }, [pageData]); return (
检验合格率
{todayIsPass ? "运行正常" : "运行异常"}
); } const devData = { // 今日合格率 todayPassRate: 0.855555, // 今日是否合格 todayIsPass: true, // 本月不良率TOP5 monthUnPass: [ { value: 0.0909, name: "硫含量" }, { value: 0.3564, name: "DIO" }, { value: 0.1203, name: "粉体电阻率" }, { value: 0.1253, name: "包装袋" }, { value: 0.2005, name: "振实密度" }, ], // 合格率趋势 passTrend: { category: ["08-01", "08-02", "08-03", "08-04", "08-05", "08-06", "08-07"], data: [ { name: "综合合格率", data: [ { name: "E60", data: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], }, { name: "E70", data: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], }, { name: "E80", data: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], }, ], }, { name: "一次性合格率", data: [ { name: "E60", data: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], }, { name: "E70", data: [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2], }, { name: "E80", data: [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3], }, ], }, ], }, // 近三天不良率 unPassRate: [ { gid: "1", date: "2022-08-06", //日期 materialName: "磷酸铁锂", //产品名称 productLevel: "E80", //产品等级 scpc: 2, //生产批次 cl: 2, //产量 jypc: 2, //检验批次 blpc: 0, //不良批次 bll: "0.123", //不良率 }, { gid: "2", date: "2022-08-06", //日期 materialName: "磷酸铁锂", //产品名称 productLevel: "E80", //产品等级 scpc: 2, //生产批次 cl: 2, //产量 jypc: 2, //检验批次 blpc: 0, //不良批次 bll: "无", //不良率 }, { gid: "3", date: "2022-08-06", //日期 materialName: "磷酸铁锂", //产品名称 productLevel: "E80", //产品等级 scpc: 2, //生产批次 cl: 2, //产量 jypc: 2, //检验批次 blpc: 0, //不良批次 bll: "1", //不良率 }, { gid: "4", date: "2022-08-06", //日期 materialName: "磷酸铁锂", //产品名称 productLevel: "E80", //产品等级 scpc: 2, //生产批次 cl: 2, //产量 jypc: 2, //检验批次 blpc: 0, //不良批次 bll: "1.000", //不良率 }, { gid: "5", date: "2022-08-06", //日期 materialName: "磷酸铁锂", //产品名称 productLevel: "E80", //产品等级 scpc: 2, //生产批次 cl: 2, //产量 jypc: 2, //检验批次 blpc: 0, //不良批次 bll: "0", //不良率 }, ], // 任务提醒 taskReminder: [ { gid: "1", billstatus: "待检验", //任务状态 templatename: "测试工单批次生成质检任务", //任务名称 materialname: "L5车间成品", //产品名称 materialdesc: "E80A", //产品等级 workordercode: "HSR5A22082201", //批次号 processname: "粗磨", //工序 createtime: "2022-08-01 00:00:00", //任务产生时间 cxsc: "50分钟", //持续时长 ms: 1, }, { gid: "2", billstatus: "待检验", //任务状态 templatename: "测试工单批次生成质检任务", //任务名称 materialname: "L5车间成品", //产品名称 materialdesc: "E80A", //产品等级 workordercode: "HSR5A22082201", //批次号 processname: "粗磨", //工序 createtime: "2022-08-01 00:00:00", //任务产生时间 cxsc: "50分钟", //持续时长 ms: 1000000, }, ], }; const localData = { todayQuality: { qualityRate: 0.231, }, monthDefectRateTop5: { PH值: 0.3333, 粒度: 0.3333, 感应强度: 0.3333, 包装袋: 0.1203, 振实密度: 0.2005, }, qualityTrend: [ { date: "08-22", ztl: { E60: 0.42, E70: 0.32, E80: 0.22, }, zhhgl: { E60: 0.12, E70: 0.12, E80: 0.12, }, }, { date: "08-21", ztl: { E60: 0.42, E70: 0.32, E80: 0.22, }, zhhgl: { E60: 0.12, E70: 0.12, E80: 0.12, }, }, { date: "08-20", ztl: { E60: 0.42, E70: 0.32, E80: 0.22, }, zhhgl: { E60: 0.12, E70: 0.12, E80: 0.12, }, }, { date: "08-19", ztl: { E60: 0.42, E70: 0.32, E80: 0.22, }, zhhgl: { E60: 0.12, E70: 0.12, E80: 0.12, }, }, { date: "08-18", ztl: { E60: 0.42, E70: 0.32, E80: 0.22, }, zhhgl: { E60: 0.12, E70: 0.12, E80: 0.12, }, }, { date: "08-17", ztl: { E60: 0.42, E70: 0.32, E80: 0.22, }, zhhgl: { E60: 0.12, E70: 0.12, E80: 0.12, }, }, { date: "08-16", ztl: { E60: 0.42, E70: 0.32, E80: 0.22, }, zhhgl: { E60: 0.12, E70: 0.12, E80: 0.12, }, }, ], threeDayReject: [ { gid: "1", date: "2022-08-06", //日期 materialName: "磷酸铁锂", //产品名称 productLevel: "E80", //产品等级 scpc: 2, //生产批次 cl: 2, //产量 jypc: 2, //检验批次 blpc: 0, //不良批次 bll: "0.123123123123", //不良率 }, { gid: "2", date: "2022-08-06", //日期 materialName: "磷酸铁锂", //产品名称 productLevel: "E80", //产品等级 scpc: 2, //生产批次 cl: 2, //产量 jypc: 2, //检验批次 blpc: 0, //不良批次 bll: "无", //不良率 }, { gid: "3" }, { gid: "4" }, { gid: "5" }, { gid: "6" }, { gid: "7" }, { gid: "8" }, { gid: "9" }, ], taskRemind: [ { gid: "1", billstatus: "待检验", //任务状态 templatename: "测试工单批次生成质检任务", //任务名称 materialname: "L5车间成品", //产品名称 materialdesc: "E80A", //产品等级 workordercode: "HSR5A22082201", //批次号 processname: "粗磨", //工序 createtime: "2022-08-01 00:00:00", //任务产生时间 cxsc: "50分钟", //持续时长 ms: 1, }, { gid: "2", billstatus: "待检验", //任务状态 templatename: "测试工单批次生成质检任务", //任务名称 materialname: "L5车间成品", //产品名称 materialdesc: "E80A", //产品等级 workordercode: "HSR5A22082201", //批次号 processname: "粗磨", //工序 createtime: "2022-08-01 00:00:00", //任务产生时间 cxsc: "50分钟", //持续时长 ms: 1000000, }, { gid: "3", billstatus: "待检验", //任务状态 templatename: "测试工单批次生成质检任务", //任务名称 materialname: "L5车间成品", //产品名称 materialdesc: "E80A", //产品等级 workordercode: "HSR5A22082201", //批次号 processname: "粗磨", //工序 createtime: "2022-08-01 00:00:00", //任务产生时间 cxsc: "50分钟", //持续时长 ms: 30000, }, { gid: "4" }, { gid: "5" }, { gid: "6" }, { gid: "7" }, { gid: "8" }, { gid: "9" }, ], }; // 今日合格率 const todayPassRateConfig = { series: { type: "liquidFill", center: ["50%", "50%"], radius: "75%", data: [ { value: 0.855, itemStyle: { opacity: 0.3, }, }, 0.855, ], itemStyle: {}, label: { position: ["50%", "30%"], formatter: (opt) => `${parseFloat((opt.value * 100).toFixed(1))}%`, fontSize: 28, }, backgroundStyle: { color: "none", }, outline: { show: true, borderDistance: 2, itemStyle: { color: "none", borderColor: "#f6202f", borderWidth: 2, }, }, }, }; let todayPassRateColors = { red: { fill: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: "hsl(356deg 92% 65%)", }, { offset: 1, color: "hsl(356deg 92% 25%)", }, ]), color: "#f6202f", }, green: { fill: "#92D050", color: "#92D050", }, orange: { fill: "#FFC000", color: "#FFC000", }, }; todayPassRateConfig.init = (pageData) => { let { todayPassRate = 0, todayIsPass } = pageData; let opt = _.cloneDeep(todayPassRateConfig); opt.series.data[0].value = todayPassRate; opt.series.data[1] = todayPassRate; let { orange, green } = todayPassRateColors; let { fill, color } = todayIsPass ? green : orange; opt.series.itemStyle.color = fill; opt.series.label.color = color; opt.series.outline.itemStyle.borderColor = color; return opt; }; // 本月不良率TOP5 const monthUnPassConfig = { color: ["#4D7BF3", "#4FCCFF", "#6EE420", "#FFC760", "#FF005A"], legend: { orient: "vertical", left: 10, top: "middle", itemGap: 16, textStyle: { rich: { 0: { opacity: 0.75 }, 1: { opacity: 1 }, }, }, }, series: { type: "pie", radius: ["40%", "70%"], center: ["65%", "45%"], label: { show: true, textBorderColor: "transparent", rich: { 0: { color: "#4D7BF3" }, 1: { color: "#4FCCFF" }, 2: { color: "#6EE420" }, 3: { color: "#FFC760" }, 4: { color: "#FF005A" }, }, }, labelLine: { show: true, }, data: [], }, }; monthUnPassConfig.init = (pageData) => { let { monthUnPass } = pageData; if (!monthUnPass) return; let opt = _.cloneDeep(monthUnPassConfig); opt.series.data = monthUnPass; opt.legend.formatter = (name) => { let { value } = monthUnPass.find((i) => i.name === name); return `{0|${name}:}{1|${(value * 100).toFixed(2)}%}`; }; opt.series.label.formatter = (data) => { let { name, dataIndex } = data; return `{${dataIndex}|${name}}`; }; return opt; }; // 合格率趋势 const passTrendConfig = { grid: { bottom: 40, top: 80, left: 70, right: 70, }, legend: { right: 20, top: 20, }, barMaxWidth: 30, xAxis: { type: "category", data: [], axisTick: { show: true, alignWithLabel: true, }, axisLine: { lineStyle: { color: "rgba(255,255,255,0.65)", }, }, axisLabel: { color: "rgba(255,255,255,0.85)", }, }, yAxis: [ { type: "value", max: 1, axisTick: { show: true, alignWithLabel: true, }, axisLine: { lineStyle: { color: "rgba(255,255,255,0.65)", }, }, axisLabel: { color: "rgba(255,255,255,0.85)", formatter: (value) => (value * 100).toFixed(0) + "%", }, }, { type: "value", max: 1, axisTick: { show: true, alignWithLabel: true, }, axisLine: { lineStyle: { color: "rgba(255,255,255,0.65)", }, }, axisLabel: { color: "rgba(255,255,255,0.85)", formatter: (value) => (value * 100).toFixed(0) + "%", }, }, ], series: [], }; const colors = [[new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 1, color: 'rgb(249, 203, 173)' }, { offset: 0.26, color: 'rgb(183, 210, 236)' }, { offset: 0.16, color: 'rgb(183, 210, 236)' }, { offset: 0, color: 'rgb(208, 225, 242)' }]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 1, color: 'rgb(225, 193, 0)' }, { offset: 0.63, color: 'rgb(217, 199, 117)' }, { offset: 0.26, color: 'rgb(183, 210, 236)' }, { offset: 0.17, color: 'rgb(183, 210, 236)' }, { offset: 0, color: 'rgb(208, 225, 242)' }]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 1, color: 'rgb(169, 209, 142)' }, { offset: 0.26, color: 'rgb(183, 210, 236)' }, { offset: 0.16, color: 'rgb(183, 210, 236)' }, { offset: 0, color: 'rgb(208, 225, 242)' }]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 1, color: 'rgb(0, 176, 240)' }, { offset: 0.26, color: 'rgb(183, 210, 236)' }, { offset: 0.16, color: 'rgb(183, 210, 236)' }, { offset: 0, color: 'rgb(208, 225, 242)' }]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 1, color: 'rgb(191, 144, 0)' }, { offset: 0.26, color: 'rgb(183, 210, 236)' }, { offset: 0.16, color: 'rgb(183, 210, 236)' }, { offset: 0, color: 'rgb(208, 225, 242)' }]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 1, color: 'rgb(237, 125, 49)' }, { offset: 0.26, color: 'rgb(183, 210, 236)' }, { offset: 0.16, color: 'rgb(183, 210, 236)' }, { offset: 0, color: 'rgb(208, 225, 242)' }]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 1, color: 'rgb(255, 153, 153)' }, { offset: 0.26, color: 'rgb(183, 210, 236)' }, { offset: 0.16, color: 'rgb(183, 210, 236)' }, { offset: 0, color: 'rgb(208, 225, 242)' }]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 1, color: 'rgb(51, 102, 255)' }, { offset: 0.26, color: 'rgb(183, 210, 236)' }, { offset: 0.16, color: 'rgb(183, 210, 236)' }, { offset: 0, color: 'rgb(208, 225, 242)' }]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 1, color: 'rgb(0, 255, 153)' }, { offset: 0.26, color: 'rgb(183, 210, 236)' }, { offset: 0.16, color: 'rgb(183, 210, 236)' }, { offset: 0, color: 'rgb(208, 225, 242)' }]), new echarts.graphic.LinearGradient(0, 1, 0, 0, [{ offset: 1, color: 'rgb(255, 51, 253)' }, { offset: 0.26, color: 'rgb(183, 210, 236)' }, { offset: 0.16, color: 'rgb(183, 210, 236)' }, { offset: 0, color: 'rgb(208, 225, 242)' }])], [ "#3366FF", "#00CCFF", "#FF6699", "#8497B0", "#C9C9C9", "#FFD966","#8FAADC","#ED7D31","#FFFF00","#92D050" ], ] passTrendConfig.init = (pageData) => { let { passTrend } = pageData; if (!passTrend) return; let opt = _.cloneDeep(passTrendConfig); let { category, data } = passTrend; opt.xAxis.data = category; opt.series = []; data.forEach((item, yAxisIndex) => { let type = yAxisIndex === 0 ? "bar" : "line"; let { name: typeName } = item; item.data.forEach(({ name: proName, data }, index) => { let name = `${proName} ${typeName}`; opt.series.push({ name, yAxisIndex, type, data, color: colors[yAxisIndex][index] }); }); }); return opt; }; let chartConfig = (() => { let chartOpt = { todayPassRateConfig, monthUnPassConfig, passTrendConfig, }; let getOpt = (type) => chartOpt[type]; return getOpt; })(); export default withRouter(QualityBoard);