src/views/reportAnalysis/productionStatistics/index.vue
@@ -55,13 +55,14 @@
        </div>
        <div class="bi-panel-body">
          <div class="chart-filter-tabs">
            <span v-for="area in salesAreas"
                  :key="area"
            <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"
@@ -74,16 +75,16 @@
              </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>
@@ -109,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>
@@ -124,21 +132,20 @@
          </div>
          <div class="ring-box-left">
            <div class="left-label">粉煤灰</div>
            <div class="left-value">月处理 <span style="font-weight: bold;font-size: 1.3vh;">7812</span> 吨 年处理 <span style="font-weight: bold;font-size: 1.3vh;">7812</span> 吨</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;">7812</span> 吨 年处理 <span style="font-weight: bold;font-size: 1.3vh;">7812</span> 吨</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="ring-box-topleft">
            <div class="topleft-label">项目产量</div>
          </div>
          <div class="ring-box-right">
            <div class="right-label">砌块产量</div>
            <div class="right-value">月产量 <span style="font-weight: bold;font-size: 1.3vh;">7812</span> 吨 年产量 <span style="font-weight: bold;font-size: 1.3vh;">7812
              </span> 吨</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;">7812</span> 吨 年产量 <span style="font-weight: bold;font-size: 1.3vh;">7812</span> 吨</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>
@@ -156,13 +163,14 @@
        </div>
        <div class="bi-panel-body">
          <div class="chart-filter-tabs">
            <span v-for="area in salesAreas"
                  :key="area"
            <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"
@@ -175,16 +183,16 @@
              </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>
@@ -246,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);
@@ -303,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;
@@ -334,47 +400,31 @@
  let productionChartInstance = null;
  let customerTrendChartInstance = null;
  let salesRankingChartInstance = null;
  let blockCostChartResizeObserver = null;
  let boardCostChartResizeObserver = null;
  // 生产单耗图表配置
  // 生产单耗图表配置(砌块,接口数据)
  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] },
      };
    });
        itemStyle: { color: colors[0] },
      },
      {
        name: legendNames[1],
        data: outputData,
        type: "bar",
        itemStyle: { color: colors[1] },
      },
    ];
    return {
      backgroundColor: "transparent",
@@ -385,22 +435,22 @@
        borderWidth: getResponsiveValue(1),
        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
      },
      // legend: {
      //   data: materials,
      //   top: "10%",
      //   right: "1%",
      //   textStyle: {
      //     color: "#B8C8E0",
      //     fontSize: getResponsiveValue(9),
      //   },
      //   itemWidth: getResponsiveValue(10),
      //   itemHeight: getResponsiveValue(10),
      // },
      legend: {
        data: legendNames,
        top: "2%",
        right: "1%",
        textStyle: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(9),
        },
        itemWidth: getResponsiveValue(10),
        itemHeight: getResponsiveValue(10),
      },
      grid: {
        left: "1%",
        right: "1%",
        bottom: "1%",
        top: "8%",
        top: "18%",
        bottom: xMode === "material" ? "0%" : "1%",
        containLabel: true,
      },
      xAxis: {
@@ -411,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 },
      },
@@ -425,167 +480,32 @@
        },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series: 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,
    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[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,
      {
        name: legendNames[1],
        data: outputData,
        type: "bar",
        itemStyle: { color: colors[1] },
      },
      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",
@@ -597,8 +517,8 @@
        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
      },
      legend: {
        data: salesAreas,
        top: "10%",
        data: legendNames,
        top: "2%",
        right: "1%",
        textStyle: {
          color: "#B8C8E0",
@@ -610,8 +530,105 @@
      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: "28%",
        top: "10%",
        containLabel: true,
      },
      xAxis: {
@@ -636,59 +653,37 @@
        },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series: series,
      series,
    };
  });
  // 新增客户趋势图表配置
  // 固废处理量图表(接口 solidWaste)
  const customerTrendChartOption = computed(() => {
    const customerTypes = ["全部", "粉煤灰", "石膏", "石灰"];
    const legendNames = ["全部", "粉煤灰", "石膏", "石灰"];
    const colors = ["#00A4ED", "#4A8BFF", "#8A6BFF", "#C8C447"];
    const year = 2024;
    const periodType = customerTimeDimension.value;
    const periods = solidWasteChartSeries.value.categories || [];
    const dataBySeries = [
      solidWasteChartSeries.value.total || [],
      solidWasteChartSeries.value.flyAsh || [],
      solidWasteChartSeries.value.gypsum || [],
      solidWasteChartSeries.value.lime || [],
    ];
    // 生成时间段
    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" },
          ]),
        },
      };
    });
    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",
@@ -700,7 +695,7 @@
        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
      },
      legend: {
        data: customerTypes,
        data: legendNames,
        top: "10%",
        right: "1%",
        textStyle: {
@@ -739,51 +734,18 @@
        },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series: series,
      series,
    };
  });
  // 能耗统计图表配置
  // 能耗统计图表配置(接口 energy)
  const salesRankingChartOption = computed(() => {
    const energyTypes = ["水", "电", "蒸汽"];
    const colors = ["#00A4ED", "#AC43C2", "#F5BC4A"];
    const year = 2024;
    const periodType = salesTimeDimension.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 {
      // 月度数据:7天
      const month = 1;
      for (let day = 1; day <= 7; day++) {
        periods.push(`${month}/${day}`);
      }
    }
    // 为每种能源类型生成数据
    const waterData = periods.map(() => {
      return periodType === "year"
        ? Math.floor(Math.random() * 300) + 400
        : Math.floor(Math.random() * 30) + 40;
    });
    const steamData = periods.map(() => {
      return periodType === "year"
        ? Math.floor(Math.random() * 400) + 500
        : Math.floor(Math.random() * 40) + 50;
    });
    const electricityData = periods.map(() => {
      return periodType === "year"
        ? Math.floor(Math.random() * 200) + 300
        : Math.floor(Math.random() * 20) + 30;
    });
    const periods = energyChartSeries.value.categories || [];
    const waterData = energyChartSeries.value.water || [];
    const electricityData = energyChartSeries.value.electricity || [];
    const steamData = energyChartSeries.value.steam || [];
    const series = [
      {
@@ -925,6 +887,447 @@
    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();
  };
  // 初始化图表
  const initCharts = () => {
    // 初始化砌块成本图表
@@ -955,21 +1358,37 @@
    updateCharts();
  };
  /** 砌块/板材容器高度变化后(如隐藏汇总卡)需 resize,否则底部留白 */
  const resizeCostPanelCharts = () => {
    nextTick(() => {
      requestAnimationFrame(() => {
        blockCostChartInstance?.resize();
        boardCostChartInstance?.resize();
      });
    });
  };
  // 更新图表
  const updateCharts = () => {
    // 更新砌块成本图表
    // 更新砌块成本图表(replaceMerge:全部↔单物料时横轴时间/物料切换)
    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"],
      });
    }
    // 更新产量分析图表
    // 更新产量分析图表(replaceMerge:全部↔砌块/板材切换时去掉多余折线,避免合并残留)
    if (productionChartInstance) {
      productionChartInstance.setOption(productionChartOption.value);
      productionChartInstance.setOption(productionChartOption.value, {
        replaceMerge: ["series"],
      });
    }
    // 更新新增客户趋势图表
@@ -981,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();
  };
  // 监听窗口大小变化
@@ -1057,9 +1461,32 @@
      }, 1000);
    }
    // 等待DOM更新后初始化图表
    nextTick(() => {
    // 等待DOM更新后初始化图表并拉取接口数据
    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();
    });
    // 添加窗口大小变化监听
@@ -1073,6 +1500,11 @@
      clearInterval(timeTicker);
      timeTicker = null;
    }
    blockCostChartResizeObserver?.disconnect();
    blockCostChartResizeObserver = null;
    boardCostChartResizeObserver?.disconnect();
    boardCostChartResizeObserver = null;
    if (blockCostChartInstance) {
      blockCostChartInstance.dispose();
@@ -1480,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 {