Просмотр исходного кода

1.质量目标组件修改 2.添加成品率对比组件 3.添加产品一致性检验组件

luoxiao 1 неделя назад
Родитель
Сommit
2fd5886779

+ 3 - 2
src/store/modules/common.ts

@@ -27,11 +27,12 @@ export const useCommonStore = defineStore("commonStore", {
     WeiDianZCJGG: "WeiDianZiCheJianGongGao",
     // ====
     qualityTOP01: "InspectionTaskOfToday",
-    qualityTOP02: "ProductionPassThroughRate",
+    qualityTOP02: "DistributionOfUnqualifiedProductsMsg",
     qualityTOP03: "QualityAnomaly",
-    qualityBottom01: "DistributionOfUnqualifiedProductsMsg",
+    qualityBottom01: "SingleProductionQuantityCompletion",
     // qualityBottom02: "DefectDistributionDiagram",
     qualityBottom02: "QualityGoal",
+    qualityBottom03: "QualityConsistencyInspection",
 
     // ====
     workTOP01: "ManagementPersons",

+ 15 - 0
src/views/screens/configs/components.ts

@@ -23,6 +23,8 @@ import ShengChanJiHuaQingKuang from "@/views/screens/screen-components/ShengChan
 import ChengPinRuKuQingKuang from "@/views/screens/screen-components/ChengPinRuKuQingKuang.vue";
 import WeiDianZiCheJianGongGao from "@/views/screens/screen-components/WeiDianZiCheJianGongGao.vue";
 import DistributionOfUnqualifiedProductsMsg from "@/views/screens/screen-components/DistributionOfUnqualifiedProductsMsg.vue";
+import SingleProductionQuantityCompletion from "@/views/screens/screen-components/SingleProductionQuantityCompletion.vue";
+import QualityConsistencyInspection from "@/views/screens/screen-components/QualityConsistencyInspection.vue";
 
 export interface ScreenComponent {
   name: string;
@@ -104,12 +106,25 @@ export const componentsDicts: { [key: string]: ScreenComponent } = {
     key: "QualityAnomaly",
     description: "This component shows the quality anomaly.",
   },
+  SingleProductionQuantityCompletion: {
+    name: "单产品成品率完成情况",
+    component: SingleProductionQuantityCompletion,
+    key: "SingleProductionQuantityCompletion",
+    description:
+      "This component shows the single production quantity completion.",
+  },
   QualityGoal: {
     name: "质量目标",
     component: QualityGoal,
     key: "QualityGoal",
     description: "This component shows the quality goal.",
   },
+  QualityConsistencyInspection: {
+    name: "质量一致性检验",
+    component: QualityConsistencyInspection,
+    key: "QualityConsistencyInspection",
+    description: "This component shows the quality consistency inspection.",
+  },
   ProductionPassThroughRate: {
     name: "生产直通率",
     component: ProductionPassThroughRate,

+ 25 - 21
src/views/screens/qualityScreen.vue

@@ -11,19 +11,20 @@
             <component :is="qualityTOP02" moduleId="qualityTOP02" />
           </div> -->
           <dv-border-box-10 class="top-container-box top-right2">
-            <component :is="qualityTOP03" moduleId="qualityTOP03" />
+            <component :is="qualityTOP02" moduleId="qualityTOP02" />
           </dv-border-box-10>
         </div>
         <div class="vertical-space"></div>
         <div class="bottom-container">
-          <dv-border-box-10 class="bottom-container-box bottom-right">
+          <dv-border-box-10 class="bottom-container-box bottom-left">
             <component :is="qualityBottom01" moduleId="qualityBottom01" />
           </dv-border-box-10>
-          <div class="bottom-container2">
-            <dv-border-box-10 class="bottom-container-box bottom-right2">
+          <dv-border-box-10 class="bottom-container-box bottom-mid">
             <component :is="qualityBottom02" moduleId="qualityBottom02" />
-            </dv-border-box-10>
-          </div>
+          </dv-border-box-10>
+          <dv-border-box-10 class="bottom-container-box bottom-right">
+            <component :is="qualityBottom03" moduleId="qualityBottom03" />
+          </dv-border-box-10>
         </div>
       </div>
     </dv-full-screen-container>
@@ -46,15 +47,18 @@ const qualityTOP02 = computed(() => {
   return getComponetnByName(commonS.qualityTOP02);
 });
 
-const qualityTOP03 = computed(() => {
-  return getComponetnByName(commonS.qualityTOP03);
-});
+// const qualityTOP03 = computed(() => {
+//   return getComponetnByName(commonS.qualityTOP03);
+// });
 const qualityBottom01 = computed(() => {
   return getComponetnByName(commonS.qualityBottom01);
 });
 const qualityBottom02 = computed(() => {
   return getComponetnByName(commonS.qualityBottom02);
 });
+const qualityBottom03 = computed(() => {
+  return getComponetnByName(commonS.qualityBottom03);
+});
 </script>
 
 <style lang="scss" scoped>
@@ -111,23 +115,23 @@ const qualityBottom02 = computed(() => {
   }
 
   .bottom-left {
-    width: 35%;
+    width: 20%;
   }
 
-  .bottom-right {
-    width: 65%;
+  .bottom-mid {
+    width: 50%;
   }
 
-  .bottom-right2 {
-    width: 100%;
+  .bottom-right {
+    width: 30%;
   }
 }
 
-.bottom-container2 {
-  height: 100%;
-  width: 100%;
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
+// .bottom-container2 {
+//   height: 100%;
+//   width: 100%;
+//   display: flex;
+//   justify-content: center;
+//   align-items: center;
+// }
 </style>

+ 216 - 0
src/views/screens/screen-components/QualityConsistencyInspection.vue

@@ -0,0 +1,216 @@
+<template>
+  <div class="multi-product-ring-container">
+    <ScreenComHeader :module-id="moduleId" title="产品一致性检验" />
+    <div ref="chartRef" style="width: 100%; height: 100%"></div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from "echarts";
+import ScreenComHeader from "@/views/screens/configs/screenComHeader.vue";
+
+const props = defineProps({
+  moduleId: {
+    type: String,
+    required: true,
+  },
+});
+const chartRef = ref(null);
+let chartInstance = null;
+let timer = null;
+const currentProductIndex = ref(0);
+
+// 假数据结构(含ISO时间字符串)
+const mockData = [
+  {
+    productName: "智能控制器",
+    inspectionItems: [
+      {
+        name: "外观检查",
+        startTime: "2023-05-10T09:00:00Z",
+        endTime: "2023-05-10T09:25:00Z",
+        color: "#5470C6",
+      },
+      {
+        name: "功能测试",
+        startTime: "2023-05-10T09:25:00Z",
+        endTime: "2023-05-10T10:25:00Z",
+        color: "#91CC75",
+      },
+      {
+        name: "老化试验",
+        startTime: "2023-05-10T10:25:00Z",
+        endTime: "2023-05-10T12:25:00Z",
+        color: "#EE6666",
+      },
+    ],
+  },
+  {
+    productName: "温度传感器",
+    inspectionItems: [
+      {
+        name: "外观检查",
+        startTime: "2023-05-10T08:00:00Z",
+        endTime: "2023-05-10T08:30:00Z",
+        color: "#5470C6",
+      },
+      {
+        name: "精度校准",
+        startTime: "2023-05-10T08:30:00Z",
+        endTime: "2023-05-10T10:30:00Z",
+        color: "#73C0DE",
+      },
+    ],
+  },
+];
+
+// 计算时间差(分钟)
+const getDuration = (start, end) => {
+  return (new Date(end) - new Date(start)) / (1000 * 60);
+};
+
+// 获取当前检验阶段
+const getCurrentPhase = (product) => {
+  const now = new Date(); // 真实当前时间
+  return product.inspectionItems.findIndex((item) => {
+    return now >= new Date(item.startTime) && now < new Date(item.endTime);
+  });
+};
+
+// 初始化图表
+const initChart = () => {
+  if (!chartRef.value) return;
+
+  chartInstance = echarts.init(chartRef.value);
+  updateChart();
+  window.addEventListener("resize", handleResize);
+
+  // 3秒切换产品
+  timer = setInterval(() => {
+    currentProductIndex.value =
+      (currentProductIndex.value + 1) % mockData.length;
+    updateChart();
+  }, 3000);
+};
+
+// 更新图表
+const updateChart = () => {
+  if (!chartInstance || mockData.length === 0) return;
+
+  const currentProduct = mockData[currentProductIndex.value];
+  const currentPhaseIndex = getCurrentPhase(currentProduct);
+
+  // 预处理数据
+  const processedData = currentProduct.inspectionItems.map((item) => {
+    const duration = getDuration(item.startTime, item.endTime);
+    return {
+      ...item,
+      duration,
+      timeLabel: `${formatTime(item.startTime)}~${formatTime(item.endTime)}`,
+    };
+  });
+
+  const option = {
+    backgroundColor: "transparent",
+    title: [
+      {
+        text: `当前产品: ${currentProduct.productName}`,
+        left: "center",
+        top: 10,
+        textStyle: {
+          color: "#fff",
+          fontSize: 14,
+        },
+      },
+    ],
+    tooltip: {
+      trigger: "item",
+      formatter: (params) => {
+        return `
+          <div style="font-weight:bold;margin-bottom:5px">${params.name}</div>
+          <div style="margin:3px 0">时段: ${params.data.timeLabel}</div>
+          <div style="margin:3px 0">耗时: ${params.data.duration}分钟</div>
+        `;
+      },
+      backgroundColor: "rgba(0,0,0,0.8)",
+      borderColor: "#333",
+      textStyle: {
+        color: "#fff",
+        fontSize: 12,
+      },
+    },
+    series: [
+      {
+        name: "检验进度",
+        type: "pie",
+        radius: ["45%", "35%"],
+        center: ["50%", "50%"],
+        itemStyle: {
+          borderRadius: 6,
+          borderColor: "#0f1325",
+          borderWidth: 2,
+        },
+        label: {
+          show: true,
+          fontSize: 14,
+          color: "#fff",
+          weight: "bold",
+        },
+        labelLine: {
+          show: true,
+          length: 60,
+          smooth: 0.1, // 引导线轻微弯曲
+        },
+        data: processedData.map((item, index) => ({
+          name: item.name,
+          value: item.duration,
+          timeLabel: item.timeLabel,
+          duration: item.duration,
+          itemStyle: {
+            color: item.color,
+            shadowBlur: index === currentPhaseIndex ? 10 : 0,
+            shadowColor: item.color,
+          },
+        })),
+      },
+    ],
+  };
+
+  chartInstance.setOption(option, true);
+};
+
+// 时间格式化 (HH:mm)
+const formatTime = (isoString) => {
+  const date = new Date(isoString);
+  return date.toTimeString().slice(0, 5);
+};
+
+const handleResize = () => {
+  chartInstance?.resize();
+};
+
+onMounted(() => {
+  initChart();
+});
+
+onBeforeUnmount(() => {
+  if (timer) clearInterval(timer);
+  if (chartInstance) {
+    chartInstance.dispose();
+    window.removeEventListener("resize", handleResize);
+  }
+});
+</script>
+
+<style scoped>
+.multi-product-ring-container {
+  width: 100%;
+  height: 100%;
+  min-height: 450px; /* 增加高度适应新元素 */
+  position: relative;
+  background-color: transparent;
+  border-radius: 8px;
+  padding: 10px;
+  box-sizing: border-box;
+}
+</style>

+ 75 - 66
src/views/screens/screen-components/QualityGoal.vue

@@ -10,64 +10,17 @@ import * as echarts from "echarts";
 import ScreenComHeader from "@/views/screens/configs/screenComHeader.vue";
 import { qualityReport } from "@/api/screens";
 
-const moduleId = "quality-target";
+const props = defineProps({
+  moduleId: {
+    type: String,
+    required: true,
+  },
+});
 const chartRef = ref(null);
 let chartInstance = null;
 let timer = null;
 const currentIndex = ref(0);
 
-// 修改后的数据结构
-const chartDataList = [
-  {
-    title: "产品A工序成品率对比",
-    xAxis: ["工序1", "工序2", "工序3", "工序4"],
-    productTarget: 97, // 产品目标值(虚线)
-    productActual: 90, // 产品实际值(虚线)
-    series: [
-      {
-        name: "工序目标值",
-        data: [90, 96, 92, 88],
-        color: "#5470C6",
-        type: "line",
-        symbol: "diamond",
-        symbolSize: 10,
-      },
-      {
-        name: "工序实际值",
-        data: [80, 72, 90, 97],
-        color: "#91CC75",
-        type: "line",
-        symbol: "circle",
-        symbolSize: 8,
-      },
-    ],
-  },
-  {
-    title: "产品B工序成品率对比",
-    xAxis: ["工序1", "工序2", "工序3"],
-    productTarget: 97, // 产品目标值(虚线)
-    productActual: 90, // 产品实际值(虚线)
-    series: [
-      {
-        name: "工序目标值",
-        data: [90, 94, 92],
-        color: "#5470C6",
-        type: "line",
-        symbol: "diamond",
-        symbolSize: 10,
-      },
-      {
-        name: "工序实际值",
-        data: [94, 96, 97],
-        color: "#91CC75",
-        type: "line",
-        symbol: "circle",
-        symbolSize: 8,
-      },
-    ],
-  },
-];
-
 // 初始化图表
 const initChart = () => {
   if (!chartRef.value) return;
@@ -77,15 +30,71 @@ const initChart = () => {
   window.addEventListener("resize", handleResize);
 };
 
+// 数据转换函数
+const transformData = (rawData) => {
+  return rawData.map((item) => {
+    // 处理工序名称列表
+    const operationNames = item.operationNameList.filter(
+      (name) => name !== "成品率"
+    );
+
+    // 处理工序数值列表 - 每3个一组:[目标值, 实际值, 差值]
+    const operationNumbers = item.operationNumberList;
+    const targetValues = [];
+    const actualValues = [];
+
+    for (let i = 0; i < operationNumbers.length; i += 3) {
+      // 跳过成品率数据(通常是最后3个)
+      if (i / 3 >= operationNames.length) break;
+
+      targetValues.push(parseFloat(operationNumbers[i]));
+      actualValues.push(parseFloat(operationNumbers[i + 1]));
+    }
+
+    // 获取成品率数据(最后3个值)
+    const yieldIndex = operationNumbers.length - 3;
+    const productTarget = parseFloat(operationNumbers[yieldIndex]);
+    const productActual = parseFloat(operationNumbers[yieldIndex + 1]);
+
+    return {
+      title:
+        `${item.materialModel}工序成品率对比` +
+        (item.materialCategory ? `(${item.materialCategory})` : ""),
+      xAxis: operationNames,
+      productTarget: productTarget,
+      productActual: productActual,
+      series: [
+        {
+          name: "工序目标值",
+          data: targetValues,
+          color: "#5470C6",
+          type: "line",
+          symbol: "diamond",
+          symbolSize: 10,
+        },
+        {
+          name: "工序实际值",
+          data: actualValues,
+          color: "#91CC75",
+          type: "line",
+          symbol: "circle",
+          symbolSize: 8,
+        },
+      ],
+    };
+  });
+};
+
 // 更新图表数据
 const updateChart = () => {
-  if (!chartInstance) return;
+  if (!chartInstance || !chartData.value.length) return;
 
-  const currentData = chartDataList[currentIndex.value];
+  const currentData = chartData.value[currentIndex.value];
 
   // 计算工序差值
   const processDiff = currentData.series[0].data.map((target, index) => {
-    return currentData.series[1].data[index] - target;
+    const diff = currentData.series[1].data[index] - target;
+    return parseFloat(diff.toFixed(1));
   });
 
   const option = {
@@ -94,7 +103,7 @@ const updateChart = () => {
       text: currentData.title,
       left: "center",
       textStyle: {
-        fontSize: 16,
+        fontSize: 14,
         color: "#fff",
       },
     },
@@ -166,9 +175,9 @@ const updateChart = () => {
     },
     grid: {
       top: "10%",
-      right: "10%",
-      bottom: "10%", // 增加底部空间
-      left: "5%",
+      right: "12%",
+      bottom: "15%", // 增加底部空间
+      left: "8%",
     },
     xAxis: {
       type: "category",
@@ -192,8 +201,8 @@ const updateChart = () => {
         color: "#fff",
         padding: [0, 0, 0, 40],
       },
-      min: 70, // 固定最小值,确保产品线可见
-      max: 100,
+      min: 60, // 固定最小值,确保产品线可见
+      max: 120,
       axisLine: {
         lineStyle: {
           color: "#fff",
@@ -328,7 +337,7 @@ const updateChart = () => {
 };
 
 const nextChart = () => {
-  currentIndex.value = (currentIndex.value + 1) % chartDataList.length;
+  currentIndex.value = (currentIndex.value + 1) % chartData.value.length;
   updateChart();
   resetTimer();
 };
@@ -348,9 +357,9 @@ const handleResize = () => {
 
 const chartData = ref([]);
 
-const loadData = () => {
-  chartData.value = qualityReport();
-  console.log("Loading data...", chartData.value);
+const loadData = async () => {
+  const rowData = await qualityReport();
+  chartData.value = transformData(rowData.data);
 };
 
 onMounted(() => {

+ 264 - 0
src/views/screens/screen-components/SingleProductionQuantityCompletion.vue

@@ -0,0 +1,264 @@
+<template>
+  <div class="quality-target-bar-container">
+    <ScreenComHeader :module-id="moduleId" title="成品率对比" />
+    <div ref="chartRef" style="width: 100%; height: 100%"></div>
+  </div>
+</template>
+
+<script setup>
+import * as echarts from "echarts";
+import ScreenComHeader from "@/views/screens/configs/screenComHeader.vue";
+import { qualityReport } from "@/api/screens";
+
+const props = defineProps({
+  moduleId: {
+    type: String,
+    required: true,
+  },
+});
+const chartRef = ref(null);
+let chartInstance = null;
+let timer = null;
+const currentIndex = ref(0);
+const chartData = ref([]);
+
+// 初始化图表
+const initChart = () => {
+  if (!chartRef.value) return;
+
+  chartInstance = echarts.init(chartRef.value);
+  updateChart();
+  window.addEventListener("resize", handleResize);
+
+  // 启动轮播定时器
+  startAutoPlay();
+};
+
+// 启动自动轮播
+const startAutoPlay = () => {
+  if (timer) clearInterval(timer);
+  timer = setInterval(() => {
+    currentIndex.value = (currentIndex.value + 1) % chartData.value.length;
+    updateChart();
+  }, 3000); // 3秒切换一次
+};
+
+// 数据转换函数 - 只提取成品率数据
+const transformData = (rawData) => {
+  return rawData.map((item) => {
+    const operationNumbers = item.operationNumberList;
+    const yieldIndex = operationNumbers.length - 3;
+
+    return {
+      name:
+        item.materialModel +
+        (item.materialCategory ? `(${item.materialCategory})` : ""),
+      target: parseFloat(operationNumbers[yieldIndex]),
+      actual: parseFloat(operationNumbers[yieldIndex + 1]),
+      diff: parseFloat(operationNumbers[yieldIndex + 2]),
+    };
+  });
+};
+
+// 更新图表数据 - 只显示当前索引的产品
+const updateChart = () => {
+  if (!chartInstance || !chartData.value.length) return;
+
+  const currentData = chartData.value[currentIndex.value];
+
+  const option = {
+    backgroundColor: "transparent",
+    title: {
+      text: currentData.name,
+      left: "center",
+      textStyle: {
+        fontSize: 14,
+        color: "#fff",
+      },
+    },
+    tooltip: {
+      trigger: "axis",
+      axisPointer: {
+        type: "shadow",
+      },
+      backgroundColor: "rgba(0,0,0,0.7)",
+      textStyle: {
+        color: "#fff",
+      },
+      formatter: () => {
+        const diffColor = currentData.diff >= 0 ? "#91CC75" : "#EE6666";
+        const diffSymbol = currentData.diff >= 0 ? "+" : "";
+
+        return `
+          <div style="font-weight:bold;margin-bottom:5px">${currentData.name}</div>
+          <div style="display:flex;align-items:center;margin:3px 0">
+            <span style="display:inline-block;width:10px;height:10px;background:#5470C6;margin-right:5px"></span>
+            <span style="flex:1">目标成品率:</span>
+            <span style="font-weight:bold">${currentData.target}%</span>
+          </div>
+          <div style="display:flex;align-items:center;margin:3px 0">
+            <span style="display:inline-block;width:10px;height:10px;background:#91CC75;margin-right:5px"></span>
+            <span style="flex:1">实际成品率:</span>
+            <span style="font-weight:bold">${currentData.actual}%</span>
+          </div>
+          <div style="display:flex;align-items:center;margin:3px 0">
+            <span style="display:inline-block;width:10px;height:10px;background:${diffColor};margin-right:5px"></span>
+            <span style="flex:1">差值:</span>
+            <span style="font-weight:bold;color:${diffColor}">${diffSymbol}${currentData.diff}%</span>
+          </div>
+        `;
+      },
+    },
+    legend: {
+      data: ["目标成品率", "实际成品率"],
+      bottom: 35,
+      textStyle: {
+        color: "#fff",
+      },
+      itemWidth: 20,
+      itemHeight: 10,
+    },
+    grid: {
+      top: "10%",
+      right: "10%",
+      bottom: "15%",
+      left: "10%",
+      containLabel: true,
+    },
+    xAxis: {
+      type: "category",
+      data: ["产品成品率对比"],
+      axisLine: {
+        lineStyle: {
+          color: "#fff",
+        },
+      },
+      axisLabel: {
+        color: "#fff",
+      },
+    },
+    yAxis: {
+      type: "value",
+      name: "成品率(%)",
+      nameTextStyle: {
+        color: "#fff",
+        padding: [0, 0, 0, 40],
+      },
+      min: 0,
+      max: 100,
+      axisLine: {
+        lineStyle: {
+          color: "#fff",
+        },
+      },
+      axisLabel: {
+        color: "#fff",
+        formatter: "{value}%",
+      },
+      splitLine: {
+        lineStyle: {
+          color: "rgba(255,255,255,0.1)",
+        },
+      },
+    },
+    series: [
+      {
+        name: "目标成品率",
+        type: "bar",
+        barWidth: 40,
+        data: [
+          {
+            value: currentData.target,
+            itemStyle: {
+              color: "#5470C6",
+            },
+          },
+        ],
+        label: {
+          show: true,
+          position: "top",
+          formatter: "{c}%",
+          color: "#fff",
+        },
+      },
+      {
+        name: "实际成品率",
+        type: "bar",
+        barWidth: 40,
+        data: [
+          {
+            value: currentData.actual,
+            itemStyle: {
+              color:
+                currentData.actual >= currentData.target
+                  ? "#91CC75"
+                  : "#EE6666",
+            },
+          },
+        ],
+        label: {
+          show: true,
+          position: "top",
+          formatter: (params) => {
+            const diff = currentData.diff;
+            const diffColor = diff >= 0 ? "#91CC75" : "#EE6666";
+            const diffSymbol = diff >= 0 ? "+" : "";
+            return `${params.value}% {diff|${diffSymbol}${diff}%}`;
+          },
+          color: "#fff",
+          rich: {
+            diff: {
+              color: "#EE6666",
+              padding: [0, 0, 0, 5],
+            },
+          },
+        },
+      },
+    ],
+    animationDuration: 500,
+  };
+
+  chartInstance.setOption(option, true);
+};
+
+const handleResize = () => {
+  chartInstance?.resize();
+};
+
+const loadData = async () => {
+  try {
+    const rowData = await qualityReport();
+    chartData.value = transformData(rowData.data);
+    if (chartData.value.length > 0) {
+      initChart();
+    }
+  } catch (error) {
+    console.error("加载数据失败:", error);
+  }
+};
+
+onMounted(() => {
+  loadData();
+});
+
+onBeforeUnmount(() => {
+  if (chartInstance) {
+    chartInstance.dispose();
+    window.removeEventListener("resize", handleResize);
+  }
+  if (timer) clearInterval(timer);
+});
+</script>
+
+<style scoped>
+.quality-target-bar-container {
+  width: 100%;
+  height: 100%;
+  min-height: 300px;
+  position: relative;
+  background-color: transparent;
+  border-radius: 8px;
+  padding: 10px;
+  box-sizing: border-box;
+}
+</style>