From 3fc5f85600e51d82a353e0f02164866ced8f49aa Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期四, 02 四月 2026 15:37:33 +0800
Subject: [PATCH] Merge branch 'dev_银川_中盛建材' of http://114.132.189.42:9002/r/product-inventory-management into dev_银川_中盛建材

---
 src/views/reportAnalysis/productionStatistics/index.vue |  437 +++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 380 insertions(+), 57 deletions(-)

diff --git a/src/views/reportAnalysis/productionStatistics/index.vue b/src/views/reportAnalysis/productionStatistics/index.vue
index 34a7b8d..47822ce 100644
--- a/src/views/reportAnalysis/productionStatistics/index.vue
+++ b/src/views/reportAnalysis/productionStatistics/index.vue
@@ -61,7 +61,8 @@
                   :class="{ active: blockSelectedMaterial === name }"
                   @click="selectBlockMaterial(name)">{{ name }}</span>
           </div>
-          <div class="material-info-card">
+          <div v-if="blockSelectedMaterial !== '鍏ㄩ儴'"
+               class="material-info-card">
             <div class="material-icon">
               <svg width="24"
                    height="24"
@@ -168,7 +169,8 @@
                   :class="{ active: boardSelectedMaterial === name }"
                   @click="selectBoardMaterial(name)">{{ name }}</span>
           </div>
-          <div class="material-info-card">
+          <div v-if="boardSelectedMaterial !== '鍏ㄩ儴'"
+               class="material-info-card">
             <div class="material-icon">
               <svg width="24"
                    height="24"
@@ -341,19 +343,26 @@
     yearlyConsumption: "--",
   });
 
+  /** xMode: time=妯酱涓烘椂闂达紱material=閫夈�屽叏閮ㄣ�嶆椂妯酱涓哄悇鐗╂枡鍚嶇О */
   const blockCostChartSeries = ref({
+    xMode: "time",
     categories: [],
     input: [],
     output: [],
   });
   const boardCostChartSeries = ref({
+    xMode: "time",
     categories: [],
     input: [],
     output: [],
   });
   const productionChartSeries = ref({
     categories: [],
+    /** single锛氬崟绾匡紱dual锛氬叏閮ㄦ椂鐮屽潡+鏉挎潗鍙岀嚎 */
+    mode: "single",
     values: [],
+    blockValues: [],
+    plateValues: [],
   });
 
   // 鍥哄簾澶勭悊閲忔姌绾垮浘锛�/home/productionStatistics/solidWaste锛�
@@ -391,9 +400,12 @@
   let productionChartInstance = null;
   let customerTrendChartInstance = null;
   let salesRankingChartInstance = null;
+  let blockCostChartResizeObserver = null;
+  let boardCostChartResizeObserver = null;
 
   // 鐢熶骇鍗曡�楀浘琛ㄩ厤缃紙鐮屽潡锛屾帴鍙f暟鎹級
   const blockCostChartOption = computed(() => {
+    const xMode = blockCostChartSeries.value.xMode || "time";
     const periods = blockCostChartSeries.value.categories || [];
     const inputData = blockCostChartSeries.value.input || [];
     const outputData = blockCostChartSeries.value.output || [];
@@ -437,8 +449,8 @@
       grid: {
         left: "1%",
         right: "1%",
-        bottom: "1%",
         top: "18%",
+        bottom: xMode === "material" ? "0%" : "1%",
         containLabel: true,
       },
       xAxis: {
@@ -449,7 +461,12 @@
         axisLabel: {
           color: "#B8C8E0",
           fontSize: getResponsiveValue(11),
-          margin: getResponsiveValue(10),
+          margin:
+            xMode === "material"
+              ? getResponsiveValue(6)
+              : getResponsiveValue(10),
+          rotate: xMode === "material" && periods.length > 5 ? 28 : 0,
+          interval: 0,
         },
         splitLine: { show: false },
       },
@@ -469,6 +486,7 @@
 
   // 鏉挎潗鍗曡�楀浘琛ㄩ厤缃紙鎺ュ彛鏁版嵁锛屼笌鐮屽潡缁撴瀯涓�鑷达級
   const boardCostChartOption = computed(() => {
+    const xMode = boardCostChartSeries.value.xMode || "time";
     const periods = boardCostChartSeries.value.categories || [];
     const inputData = boardCostChartSeries.value.input || [];
     const outputData = boardCostChartSeries.value.output || [];
@@ -512,8 +530,8 @@
       grid: {
         left: "1%",
         right: "1%",
-        bottom: "1%",
         top: "18%",
+        bottom: xMode === "material" ? "0%" : "1%",
         containLabel: true,
       },
       xAxis: {
@@ -524,7 +542,12 @@
         axisLabel: {
           color: "#B8C8E0",
           fontSize: getResponsiveValue(11),
-          margin: getResponsiveValue(10),
+          margin:
+            xMode === "material"
+              ? getResponsiveValue(6)
+              : getResponsiveValue(10),
+          rotate: xMode === "material" && periods.length > 5 ? 28 : 0,
+          interval: 0,
         },
         splitLine: { show: false },
       },
@@ -545,26 +568,51 @@
   // 鐗╂枡鐢熶骇閲忓垎鏋愶紙鎺ュ彛鏁版嵁锛�
   const productionChartOption = computed(() => {
     const periods = productionChartSeries.value.categories || [];
-    const values = productionChartSeries.value.values || [];
-    const lineColor = "#4A8BFF";
-    const seriesName = "浜ч噺";
-    const series = [
-      {
-        name: seriesName,
-        data: values,
-        type: "line",
-        smooth: true,
-        lineStyle: { width: getResponsiveValue(2), color: lineColor },
-        itemStyle: { color: lineColor },
-        areaStyle: {
-          opacity: 0.3,
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: lineColor + "80" },
-            { offset: 1, color: lineColor + "00" },
-          ]),
-        },
+    const mode = productionChartSeries.value.mode || "single";
+    const blockLineColor = "#4A8BFF";
+    const plateLineColor = "#52C9A0";
+
+    const buildAreaLine = (name, data, lineColor) => ({
+      name,
+      data,
+      type: "line",
+      smooth: true,
+      lineStyle: { width: getResponsiveValue(2), color: lineColor },
+      itemStyle: { color: lineColor },
+      areaStyle: {
+        opacity: 0.3,
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: lineColor + "80" },
+          { offset: 1, color: lineColor + "00" },
+        ]),
       },
-    ];
+    });
+
+    let series;
+    if (mode === "dual") {
+      series = [
+        buildAreaLine(
+          "鐮屽潡",
+          productionChartSeries.value.blockValues || [],
+          blockLineColor
+        ),
+        buildAreaLine(
+          "鏉挎潗",
+          productionChartSeries.value.plateValues || [],
+          plateLineColor
+        ),
+      ];
+    } else {
+      const cat = productionCategory.value;
+      const seriesName = cat === "鐮屽潡" ? "鐮屽潡" : "鏉挎潗";
+      series = [
+        buildAreaLine(
+          seriesName,
+          productionChartSeries.value.values || [],
+          blockLineColor
+        ),
+      ];
+    }
 
     return {
       backgroundColor: "transparent",
@@ -575,22 +623,12 @@
         borderWidth: getResponsiveValue(1),
         textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
       },
-      legend: {
-        data: [seriesName],
-        top: "10%",
-        right: "1%",
-        textStyle: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(9),
-        },
-        itemWidth: getResponsiveValue(10),
-        itemHeight: getResponsiveValue(10),
-      },
+      legend: { show: false },
       grid: {
         left: "1%",
         right: "1%",
         bottom: "1%",
-        top: "28%",
+        top: "10%",
         containLabel: true,
       },
       xAxis: {
@@ -857,6 +895,36 @@
     鏉挎潗: "plateOutput",
   };
 
+  /** 鍏ㄩ儴锛氭寜 dateStr 鍚堝苟鐮屽潡/鏉挎潗涓ゆ潯搴忓垪 */
+  /** 灏嗘煇鐗╂枡鏃堕棿搴忓垪 chartData 姹囨�讳负鎶曞叆/浜у嚭鍚堣锛堢敤浜庛�屽叏閮ㄣ�嶆í杞�=鐗╂枡瀵规瘮锛� */
+  const sumChartDataInputOutput = chartData => {
+    const arr = Array.isArray(chartData) ? chartData : [];
+    const input = arr.reduce((s, c) => s + (Number(c.inputSum) || 0), 0);
+    const output = arr.reduce((s, c) => s + (Number(c.outputSum) || 0), 0);
+    return { input, output };
+  };
+
+  const mergeBlockPlateProductionSeries = (blockList, plateList) => {
+    const blocks = Array.isArray(blockList) ? blockList : [];
+    const plates = Array.isArray(plateList) ? plateList : [];
+    const blockByDate = new Map(blocks.map(i => [i.dateStr, i]));
+    const plateByDate = new Map(plates.map(i => [i.dateStr, i]));
+    const dates = [
+      ...new Set([...blockByDate.keys(), ...plateByDate.keys()]),
+    ].sort();
+    return {
+      categories: dates,
+      blockValues: dates.map(d => {
+        const row = blockByDate.get(d);
+        return row ? Number(row.blockOutput) || 0 : 0;
+      }),
+      plateValues: dates.map(d => {
+        const row = plateByDate.get(d);
+        return row ? Number(row.plateOutput) || 0 : 0;
+      }),
+    };
+  };
+
   const loadBlockCost = async () => {
     const materialName = blockSelectedMaterial.value;
     if (!materialName) {
@@ -865,13 +933,95 @@
         monthlyConsumption: "--",
         yearlyConsumption: "--",
       };
-      blockCostChartSeries.value = { categories: [], input: [], output: [] };
+      blockCostChartSeries.value = {
+        xMode: "time",
+        categories: [],
+        input: [],
+        output: [],
+      };
       updateCharts();
       return;
     }
     const dateType = mapTimeDimensionToDateType(blockTimeDimension.value);
     try {
-      const res = await getProductionStatisticsBlocks({ materialName, dateType });
+      if (materialName === "鍏ㄩ儴") {
+        const names = blockMaterialList.value.filter(n => n !== "鍏ㄩ儴");
+        if (!names.length) {
+          blockMaterialSummary.value = {
+            materialName: "鍏ㄩ儴",
+            monthlyConsumption: "--",
+            yearlyConsumption: "--",
+          };
+          blockCostChartSeries.value = {
+            xMode: "material",
+            categories: [],
+            input: [],
+            output: [],
+          };
+          updateCharts();
+          return;
+        }
+        const res = await getProductionStatisticsBlocks({ dateType });
+        const rows = Array.isArray(res.data) ? res.data : [];
+        const rowByName = new Map(
+          rows.filter(r => r && r.materialName).map(r => [r.materialName, r])
+        );
+        const useBulk = names.every(n => rowByName.has(n));
+
+        let categories;
+        let input;
+        let output;
+        if (useBulk) {
+          categories = [];
+          input = [];
+          output = [];
+          for (const name of names) {
+            const row = rowByName.get(name);
+            const chartData = Array.isArray(row.chartData) ? row.chartData : [];
+            const sums = sumChartDataInputOutput(chartData);
+            categories.push(name);
+            input.push(sums.input);
+            output.push(sums.output);
+          }
+        } else {
+          const parts = await Promise.all(
+            names.map(async name => {
+              const r = await getProductionStatisticsBlocks({
+                dateType,
+                materialName: name,
+              });
+              const rows2 = Array.isArray(r.data) ? r.data : [];
+              const row =
+                rows2.find(x => x.materialName === name) || rows2[0] || {};
+              const chartData = Array.isArray(row.chartData) ? row.chartData : [];
+              const sums = sumChartDataInputOutput(chartData);
+              return { name, ...sums };
+            })
+          );
+          categories = parts.map(p => p.name);
+          input = parts.map(p => p.input);
+          output = parts.map(p => p.output);
+        }
+
+        blockMaterialSummary.value = {
+          materialName: "鍏ㄩ儴",
+          monthlyConsumption: "--",
+          yearlyConsumption: "--",
+        };
+        blockCostChartSeries.value = {
+          xMode: "material",
+          categories,
+          input,
+          output,
+        };
+        updateCharts();
+        return;
+      }
+
+      const res = await getProductionStatisticsBlocks({
+        dateType,
+        materialName,
+      });
       const rows = Array.isArray(res.data) ? res.data : [];
       const row =
         rows.find(r => r.materialName === materialName) || rows[0] || {};
@@ -882,6 +1032,7 @@
         yearlyConsumption: row.yearlyConsumption ?? "--",
       };
       blockCostChartSeries.value = {
+        xMode: "time",
         categories: chartData.map(c => c.date),
         input: chartData.map(c => c.inputSum ?? 0),
         output: chartData.map(c => c.outputSum ?? 0),
@@ -900,13 +1051,95 @@
         monthlyConsumption: "--",
         yearlyConsumption: "--",
       };
-      boardCostChartSeries.value = { categories: [], input: [], output: [] };
+      boardCostChartSeries.value = {
+        xMode: "time",
+        categories: [],
+        input: [],
+        output: [],
+      };
       updateCharts();
       return;
     }
     const dateType = mapTimeDimensionToDateType(boardTimeDimension.value);
     try {
-      const res = await getProductionStatisticsPlates({ materialName, dateType });
+      if (materialName === "鍏ㄩ儴") {
+        const names = boardMaterialList.value.filter(n => n !== "鍏ㄩ儴");
+        if (!names.length) {
+          boardMaterialSummary.value = {
+            materialName: "鍏ㄩ儴",
+            monthlyConsumption: "--",
+            yearlyConsumption: "--",
+          };
+          boardCostChartSeries.value = {
+            xMode: "material",
+            categories: [],
+            input: [],
+            output: [],
+          };
+          updateCharts();
+          return;
+        }
+        const res = await getProductionStatisticsPlates({ dateType });
+        const rows = Array.isArray(res.data) ? res.data : [];
+        const rowByName = new Map(
+          rows.filter(r => r && r.materialName).map(r => [r.materialName, r])
+        );
+        const useBulk = names.every(n => rowByName.has(n));
+
+        let categories;
+        let input;
+        let output;
+        if (useBulk) {
+          categories = [];
+          input = [];
+          output = [];
+          for (const name of names) {
+            const row = rowByName.get(name);
+            const chartData = Array.isArray(row.chartData) ? row.chartData : [];
+            const sums = sumChartDataInputOutput(chartData);
+            categories.push(name);
+            input.push(sums.input);
+            output.push(sums.output);
+          }
+        } else {
+          const parts = await Promise.all(
+            names.map(async name => {
+              const r = await getProductionStatisticsPlates({
+                dateType,
+                materialName: name,
+              });
+              const rows2 = Array.isArray(r.data) ? r.data : [];
+              const row =
+                rows2.find(x => x.materialName === name) || rows2[0] || {};
+              const chartData = Array.isArray(row.chartData) ? row.chartData : [];
+              const sums = sumChartDataInputOutput(chartData);
+              return { name, ...sums };
+            })
+          );
+          categories = parts.map(p => p.name);
+          input = parts.map(p => p.input);
+          output = parts.map(p => p.output);
+        }
+
+        boardMaterialSummary.value = {
+          materialName: "鍏ㄩ儴",
+          monthlyConsumption: "--",
+          yearlyConsumption: "--",
+        };
+        boardCostChartSeries.value = {
+          xMode: "material",
+          categories,
+          input,
+          output,
+        };
+        updateCharts();
+        return;
+      }
+
+      const res = await getProductionStatisticsPlates({
+        dateType,
+        materialName,
+      });
       const rows = Array.isArray(res.data) ? res.data : [];
       const row =
         rows.find(r => r.materialName === materialName) || rows[0] || {};
@@ -917,6 +1150,7 @@
         yearlyConsumption: row.yearlyConsumption ?? "--",
       };
       boardCostChartSeries.value = {
+        xMode: "time",
         categories: chartData.map(c => c.date),
         input: chartData.map(c => c.inputSum ?? 0),
         output: chartData.map(c => c.outputSum ?? 0),
@@ -930,7 +1164,8 @@
   const loadBlockMaterials = async () => {
     try {
       const res = await getProductionMaterials({ materialType: "1" });
-      const list = Array.isArray(res.data) ? [...res.data] : [];
+      const raw = Array.isArray(res.data) ? [...res.data] : [];
+      const list = ["鍏ㄩ儴", ...raw];
       blockMaterialList.value = list;
       if (list.length) {
         if (!list.includes(blockSelectedMaterial.value)) {
@@ -944,7 +1179,12 @@
           monthlyConsumption: "--",
           yearlyConsumption: "--",
         };
-        blockCostChartSeries.value = { categories: [], input: [], output: [] };
+        blockCostChartSeries.value = {
+          xMode: "time",
+          categories: [],
+          input: [],
+          output: [],
+        };
         updateCharts();
       }
     } catch (e) {
@@ -955,7 +1195,8 @@
   const loadBoardMaterials = async () => {
     try {
       const res = await getProductionMaterials({ materialType: "2" });
-      const list = Array.isArray(res.data) ? [...res.data] : [];
+      const raw = Array.isArray(res.data) ? [...res.data] : [];
+      const list = ["鍏ㄩ儴", ...raw];
       boardMaterialList.value = list;
       if (list.length) {
         if (!list.includes(boardSelectedMaterial.value)) {
@@ -969,7 +1210,12 @@
           monthlyConsumption: "--",
           yearlyConsumption: "--",
         };
-        boardCostChartSeries.value = { categories: [], input: [], output: [] };
+        boardCostChartSeries.value = {
+          xMode: "time",
+          categories: [],
+          input: [],
+          output: [],
+        };
         updateCharts();
       }
     } catch (e) {
@@ -983,12 +1229,30 @@
       const res = await getMaterialProductionAnalysis({ dateType });
       const payload = res.data && typeof res.data === "object" ? res.data : {};
       const cat = productionCategory.value;
-      const list = Array.isArray(payload[cat]) ? payload[cat] : [];
-      const field = productionOutputKey[cat] || "totalOutput";
-      productionChartSeries.value = {
-        categories: list.map(i => i.dateStr),
-        values: list.map(i => Number(i[field]) || 0),
-      };
+
+      if (cat === "鍏ㄩ儴") {
+        const merged = mergeBlockPlateProductionSeries(
+          payload["鐮屽潡"],
+          payload["鏉挎潗"]
+        );
+        productionChartSeries.value = {
+          categories: merged.categories,
+          mode: "dual",
+          values: [],
+          blockValues: merged.blockValues,
+          plateValues: merged.plateValues,
+        };
+      } else {
+        const list = Array.isArray(payload[cat]) ? payload[cat] : [];
+        const field = productionOutputKey[cat] || "totalOutput";
+        productionChartSeries.value = {
+          categories: list.map(i => i.dateStr),
+          mode: "single",
+          values: list.map(i => Number(i[field]) || 0),
+          blockValues: [],
+          plateValues: [],
+        };
+      }
       updateCharts();
     } catch (e) {
       console.error(e);
@@ -1094,21 +1358,37 @@
     updateCharts();
   };
 
+  /** 鐮屽潡/鏉挎潗瀹瑰櫒楂樺害鍙樺寲鍚庯紙濡傞殣钘忔眹鎬诲崱锛夐渶 resize锛屽惁鍒欏簳閮ㄧ暀鐧� */
+  const resizeCostPanelCharts = () => {
+    nextTick(() => {
+      requestAnimationFrame(() => {
+        blockCostChartInstance?.resize();
+        boardCostChartInstance?.resize();
+      });
+    });
+  };
+
   // 鏇存柊鍥捐〃
   const updateCharts = () => {
-    // 鏇存柊鐮屽潡鎴愭湰鍥捐〃
+    // 鏇存柊鐮屽潡鎴愭湰鍥捐〃锛坮eplaceMerge锛氬叏閮ㄢ啍鍗曠墿鏂欐椂妯酱鏃堕棿/鐗╂枡鍒囨崲锛�
     if (blockCostChartInstance) {
-      blockCostChartInstance.setOption(blockCostChartOption.value);
+      blockCostChartInstance.setOption(blockCostChartOption.value, {
+        replaceMerge: ["series", "xAxis"],
+      });
     }
 
     // 鏇存柊鏉挎潗鎴愭湰鍥捐〃
     if (boardCostChartInstance) {
-      boardCostChartInstance.setOption(boardCostChartOption.value);
+      boardCostChartInstance.setOption(boardCostChartOption.value, {
+        replaceMerge: ["series", "xAxis"],
+      });
     }
 
-    // 鏇存柊浜ч噺鍒嗘瀽鍥捐〃
+    // 鏇存柊浜ч噺鍒嗘瀽鍥捐〃锛坮eplaceMerge锛氬叏閮ㄢ啍鐮屽潡/鏉挎潗鍒囨崲鏃跺幓鎺夊浣欐姌绾匡紝閬垮厤鍚堝苟娈嬬暀锛�
     if (productionChartInstance) {
-      productionChartInstance.setOption(productionChartOption.value);
+      productionChartInstance.setOption(productionChartOption.value, {
+        replaceMerge: ["series"],
+      });
     }
 
     // 鏇存柊鏂板瀹㈡埛瓒嬪娍鍥捐〃
@@ -1120,6 +1400,8 @@
     if (salesRankingChartInstance) {
       salesRankingChartInstance.setOption(salesRankingChartOption.value);
     }
+
+    resizeCostPanelCharts();
   };
 
   // 澶勭悊鏃堕棿缁村害閫夋嫨
@@ -1182,6 +1464,20 @@
     // 绛夊緟DOM鏇存柊鍚庡垵濮嬪寲鍥捐〃骞舵媺鍙栨帴鍙f暟鎹�
     nextTick(async () => {
       initCharts();
+      if (typeof ResizeObserver !== "undefined") {
+        if (blockCostChart.value) {
+          blockCostChartResizeObserver = new ResizeObserver(() => {
+            blockCostChartInstance?.resize();
+          });
+          blockCostChartResizeObserver.observe(blockCostChart.value);
+        }
+        if (boardCostChart.value) {
+          boardCostChartResizeObserver = new ResizeObserver(() => {
+            boardCostChartInstance?.resize();
+          });
+          boardCostChartResizeObserver.observe(boardCostChart.value);
+        }
+      }
       await Promise.all([
         loadBlockMaterials(),
         loadBoardMaterials(),
@@ -1190,6 +1486,7 @@
         loadSolidWasteData(),
         loadEnergyData(),
       ]);
+      resizeCostPanelCharts();
     });
 
     // 娣诲姞绐楀彛澶у皬鍙樺寲鐩戝惉
@@ -1203,6 +1500,11 @@
       clearInterval(timeTicker);
       timeTicker = null;
     }
+
+    blockCostChartResizeObserver?.disconnect();
+    blockCostChartResizeObserver = null;
+    boardCostChartResizeObserver?.disconnect();
+    boardCostChartResizeObserver = null;
 
     if (blockCostChartInstance) {
       blockCostChartInstance.dispose();
@@ -1610,9 +1912,30 @@
     margin-bottom: 0.2vh;
   }
 
+  /* 鐮屽潡/鏉挎潗锛氬浘琛ㄥ崰婊� Tab 涓庢眹鎬诲崱涓嬫柟鐨勫墿浣欓珮搴︼紱闅愯棌姹囨�诲崱鏃惰嚜鍔ㄦ媺楂� */
+  .bi-panel-top-left,
+  .bi-panel-bottom-left {
+    min-height: 0;
+  }
+
+  .bi-panel-top-left .bi-panel-body,
+  .bi-panel-bottom-left .bi-panel-body {
+    display: flex;
+    flex-direction: column;
+    min-height: 0;
+  }
+
+  .bi-panel-top-left .chart-filter-tabs,
+  .bi-panel-bottom-left .chart-filter-tabs {
+    flex-shrink: 0;
+  }
+
   .bi-panel-top-left .echart-fill,
   .bi-panel-bottom-left .echart-fill {
-    height: 24vh;
+    flex: 1;
+    min-height: 0;
+    height: auto;
+    width: 100%;
   }
 
   .bi-panel-bottom-right .echart-fill {

--
Gitblit v1.9.3