From 1cf342fa23a6c34bd9dd1ff4e416cd255c044fdd Mon Sep 17 00:00:00 2001
From: yyb <995253665@qq.com>
Date: 星期四, 02 四月 2026 11:41:17 +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 | 1753 ++++++++++++++++++++++++++++++++++++++++------------------
 1 files changed, 1,212 insertions(+), 541 deletions(-)

diff --git a/src/views/reportAnalysis/productionStatistics/index.vue b/src/views/reportAnalysis/productionStatistics/index.vue
index a168ed9..47822ce 100644
--- a/src/views/reportAnalysis/productionStatistics/index.vue
+++ b/src/views/reportAnalysis/productionStatistics/index.vue
@@ -54,31 +54,37 @@
                 @click="handleBlockTimeDimensionChange('month')">鏈�</span>
         </div>
         <div class="bi-panel-body">
-            <div class="chart-filter-tabs">
-            <span v-for="area in salesAreas"
-                  :key="area"
+          <div class="chart-filter-tabs">
+            <span v-for="name in blockMaterialList"
+                  :key="name"
                   class="cf-tab"
-                  :class="{ active: blockSelectedArea === area }"
-                  @click="handleBlockAreaChange(area)">{{ area }}</span>
+                  :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" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <svg width="24"
+                   height="24"
+                   viewBox="0 0 24 24"
+                   fill="none"
+                   stroke="currentColor"
+                   stroke-width="2">
                 <path d="M20 7h-4V4c0-1.1-.9-2-2-2h-4c-1.1 0-2 .9-2 2v3H4c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2z" />
                 <polyline points="22,7 12,13 2,7" />
               </svg>
             </div>
             <div class="material-details">
-              <div class="material-name">{{ blockMaterialType }}AAA</div>
+              <div class="material-name">{{ blockMaterialSummary.materialName || "鈥�" }}</div>
               <div class="material-stats">
                 <div class="stat-item">
                   <span class="stat-label">鏈堢疮璁″崟鑰�</span>
-                  <span class="stat-value">78/12</span>
+                  <span class="stat-value">{{ blockMaterialSummary.monthlyConsumption }}</span>
                   <span class="stat-unit">鍚�</span>
                 </div>
                 <div class="stat-item">
                   <span class="stat-label">骞寸疮璁″崟鑰�</span>
-                  <span class="stat-value">78/12</span>
+                  <span class="stat-value">{{ blockMaterialSummary.yearlyConsumption }}</span>
                   <span class="stat-unit">鍚�</span>
                 </div>
               </div>
@@ -104,6 +110,13 @@
                 @click="handleProductionTimeDimensionChange('month')">鏈�</span>
         </div>
         <div class="bi-panel-body">
+          <div class="chart-filter-tabs">
+            <span v-for="cat in productionCategories"
+                  :key="cat"
+                  class="cf-tab"
+                  :class="{ active: productionCategory === cat }"
+                  @click="selectProductionCategory(cat)">{{ cat }}</span>
+          </div>
           <div class="chart-unit-row">
             <span>鍗曚綅锛氫欢</span>
           </div>
@@ -113,28 +126,28 @@
       </div>
       <!-- 涓棿涓績鐜� -->
       <div class="center-ring">
-        <!-- <div class="center-ring-box">
-          <div class="center-metric m1">
-            <div class="center-metric-label">椤圭洰浜ч噺</div>
-            <div class="center-metric-value">{{ projectProduction }}</div>
-            <div class="center-metric-unit">浠�</div>
+        <div class="center-ring-box">
+          <div class="ring-box-topright">
+            <div class="topright-label">鍥哄簾澶勭悊閲�</div>
           </div>
-          <div class="center-metric m2">
-            <div class="center-metric-label">鍥轰綋澶勭悊閲�</div>
-            <div class="center-metric-value">{{ solidWaste澶勭悊閲� }}</div>
-            <div class="center-metric-unit">鍚�</div>
+          <div class="ring-box-left">
+            <div class="left-label">绮夌叅鐏�</div>
+            <div class="left-value">鏈堝鐞� <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.flyAshMonth }}</span> 鍚� 骞村鐞� <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.flyAshYear }}</span> 鍚�</div>
+            <div class="left-label"
+                 style="margin-top: 2vh;">鐭宠啅</div>
+            <div class="left-value">鏈堝鐞� <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.gypsumMonth }}</span> 鍚� 骞村鐞� <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.gypsumYear }}</span> 鍚�</div>
           </div>
-          <div class="center-metric m3">
-            <div class="center-metric-label">鐮屽潡浜ч噺</div>
-            <div class="center-metric-value">{{ blockProduction }}</div>
-            <div class="center-metric-unit">浠�</div>
+          <div class="ring-box-topleft">
+            <div class="topleft-label">椤圭洰浜ч噺</div>
           </div>
-          <div class="center-metric m4">
-            <div class="center-metric-label">鏉挎潗浜ч噺</div>
-            <div class="center-metric-value">{{ boardProduction }}</div>
-            <div class="center-metric-unit">浠�</div>
+          <div class="ring-box-right">
+            <div class="right-label">鐮屽潡浜ч噺</div>
+            <div class="right-value">鏈堜骇閲� <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.blockMonth }}</span> 鍚� 骞翠骇閲� <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.blockYear }}</span> 鍚�</div>
+            <div class="right-label"
+                 style="margin-top: 2vh;">鏉挎潗浜ч噺</div>
+            <div class="right-value">鏈堜骇閲� <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.plateMonth }}</span> 鍚� 骞翠骇閲� <span style="font-weight: bold;font-size: 1.3vh;">{{ middleRingStats.plateYear }}</span> 鍚�</div>
           </div>
-        </div> -->
+        </div>
       </div>
       <!-- 宸︿笅锛氱敓浜ф垚鏈崟鑰楃粺璁★紙鏉挎潗锛� -->
       <div class="bi-panel bi-panel-bottom-left">
@@ -149,31 +162,37 @@
                 @click="handleBoardTimeDimensionChange('month')">鏈�</span>
         </div>
         <div class="bi-panel-body">
-            <div class="chart-filter-tabs">
-            <span v-for="area in salesAreas"
-                  :key="area"
+          <div class="chart-filter-tabs">
+            <span v-for="name in boardMaterialList"
+                  :key="name"
                   class="cf-tab"
-                  :class="{ active: blockSelectedArea === area }"
-                  @click="handleBlockAreaChange(area)">{{ area }}</span>
+                  :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" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+              <svg width="24"
+                   height="24"
+                   viewBox="0 0 24 24"
+                   fill="none"
+                   stroke="currentColor"
+                   stroke-width="2">
                 <path d="M20 7h-4V4c0-1.1-.9-2-2-2h-4c-1.1 0-2 .9-2 2v3H4c-1.1 0-2 .9-2 2v11c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2z" />
                 <polyline points="22,7 12,13 2,7" />
               </svg>
             </div>
             <div class="material-details">
-              <div class="material-name">{{ boardMaterialType }}AAA</div>
+              <div class="material-name">{{ boardMaterialSummary.materialName || "鈥�" }}</div>
               <div class="material-stats">
                 <div class="stat-item">
                   <span class="stat-label">鏈堢疮璁″崟鑰�</span>
-                  <span class="stat-value">78/12</span>
+                  <span class="stat-value">{{ boardMaterialSummary.monthlyConsumption }}</span>
                   <span class="stat-unit">鍚�</span>
                 </div>
                 <div class="stat-item">
                   <span class="stat-label">骞寸疮璁″崟鑰�</span>
-                  <span class="stat-value">78/12</span>
+                  <span class="stat-value">{{ boardMaterialSummary.yearlyConsumption }}</span>
                   <span class="stat-unit">鍚�</span>
                 </div>
               </div>
@@ -198,6 +217,8 @@
                 :class="{ active: customerTimeDimension === 'month' }"
                 @click="handleCustomerTimeDimensionChange('month')">鏈�</span>
         </div>
+        <div ref="customerTrendChart"
+             class="echart-fill"></div>
         <!-- <div class="bi-panel-body">
           <div class="chart-unit-row chart-unit-single">
             <span>鍗曚綅锛氬</span>
@@ -209,7 +230,7 @@
       <!-- 鍙充笅锛氶攢閲忔帓鍚嶅垎鏋� -->
       <div class="bi-panel bi-panel-bottom-right">
         <PanelHeader :isFullscreen="true"
-                     title="鐗╂枡鐢熶骇閲忕粺璁�" />
+                     title="鑳借�楃粺璁�" />
         <div class="panel-tabs">
           <span class="tab-item"
                 :class="{ active: salesTimeDimension === 'year' }"
@@ -219,15 +240,8 @@
                 @click="handleSalesTimeDimensionChange('month')">鏈�</span>
         </div>
         <div class="bi-panel-body">
-          <!-- <div class="chart-filter-tabs">
-            <span v-for="area in salesAreas"
-                  :key="area"
-                  class="cf-tab"
-                  :class="{ active: selectedArea === area }"
-                  @click="handleAreaChange(area)">{{ area }}</span>
-          </div>
           <div ref="salesRankingChart"
-               class="echart-fill"></div> -->
+               class="echart-fill"></div>
         </div>
       </div>
     </div>
@@ -240,12 +254,20 @@
     computed,
     onMounted,
     onBeforeUnmount,
-    watch,
     nextTick,
   } from "vue";
   import * as echarts from "echarts";
   import dayjs from "dayjs";
   import PanelHeader from "@/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue";
+  import {
+    getMaterialProductionAnalysis,
+    getProductionMaterials,
+    getProductionStatisticsBlocks,
+    getProductionStatisticsPlates,
+    getProductionStatisticsMiddle,
+    getProductionStatisticsSolidWaste,
+    getProductionStatisticsEnergy,
+  } from "@/api/reportAnalysis/productionStatistics.js";
 
   const screenRoot = ref(null);
   const isFullscreen = ref(false);
@@ -297,30 +319,80 @@
 
   // 閫夋嫨鍣ㄦ暟鎹�
   const blockTimeDimension = ref("year");
-  const blockMaterialType = ref("鐭崇伆");
   const boardTimeDimension = ref("year");
-  const boardMaterialType = ref("鐭崇伆");
   const productionTimeDimension = ref("year");
   const customerTimeDimension = ref("year");
   const salesTimeDimension = ref("year");
-  const selectedArea = ref("鍏ㄩ儴");
 
-  const salesAreas = [
-    "鍏ㄩ儴",
-    "鐭崇伆",
-    "姘存偿",
-    "閾濈矇鑶�",
-    "鑴辨ā鍓�",
-    "闃茶厫鍓�",
-    "姘у寲闀�",
-    "鍐锋嫈涓�",
-  ];
+  const productionCategories = ["鍏ㄩ儴", "鐮屽潡", "鏉挎潗"];
+  const productionCategory = ref("鍏ㄩ儴");
 
-  // 涓績鐜暟鎹�
-  const projectProduction = ref(12345);
-  const solidWaste澶勭悊閲� = ref(6789);
-  const blockProduction = ref(7812);
-  const boardProduction = ref(7812);
+  const blockMaterialList = ref([]);
+  const blockSelectedMaterial = ref("");
+  const boardMaterialList = ref([]);
+  const boardSelectedMaterial = ref("");
+
+  const blockMaterialSummary = ref({
+    materialName: "",
+    monthlyConsumption: "--",
+    yearlyConsumption: "--",
+  });
+  const boardMaterialSummary = ref({
+    materialName: "",
+    monthlyConsumption: "--",
+    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锛�
+  const solidWasteChartSeries = ref({
+    categories: [],
+    total: [],
+    flyAsh: [],
+    gypsum: [],
+    lime: [],
+  });
+
+  // 鑳借�楃粺璁★紙/home/productionStatistics/energy锛�
+  const energyChartSeries = ref({
+    categories: [],
+    water: [],
+    electricity: [],
+    steam: [],
+  });
+
+  // 涓績鐜細鍥哄簾锛堢矇鐓ょ伆/鐭宠啅锛�+ 椤圭洰浜ч噺锛堢爩鍧�/鏉挎潗锛夛紝鎺ュ彛 /home/productionStatistics/middle
+  const middleRingStats = ref({
+    flyAshMonth: 0,
+    flyAshYear: 0,
+    gypsumMonth: 0,
+    gypsumYear: 0,
+    blockMonth: 0,
+    blockYear: 0,
+    plateMonth: 0,
+    plateYear: 0,
+  });
 
   // 鍥捐〃瀹炰緥
   let blockCostChartInstance = null;
@@ -328,466 +400,484 @@
   let productionChartInstance = null;
   let customerTrendChartInstance = null;
   let salesRankingChartInstance = null;
+  let blockCostChartResizeObserver = null;
+  let boardCostChartResizeObserver = null;
 
-  // 鐢熶骇鍗曡�楀浘琛ㄩ厤缃�
+  // 鐢熶骇鍗曡�楀浘琛ㄩ厤缃紙鐮屽潡锛屾帴鍙f暟鎹級
   const blockCostChartOption = computed(() => {
-    const materials = ["娑堣�楅噺"];
-    const colors = ["#8A6BFF"];
-    const year = 2024;
-    const periodType = blockTimeDimension.value;
-
-    // 鐢熸垚鏃堕棿娈�
-    let periods = [];
-    if (periodType === "year") {
-      // 骞村害鏁版嵁锛�6涓湀
-      for (let month = 9; month <= 12; month++) {
-        periods.push(`${month}/${year.toString().slice(2)}`);
-      }
-      for (let month = 1; month <= 3; month++) {
-        periods.push(`${month}/${(year + 1).toString().slice(2)}`);
-      }
-    } else {
-      // 鏈堝害鏁版嵁锛�30澶�
-      const month = 1;
-      for (let day = 1; day <= 30; day++) {
-        periods.push(`${month}/${day}`);
-      }
-    }
-
-    // 涓烘瘡绉嶆潗鏂欑敓鎴愭暟鎹�
-    const series = materials.map((material, index) => {
-      const data = periods.map(() => {
-        return periodType === "year"
-          ? Math.floor(Math.random() * 50) + 150
-          : Math.floor(Math.random() * 5) + 15;
-      });
-
-      return {
-        name: material,
-        data: data,
+    const xMode = blockCostChartSeries.value.xMode || "time";
+    const periods = blockCostChartSeries.value.categories || [];
+    const inputData = blockCostChartSeries.value.input || [];
+    const outputData = blockCostChartSeries.value.output || [];
+    const legendNames = ["鎶曞叆閲�", "浜у嚭閲�"];
+    const colors = ["#6B9DFF", "#8A6BFF"];
+    const series = [
+      {
+        name: legendNames[0],
+        data: inputData,
         type: "bar",
-        itemStyle: { color: colors[index] },
-      };
-    });
-
-    return {
-      backgroundColor: "transparent",
-      tooltip: {
-        trigger: "axis",
-        backgroundColor: "rgba(0,0,0,0.55)",
-        borderColor: "rgba(64,158,255,0.25)",
-        borderWidth: getResponsiveValue(1),
-        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
+        itemStyle: { color: colors[0] },
       },
-      // legend: {
-      //   data: materials,
-      //   top: "10%",
-      //   right: "1%",
-      //   textStyle: {
-      //     color: "#B8C8E0",
-      //     fontSize: getResponsiveValue(9),
-      //   },
-      //   itemWidth: getResponsiveValue(10),
-      //   itemHeight: getResponsiveValue(10),
-      // },
-      grid: {
-        left: "1%",
-        right: "1%",
-        bottom: "1%",
-        top: "8%",
-        containLabel: true,
-      },
-      xAxis: {
-        type: "category",
-        data: periods,
-        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
-        axisTick: { show: false },
-        axisLabel: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(11),
-          margin: getResponsiveValue(10),
-        },
-        splitLine: { show: false },
-      },
-      yAxis: {
-        type: "value",
-        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
-        axisLabel: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(11),
-          margin: getResponsiveValue(8),
-        },
-        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
-      },
-      series: series,
-    };
-  });
-
-  // 鏉挎潗鍗曡�楀浘琛ㄩ厤缃�
-  const boardCostChartOption = computed(() => {
-    const materials = ["娑堣�楅噺"];
-    const colors = ["#00A4ED", "#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447", "#FF6B6B"];
-    const year = 2024;
-    const periodType = boardTimeDimension.value;
-
-    // 鐢熸垚鏃堕棿娈�
-    let periods = [];
-    if (periodType === "year") {
-      // 骞村害鏁版嵁锛�6涓湀
-      for (let month = 9; month <= 12; month++) {
-        periods.push(`${month}/${year.toString().slice(2)}`);
-      }
-      for (let month = 1; month <= 3; month++) {
-        periods.push(`${month}/${(year + 1).toString().slice(2)}`);
-      }
-    } else {
-      // 鏈堝害鏁版嵁锛�30澶�
-      const month = 1;
-      for (let day = 1; day <= 30; day++) {
-        periods.push(`${month}/${day}`);
-      }
-    }
-
-    // 涓烘瘡绉嶆潗鏂欑敓鎴愭暟鎹�
-    const series = materials.map((material, index) => {
-      const data = periods.map(() => {
-        return periodType === "year"
-          ? Math.floor(Math.random() * 50) + 150
-          : Math.floor(Math.random() * 5) + 15;
-      });
-
-      return {
-        name: material,
-        data: data,
+      {
+        name: legendNames[1],
+        data: outputData,
         type: "bar",
-        itemStyle: { color: colors[index] },
-      };
-    });
-
-    return {
-      backgroundColor: "transparent",
-      tooltip: {
-        trigger: "axis",
-        backgroundColor: "rgba(0,0,0,0.55)",
-        borderColor: "rgba(64,158,255,0.25)",
-        borderWidth: getResponsiveValue(1),
-        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
+        itemStyle: { color: colors[1] },
       },
-      // legend: {
-      //   data: materials,
-      //   top: "10%",
-      //   right: "1%",
-      //   textStyle: {
-      //     color: "#B8C8E0",
-      //     fontSize: getResponsiveValue(9),
-      //   },
-      //   itemWidth: getResponsiveValue(10),
-      //   itemHeight: getResponsiveValue(10),
-      // },
-      grid: {
-        left: "1%",
-        right: "1%",
-        bottom: "1%",
-        top: "8%",
-        containLabel: true,
-      },
-      xAxis: {
-        type: "category",
-        data: periods,
-        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
-        axisTick: { show: false },
-        axisLabel: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(11),
-          margin: getResponsiveValue(10),
-        },
-        splitLine: { show: false },
-      },
-      yAxis: {
-        type: "value",
-        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
-        axisLabel: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(11),
-          margin: getResponsiveValue(8),
-        },
-        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
-      },
-      series: series,
-    };
-  });
-
-  // 浜ч噺鍒嗘瀽鍥捐〃閰嶇疆
-  const productionChartOption = computed(() => {
-    const salesAreas = ["鍏ㄩ儴", "鐮屽潡", "鏉挎潗"];
-    const colors = ["#00A4ED", "#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447", "#FF6B6B"];
-    const year = 2024;
-    const periodType = productionTimeDimension.value;
-
-    // 鐢熸垚鏃堕棿娈�
-    let periods = [];
-    if (periodType === "year") {
-      // 骞村害鏁版嵁锛�6涓湀
-      for (let month = 9; month <= 12; month++) {
-        periods.push(`${month}/${year.toString().slice(2)}`);
-      }
-      for (let month = 1; month <= 3; month++) {
-        periods.push(`${month}/${(year + 1).toString().slice(2)}`);
-      }
-    } else {
-      // 鏈堝害鏁版嵁锛�30澶�
-      const month = 1;
-      for (let day = 1; day <= 30; day++) {
-        periods.push(`${month}/${day}`);
-      }
-    }
-
-    // 涓烘瘡涓攢鍞尯鐢熸垚鏁版嵁
-    const series = salesAreas.map((area, index) => {
-      const data = periods.map(() => {
-        return periodType === "year"
-          ? Math.floor(Math.random() * 50) + 150
-          : Math.floor(Math.random() * 5) + 15;
-      });
-
-      return {
-        name: area,
-        data: data,
-        type: "line",
-        smooth: true,
-        lineStyle: { width: getResponsiveValue(2), color: colors[index] },
-        itemStyle: { color: colors[index] },
-        areaStyle: {
-          opacity: 0.3,
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: colors[index] + "80" },
-            { offset: 1, color: colors[index] + "00" },
-          ]),
-        },
-      };
-    });
-
-    return {
-      backgroundColor: "transparent",
-      tooltip: {
-        trigger: "axis",
-        backgroundColor: "rgba(0,0,0,0.55)",
-        borderColor: "rgba(64,158,255,0.25)",
-        borderWidth: getResponsiveValue(1),
-        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
-      },
-      legend: {
-        data: salesAreas,
-        top: "10%",
-        right: "1%",
-        textStyle: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(9),
-        },
-        itemWidth: getResponsiveValue(10),
-        itemHeight: getResponsiveValue(10),
-      },
-      grid: {
-        left: "1%",
-        right: "1%",
-        bottom: "1%",
-        top: "28%",
-        containLabel: true,
-      },
-      xAxis: {
-        type: "category",
-        data: periods,
-        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
-        axisTick: { show: false },
-        axisLabel: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(11),
-          margin: getResponsiveValue(10),
-        },
-        splitLine: { show: false },
-      },
-      yAxis: {
-        type: "value",
-        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
-        axisLabel: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(11),
-          margin: getResponsiveValue(8),
-        },
-        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
-      },
-      series: series,
-    };
-  });
-
-  // 鏂板瀹㈡埛瓒嬪娍鍥捐〃閰嶇疆
-  const customerTrendChartOption = computed(() => {
-    const customerTypes = ["鍏ㄩ儴", "鐭崇伆", "姘存偿", "閾濈矇鑶�", "鑴辨ā鍓�"];
-    const colors = ["#00A4ED", "#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447"];
-    const year = 2024;
-    const periodType = customerTimeDimension.value;
-
-    // 鐢熸垚鏃堕棿娈�
-    let periods = [];
-    if (periodType === "year") {
-      // 骞村害鏁版嵁锛�6涓湀
-      for (let month = 9; month <= 12; month++) {
-        periods.push(`${month}/${year.toString().slice(2)}`);
-      }
-      for (let month = 1; month <= 5; month++) {
-        periods.push(`${month}/${(year + 1).toString().slice(2)}`);
-      }
-    } else {
-      // 鏈堝害鏁版嵁锛�30澶�
-      const month = 1;
-      for (let day = 1; day <= 30; day++) {
-        periods.push(`${month}/${day}`);
-      }
-    }
-
-    // 涓烘瘡绉嶅鎴风被鍨嬬敓鎴愭暟鎹�
-    const series = customerTypes.map((type, index) => {
-      const data = periods.map(() => {
-        return periodType === "year"
-          ? Math.floor(Math.random() * 10) + 5
-          : Math.floor(Math.random() * 3) + 1;
-      });
-
-      return {
-        name: type,
-        data: data,
-        type: "line",
-        smooth: true,
-        lineStyle: { width: getResponsiveValue(2), color: colors[index] },
-        itemStyle: { color: colors[index] },
-        areaStyle: {
-          opacity: 0.3,
-          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-            { offset: 0, color: colors[index] + "80" },
-            { offset: 1, color: colors[index] + "00" },
-          ]),
-        },
-      };
-    });
-
-    return {
-      backgroundColor: "transparent",
-      tooltip: {
-        trigger: "axis",
-        backgroundColor: "rgba(0,0,0,0.55)",
-        borderColor: "rgba(64,158,255,0.25)",
-        borderWidth: getResponsiveValue(1),
-        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
-      },
-      legend: {
-        data: customerTypes,
-        top: "10%",
-        right: "1%",
-        textStyle: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(9),
-        },
-        itemWidth: getResponsiveValue(10),
-        itemHeight: getResponsiveValue(10),
-      },
-      grid: {
-        left: "1%",
-        right: "1%",
-        bottom: "1%",
-        top: "28%",
-        containLabel: true,
-      },
-      xAxis: {
-        type: "category",
-        data: periods,
-        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
-        axisTick: { show: false },
-        axisLabel: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(11),
-          margin: getResponsiveValue(10),
-        },
-        splitLine: { show: false },
-      },
-      yAxis: {
-        type: "value",
-        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
-        axisLabel: {
-          color: "#B8C8E0",
-          fontSize: getResponsiveValue(11),
-          margin: getResponsiveValue(8),
-        },
-        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
-      },
-      series: series,
-    };
-  });
-
-  // 閿�閲忔帓鍚嶅垎鏋愬浘琛ㄩ厤缃�
-  const salesRankingChartOption = computed(() => {
-    const customers = ["瀹㈡埛BB", "瀹㈡埛AA", "瀹㈡埛CC", "瀹㈡埛DD", "瀹㈡埛DD", "瀹㈡埛DD"];
-    const values = [130, 120, 102, 90, 90, 70];
-    const barColors = [
-      "#34D8F7",
-      "#4A8BFF",
-      "#8A6BFF",
-      "#C8C447",
-      "#C8C447",
-      "#C8C447",
     ];
 
     return {
       backgroundColor: "transparent",
       tooltip: {
         trigger: "axis",
-        axisPointer: { type: "shadow" },
         backgroundColor: "rgba(0,0,0,0.55)",
         borderColor: "rgba(64,158,255,0.25)",
         borderWidth: getResponsiveValue(1),
         textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
-        formatter: "{b}: {c} 绔嬫柟绫�",
+      },
+      legend: {
+        data: legendNames,
+        top: "2%",
+        right: "1%",
+        textStyle: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(9),
+        },
+        itemWidth: getResponsiveValue(10),
+        itemHeight: getResponsiveValue(10),
       },
       grid: {
-        left: "14%",
-        right: "6%",
-        top: "16%",
-        bottom: "8%",
+        left: "1%",
+        right: "1%",
+        top: "18%",
+        bottom: xMode === "material" ? "0%" : "1%",
         containLabel: true,
       },
       xAxis: {
-        type: "value",
-        axisLine: { show: false },
-        axisLabel: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
-        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
+        type: "category",
+        data: periods,
+        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
+        axisTick: { show: false },
+        axisLabel: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(11),
+          margin:
+            xMode === "material"
+              ? getResponsiveValue(6)
+              : getResponsiveValue(10),
+          rotate: xMode === "material" && periods.length > 5 ? 28 : 0,
+          interval: 0,
+        },
+        splitLine: { show: false },
       },
       yAxis: {
-        type: "category",
-        data: customers,
-        axisTick: { show: false },
-        axisLine: { show: false },
+        type: "value",
+        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
         axisLabel: {
           color: "#B8C8E0",
           fontSize: getResponsiveValue(11),
           margin: getResponsiveValue(8),
         },
+        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
       },
-      series: [
-        {
-          name: "閿�閲忥紙绔嬫柟绫筹級",
-          type: "bar",
-          barWidth: getResponsiveValue(14),
-          data: values,
-          itemStyle: {
-            color: params => barColors[params.dataIndex] || "#00A4ED",
-            borderRadius: [
-              getResponsiveValue(6),
-              getResponsiveValue(6),
-              getResponsiveValue(6),
-              getResponsiveValue(6),
-            ],
+      series,
+    };
+  });
+
+  // 鏉挎潗鍗曡�楀浘琛ㄩ厤缃紙鎺ュ彛鏁版嵁锛屼笌鐮屽潡缁撴瀯涓�鑷达級
+  const boardCostChartOption = computed(() => {
+    const xMode = boardCostChartSeries.value.xMode || "time";
+    const periods = boardCostChartSeries.value.categories || [];
+    const inputData = boardCostChartSeries.value.input || [];
+    const outputData = boardCostChartSeries.value.output || [];
+    const legendNames = ["鎶曞叆閲�", "浜у嚭閲�"];
+    const colors = ["#00A4ED", "#34D8F7"];
+    const series = [
+      {
+        name: legendNames[0],
+        data: inputData,
+        type: "bar",
+        itemStyle: { color: colors[0] },
+      },
+      {
+        name: legendNames[1],
+        data: outputData,
+        type: "bar",
+        itemStyle: { color: colors[1] },
+      },
+    ];
+
+    return {
+      backgroundColor: "transparent",
+      tooltip: {
+        trigger: "axis",
+        backgroundColor: "rgba(0,0,0,0.55)",
+        borderColor: "rgba(64,158,255,0.25)",
+        borderWidth: getResponsiveValue(1),
+        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
+      },
+      legend: {
+        data: legendNames,
+        top: "2%",
+        right: "1%",
+        textStyle: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(9),
+        },
+        itemWidth: getResponsiveValue(10),
+        itemHeight: getResponsiveValue(10),
+      },
+      grid: {
+        left: "1%",
+        right: "1%",
+        top: "18%",
+        bottom: xMode === "material" ? "0%" : "1%",
+        containLabel: true,
+      },
+      xAxis: {
+        type: "category",
+        data: periods,
+        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
+        axisTick: { show: false },
+        axisLabel: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(11),
+          margin:
+            xMode === "material"
+              ? getResponsiveValue(6)
+              : getResponsiveValue(10),
+          rotate: xMode === "material" && periods.length > 5 ? 28 : 0,
+          interval: 0,
+        },
+        splitLine: { show: false },
+      },
+      yAxis: {
+        type: "value",
+        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
+        axisLabel: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(11),
+          margin: getResponsiveValue(8),
+        },
+        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
+      },
+      series,
+    };
+  });
+
+  // 鐗╂枡鐢熶骇閲忓垎鏋愶紙鎺ュ彛鏁版嵁锛�
+  const productionChartOption = computed(() => {
+    const periods = productionChartSeries.value.categories || [];
+    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",
+      tooltip: {
+        trigger: "axis",
+        backgroundColor: "rgba(0,0,0,0.55)",
+        borderColor: "rgba(64,158,255,0.25)",
+        borderWidth: getResponsiveValue(1),
+        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
+      },
+      legend: { show: false },
+      grid: {
+        left: "1%",
+        right: "1%",
+        bottom: "1%",
+        top: "10%",
+        containLabel: true,
+      },
+      xAxis: {
+        type: "category",
+        data: periods,
+        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
+        axisTick: { show: false },
+        axisLabel: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(11),
+          margin: getResponsiveValue(10),
+        },
+        splitLine: { show: false },
+      },
+      yAxis: {
+        type: "value",
+        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
+        axisLabel: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(11),
+          margin: getResponsiveValue(8),
+        },
+        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
+      },
+      series,
+    };
+  });
+
+  // 鍥哄簾澶勭悊閲忓浘琛紙鎺ュ彛 solidWaste锛�
+  const customerTrendChartOption = computed(() => {
+    const legendNames = ["鍏ㄩ儴", "绮夌叅鐏�", "鐭宠啅", "鐭崇伆"];
+    const colors = ["#00A4ED", "#4A8BFF", "#8A6BFF", "#C8C447"];
+    const periods = solidWasteChartSeries.value.categories || [];
+    const dataBySeries = [
+      solidWasteChartSeries.value.total || [],
+      solidWasteChartSeries.value.flyAsh || [],
+      solidWasteChartSeries.value.gypsum || [],
+      solidWasteChartSeries.value.lime || [],
+    ];
+
+    const series = legendNames.map((name, index) => ({
+      name,
+      data: dataBySeries[index] || [],
+      type: "line",
+      smooth: true,
+      lineStyle: { width: getResponsiveValue(2), color: colors[index] },
+      itemStyle: { color: colors[index] },
+      areaStyle: {
+        opacity: 0.3,
+        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+          { offset: 0, color: colors[index] + "80" },
+          { offset: 1, color: colors[index] + "00" },
+        ]),
+      },
+    }));
+
+    return {
+      backgroundColor: "transparent",
+      tooltip: {
+        trigger: "axis",
+        backgroundColor: "rgba(0,0,0,0.55)",
+        borderColor: "rgba(64,158,255,0.25)",
+        borderWidth: getResponsiveValue(1),
+        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
+      },
+      legend: {
+        data: legendNames,
+        top: "10%",
+        right: "1%",
+        textStyle: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(9),
+        },
+        itemWidth: getResponsiveValue(10),
+        itemHeight: getResponsiveValue(10),
+      },
+      grid: {
+        left: "1%",
+        right: "1%",
+        bottom: "1%",
+        top: "28%",
+        containLabel: true,
+      },
+      xAxis: {
+        type: "category",
+        data: periods,
+        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
+        axisTick: { show: false },
+        axisLabel: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(11),
+          margin: getResponsiveValue(10),
+        },
+        splitLine: { show: false },
+      },
+      yAxis: {
+        type: "value",
+        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
+        axisLabel: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(11),
+          margin: getResponsiveValue(8),
+        },
+        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
+      },
+      series,
+    };
+  });
+
+  // 鑳借�楃粺璁″浘琛ㄩ厤缃紙鎺ュ彛 energy锛�
+  const salesRankingChartOption = computed(() => {
+    const energyTypes = ["姘�", "鐢�", "钂告苯"];
+    const periodType = salesTimeDimension.value;
+    const periods = energyChartSeries.value.categories || [];
+    const waterData = energyChartSeries.value.water || [];
+    const electricityData = energyChartSeries.value.electricity || [];
+    const steamData = energyChartSeries.value.steam || [];
+
+    const series = [
+      {
+        name: "姘�",
+        type: "bar",
+        data: waterData,
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: "#00A4ED" },
+            { offset: 1, color: "#0F285A" },
+          ]),
+          borderRadius: [getResponsiveValue(4), getResponsiveValue(4), 0, 0],
+        },
+        barWidth: getResponsiveValue(6),
+      },
+      {
+        name: "鐢�",
+        type: "line",
+        data: electricityData,
+        itemStyle: {
+          color: "#AC43C2",
+        },
+        lineStyle: {
+          width: getResponsiveValue(1),
+          color: new echarts.graphic.LinearGradient(0, 0, 1, 0, [
+            { offset: 0, color: "#AC43C2" },
+            { offset: 1, color: "#AC43C2" },
+          ]),
+        },
+        symbol: "circle",
+        symbolSize: getResponsiveValue(8),
+        areaStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: "#AC43C250" },
+            { offset: 1, color: "#AC43C203" },
+          ]),
+        },
+      },
+      {
+        name: "钂告苯",
+        type: "bar",
+        data: steamData,
+        itemStyle: {
+          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
+            { offset: 0, color: "#F5BC4A" },
+            { offset: 1, color: "#591C22" },
+          ]),
+          borderRadius: [getResponsiveValue(4), getResponsiveValue(4), 0, 0],
+        },
+        barWidth: getResponsiveValue(6),
+      },
+    ];
+
+    return {
+      tooltip: {
+        trigger: "axis",
+        axisPointer: { type: "cross" },
+        backgroundColor: "rgba(0,0,0,0.7)",
+        borderColor: "rgba(64,158,255,0.5)",
+        borderWidth: getResponsiveValue(1),
+        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(12) },
+        formatter: function (params) {
+          let result = params[0].name + "<br/>";
+          params.forEach(param => {
+            const unit = param.seriesName === "鐢�" ? "搴�" : "鍚�";
+            result += `${param.marker}${param.seriesName}: ${param.value} ${unit}<br/>`;
+          });
+          return result;
+        },
+      },
+      legend: {
+        data: energyTypes,
+        top: "5%",
+        right: "1%",
+        textStyle: {
+          color: "#B8C8E0",
+          fontSize: getResponsiveValue(10),
+        },
+        itemWidth: getResponsiveValue(12),
+        itemHeight: getResponsiveValue(12),
+        itemGap: getResponsiveValue(15),
+      },
+      grid: {
+        left: "1%",
+        right: "1%",
+        top: "25%",
+        bottom: "0%",
+        containLabel: true,
+      },
+      xAxis: {
+        type: "category",
+        data: periods,
+        axisLabel: {
+          fontSize: getResponsiveValue(11),
+          color: "#93B9FF",
+          interval: 0,
+          rotate: periodType === "month" ? 45 : 0,
+        },
+        axisLine: {
+          show: true,
+          lineStyle: {
+            width: getResponsiveValue(1),
+            color: "#305B9A",
           },
         },
-      ],
+        axisTick: {
+          show: false,
+        },
+      },
+      yAxis: {
+        type: "value",
+        axisLabel: {
+          fontSize: getResponsiveValue(11),
+          color: "#93B9FF",
+          formatter: function (value) {
+            return value;
+          },
+        },
+        axisLine: {
+          show: true,
+          lineStyle: {
+            color: "#305B9A",
+          },
+        },
+        splitLine: {
+          lineStyle: {
+            color: "#0F2E60",
+            type: "dashed",
+          },
+        },
+      },
+      series: series,
     };
   });
 
@@ -795,6 +885,447 @@
   // 璁$畻鍝嶅簲寮忓��
   const getResponsiveValue = baseValue => {
     return Math.round((baseValue * window.innerWidth) / baseWidth.value);
+  };
+
+  const mapTimeDimensionToDateType = dim => (dim === "year" ? "2" : "1");
+
+  const productionOutputKey = {
+    鍏ㄩ儴: "totalOutput",
+    鐮屽潡: "blockOutput",
+    鏉挎潗: "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) {
+      blockMaterialSummary.value = {
+        materialName: "",
+        monthlyConsumption: "--",
+        yearlyConsumption: "--",
+      };
+      blockCostChartSeries.value = {
+        xMode: "time",
+        categories: [],
+        input: [],
+        output: [],
+      };
+      updateCharts();
+      return;
+    }
+    const dateType = mapTimeDimensionToDateType(blockTimeDimension.value);
+    try {
+      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] || {};
+      const chartData = Array.isArray(row.chartData) ? row.chartData : [];
+      blockMaterialSummary.value = {
+        materialName: row.materialName || materialName,
+        monthlyConsumption: row.monthlyConsumption ?? "--",
+        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),
+      };
+      updateCharts();
+    } catch (e) {
+      console.error(e);
+    }
+  };
+
+  const loadBoardCost = async () => {
+    const materialName = boardSelectedMaterial.value;
+    if (!materialName) {
+      boardMaterialSummary.value = {
+        materialName: "",
+        monthlyConsumption: "--",
+        yearlyConsumption: "--",
+      };
+      boardCostChartSeries.value = {
+        xMode: "time",
+        categories: [],
+        input: [],
+        output: [],
+      };
+      updateCharts();
+      return;
+    }
+    const dateType = mapTimeDimensionToDateType(boardTimeDimension.value);
+    try {
+      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] || {};
+      const chartData = Array.isArray(row.chartData) ? row.chartData : [];
+      boardMaterialSummary.value = {
+        materialName: row.materialName || materialName,
+        monthlyConsumption: row.monthlyConsumption ?? "--",
+        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),
+      };
+      updateCharts();
+    } catch (e) {
+      console.error(e);
+    }
+  };
+
+  const loadBlockMaterials = async () => {
+    try {
+      const res = await getProductionMaterials({ materialType: "1" });
+      const raw = Array.isArray(res.data) ? [...res.data] : [];
+      const list = ["鍏ㄩ儴", ...raw];
+      blockMaterialList.value = list;
+      if (list.length) {
+        if (!list.includes(blockSelectedMaterial.value)) {
+          blockSelectedMaterial.value = list[0];
+        }
+        await loadBlockCost();
+      } else {
+        blockSelectedMaterial.value = "";
+        blockMaterialSummary.value = {
+          materialName: "",
+          monthlyConsumption: "--",
+          yearlyConsumption: "--",
+        };
+        blockCostChartSeries.value = {
+          xMode: "time",
+          categories: [],
+          input: [],
+          output: [],
+        };
+        updateCharts();
+      }
+    } catch (e) {
+      console.error(e);
+    }
+  };
+
+  const loadBoardMaterials = async () => {
+    try {
+      const res = await getProductionMaterials({ materialType: "2" });
+      const raw = Array.isArray(res.data) ? [...res.data] : [];
+      const list = ["鍏ㄩ儴", ...raw];
+      boardMaterialList.value = list;
+      if (list.length) {
+        if (!list.includes(boardSelectedMaterial.value)) {
+          boardSelectedMaterial.value = list[0];
+        }
+        await loadBoardCost();
+      } else {
+        boardSelectedMaterial.value = "";
+        boardMaterialSummary.value = {
+          materialName: "",
+          monthlyConsumption: "--",
+          yearlyConsumption: "--",
+        };
+        boardCostChartSeries.value = {
+          xMode: "time",
+          categories: [],
+          input: [],
+          output: [],
+        };
+        updateCharts();
+      }
+    } catch (e) {
+      console.error(e);
+    }
+  };
+
+  const loadMaterialProduction = async () => {
+    const dateType = mapTimeDimensionToDateType(productionTimeDimension.value);
+    try {
+      const res = await getMaterialProductionAnalysis({ dateType });
+      const payload = res.data && typeof res.data === "object" ? res.data : {};
+      const cat = productionCategory.value;
+
+      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);
+    }
+  };
+
+  const loadMiddleRingStats = async () => {
+    try {
+      const res = await getProductionStatisticsMiddle();
+      const d = res.data && typeof res.data === "object" ? res.data : {};
+      middleRingStats.value = {
+        flyAshMonth: Number(d.flyAshMonth) || 0,
+        flyAshYear: Number(d.flyAshYear) || 0,
+        gypsumMonth: Number(d.gypsumMonth) || 0,
+        gypsumYear: Number(d.gypsumYear) || 0,
+        blockMonth: Number(d.blockMonth) || 0,
+        blockYear: Number(d.blockYear) || 0,
+        plateMonth: Number(d.plateMonth) || 0,
+        plateYear: Number(d.plateYear) || 0,
+      };
+    } catch (e) {
+      console.error(e);
+    }
+  };
+
+  const loadSolidWasteData = async () => {
+    const dateType = mapTimeDimensionToDateType(customerTimeDimension.value);
+    try {
+      const res = await getProductionStatisticsSolidWaste({ dateType });
+      const list = Array.isArray(res.data) ? res.data : [];
+      solidWasteChartSeries.value = {
+        categories: list.map(i => i.dateStr),
+        total: list.map(i => Number(i.total) || 0),
+        flyAsh: list.map(i => Number(i.flyAsh) || 0),
+        gypsum: list.map(i => Number(i.gypsum) || 0),
+        lime: list.map(i => Number(i.lime) || 0),
+      };
+      updateCharts();
+    } catch (e) {
+      console.error(e);
+    }
+  };
+
+  const loadEnergyData = async () => {
+    const dateType = mapTimeDimensionToDateType(salesTimeDimension.value);
+    try {
+      const res = await getProductionStatisticsEnergy({ dateType });
+      const list = Array.isArray(res.data) ? res.data : [];
+      energyChartSeries.value = {
+        categories: list.map(i => i.dateStr),
+        water: list.map(i => Number(i.water) || 0),
+        electricity: list.map(i => Number(i.electricity) || 0),
+        steam: list.map(i => Number(i.steam) || 0),
+      };
+      updateCharts();
+    } catch (e) {
+      console.error(e);
+    }
+  };
+
+  const selectBlockMaterial = name => {
+    blockSelectedMaterial.value = name;
+    loadBlockCost();
+  };
+
+  const selectBoardMaterial = name => {
+    boardSelectedMaterial.value = name;
+    loadBoardCost();
+  };
+
+  const selectProductionCategory = cat => {
+    productionCategory.value = cat;
+    loadMaterialProduction();
   };
 
   // 鍒濆鍖栧浘琛�
@@ -827,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"],
+      });
     }
 
     // 鏇存柊鏂板瀹㈡埛瓒嬪娍鍥捐〃
@@ -853,49 +1400,34 @@
     if (salesRankingChartInstance) {
       salesRankingChartInstance.setOption(salesRankingChartOption.value);
     }
+
+    resizeCostPanelCharts();
   };
 
   // 澶勭悊鏃堕棿缁村害閫夋嫨
   const handleBlockTimeDimensionChange = dimension => {
     blockTimeDimension.value = dimension;
-    updateCharts();
+    loadBlockCost();
   };
 
   const handleBoardTimeDimensionChange = dimension => {
     boardTimeDimension.value = dimension;
-    updateCharts();
+    loadBoardCost();
   };
 
   const handleProductionTimeDimensionChange = dimension => {
     productionTimeDimension.value = dimension;
-    updateCharts();
+    loadMaterialProduction();
   };
 
   const handleCustomerTimeDimensionChange = dimension => {
     customerTimeDimension.value = dimension;
-    updateCharts();
+    loadSolidWasteData();
   };
 
   const handleSalesTimeDimensionChange = dimension => {
     salesTimeDimension.value = dimension;
-    updateCharts();
-  };
-
-  // 澶勭悊鏉愭枡绫诲瀷閫夋嫨
-  const handleBlockMaterialTypeChange = type => {
-    blockMaterialType.value = type;
-    updateCharts();
-  };
-
-  const handleBoardMaterialTypeChange = type => {
-    boardMaterialType.value = type;
-    updateCharts();
-  };
-
-  // 澶勭悊閿�鍞尯閫夋嫨
-  const handleAreaChange = area => {
-    selectedArea.value = area;
-    updateCharts();
+    loadEnergyData();
   };
 
   // 鐩戝惉绐楀彛澶у皬鍙樺寲
@@ -929,9 +1461,32 @@
       }, 1000);
     }
 
-    // 绛夊緟DOM鏇存柊鍚庡垵濮嬪寲鍥捐〃
-    nextTick(() => {
+    // 绛夊緟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(),
+        loadMaterialProduction(),
+        loadMiddleRingStats(),
+        loadSolidWasteData(),
+        loadEnergyData(),
+      ]);
+      resizeCostPanelCharts();
     });
 
     // 娣诲姞绐楀彛澶у皬鍙樺寲鐩戝惉
@@ -945,6 +1500,11 @@
       clearInterval(timeTicker);
       timeTicker = null;
     }
+
+    blockCostChartResizeObserver?.disconnect();
+    blockCostChartResizeObserver = null;
+    boardCostChartResizeObserver?.disconnect();
+    boardCostChartResizeObserver = null;
 
     if (blockCostChartInstance) {
       blockCostChartInstance.dispose();
@@ -1232,12 +1792,12 @@
   }
 
   /* .scroll-table tbody tr:nth-child(odd) {
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  background-color: rgba(64, 158, 255, 0.05);
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  }
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              background-color: rgba(64, 158, 255, 0.05);
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              }
 
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                .scroll-table tbody tr:nth-child(even) {
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    background-color: rgba(64, 158, 255, 0.1);
-                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      } */
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            .scroll-table tbody tr:nth-child(even) {
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                background-color: rgba(64, 158, 255, 0.1);
+                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  } */
   .oddTableTr {
     background-color: rgba(64, 158, 255, 0.05);
   }
@@ -1352,12 +1912,32 @@
     margin-bottom: 0.2vh;
   }
 
-  .bi-panel-top-left .echart-fill,
-  .bi-panel-bottom-left .echart-fill {
-    height: 24vh;
+  /* 鐮屽潡/鏉挎潗锛氬浘琛ㄥ崰婊� 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 {
+    flex: 1;
+    min-height: 0;
+    height: auto;
+    width: 100%;
+  }
+
   .bi-panel-bottom-right .echart-fill {
     height: calc(100% - 2.8vh);
   }
@@ -1410,11 +1990,11 @@
     grid-row: 1 / span 2;
     position: absolute;
     background: url("@/assets/BI/imageSS@2x.png") no-repeat bottom center;
-    background-size: 100% 30%;
+    background-size: 80% 30%;
     left: 25%;
     top: 25%;
-    transform: translate(-50%, -50%);
-    width: 60vh;
+    transform: translate(-50%, -45%);
+    width: 50%;
     height: 40.5vh;
     z-index: 3;
     pointer-events: none;
@@ -1422,11 +2002,103 @@
   .center-ring-box {
     position: absolute;
     /* inset: 0; */
-    height: 100%;
+    height: 88%;
     width: 100%;
+    margin-top: 3%;
     /* background-color: #fff; */
-    background: url("@/assets/BI/imageSStop.png") no-repeat center center;
-    background-size: 80% 90%;
+    background: url("@/assets/BI/SCbg.png") no-repeat center center;
+    background-size: 100% 100%;
+  }
+  .ring-box-topright {
+    position: absolute;
+    top: 6vh;
+    right: 0;
+    width: 25%;
+    height: 15%;
+    background: url("@/assets/BI/SCbgright.png") no-repeat center center;
+    background-size: 100% 100%;
+    text-align: right;
+  }
+  .ring-box-topleft {
+    position: absolute;
+    top: 6vh;
+    left: 0;
+    width: 25%;
+    height: 15%;
+    background: url("@/assets/BI/SCbgleft.png") no-repeat center center;
+    background-size: 100% 100%;
+    text-align: left;
+  }
+  .topright-label {
+    font-size: 1.8vh;
+    font-weight: 500;
+    color: rgba(234, 246, 255, 0.9);
+    margin-top: 0;
+    position: relative;
+    bottom: 3vh;
+    right: 1vh;
+  }
+
+  .topleft-label {
+    font-size: 1.8vh;
+    font-weight: 500;
+    color: rgba(234, 246, 255, 0.9);
+    margin-top: 0;
+    position: relative;
+    bottom: 3vh;
+    left: 1vh;
+  }
+  .ring-box-left {
+    /* background-color: #ebebeb; */
+    width: 30%;
+    position: absolute;
+    left: 1vh;
+    top: 56%;
+    transform: translateY(-50%);
+  }
+  .left-label {
+    font-size: 1.4vh;
+    font-weight: 500;
+    color: #0effef;
+    margin-top: 0;
+    position: relative;
+    bottom: 3vh;
+  }
+  .left-value {
+    font-size: 1.2vh;
+    font-weight: 500;
+    color: rgba(234, 246, 255, 0.9);
+    margin-top: 0;
+    position: relative;
+    bottom: 3vh;
+    margin-top: 0.4vh;
+  }
+  .ring-box-right {
+    /* background-color: #ebebeb; */
+    width: 30%;
+    float: right;
+    position: absolute;
+    right: -1vh;
+    top: 56%;
+    transform: translateY(-50%);
+  }
+  .right-label {
+    font-size: 1.4vh;
+    font-weight: 500;
+    color: #ffa60e;
+    margin-top: 0;
+    position: relative;
+    bottom: 3vh;
+  }
+
+  .right-value {
+    font-size: 1.2vh;
+    font-weight: 500;
+    color: rgba(234, 246, 255, 0.9);
+    margin-top: 0;
+    position: relative;
+    bottom: 3vh;
+    margin-top: 0.4vh;
   }
 
   .center-ring-bg {
@@ -1632,7 +2304,7 @@
     text-align: left;
     color: #c3c3c3;
   }
-    /* 鏉愭枡淇℃伅鍗$墖 */
+  /* 鏉愭枡淇℃伅鍗$墖 */
   .material-info-card {
     display: flex;
     align-items: center;
@@ -1691,5 +2363,4 @@
     font-size: 1vh;
     opacity: 0.7;
   }
-
 </style>

--
Gitblit v1.9.3