yyb
3 天以前 7f5d173ccb67eca6f823e3c9bb3f5770c12d4704
添加生产统计API并更新生产统计视图
已添加1个文件
已修改1个文件
954 ■■■■■ 文件已修改
src/api/reportAnalysis/productionStatistics.js 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/productionStatistics/index.vue 836 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/reportAnalysis/productionStatistics.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,118 @@
import request from '@/utils/request'
/**
 * ç‰©æ–™ç”Ÿäº§é‡åˆ†æž
 * GET /home/productionStatistics/materialProductionAnalysis
 *
 * å…¥å‚由后端约定(年月切换、砌块/板材/全部等),通过 params åŽŸæ ·ä¼ é€’ã€‚
 *
 * å“åº” data ç¤ºä¾‹ç»“构:
 * {
 *   å…¨éƒ¨: [{ dateStr, totalOutput, blockOutput, plateOutput }],
 *   ç Œå—: [...],
 *   æ¿æ: [...]
 * }
 */
export function getMaterialProductionAnalysis(params) {
  return request({
    url: '/home/productionStatistics/materialProductionAnalysis',
    method: 'get',
    params,
  })
}
/**
 * ç”Ÿäº§æˆæœ¬å•耗统计(砌块/板材)产品列表
 * GET /home/productionStatistics/materials
 *
 * @param {Object} [params]
 * @param {string} [params.materialType] å¯é€‰ã€‚'1' ç Œå— / '2' æ¿æ
 * @returns {Promise<{ data?: string[] }>} data ä¸ºäº§å“åç§°å­—符串数组,如 ["板材","æ°´æ³¥"]
 */
export function getProductionMaterials(params) {
  return request({
    url: '/home/productionStatistics/materials',
    method: 'get',
    params,
  })
}
/**
 * ç”Ÿäº§æˆæœ¬å•耗统计(砌块)
 * GET /home/productionStatistics/blocks
 *
 * @param {Object} [params]
 * @param {string} [params.materialName] ç‰©æ–™åç§°
 * @param {string} [params.dateType] '1' æœˆ / '2' å¹´
 */
export function getProductionStatisticsBlocks(params) {
  return request({
    url: '/home/productionStatistics/blocks',
    method: 'get',
    params,
  })
}
/**
 * ç”Ÿäº§æˆæœ¬å•耗统计(板材)
 * GET /home/productionStatistics/plates
 *
 * @param {Object} [params]
 * @param {string} [params.materialName] ç‰©æ–™åç§°
 * @param {string} [params.dateType] '1' æœˆ / '2' å¹´
 */
export function getProductionStatisticsPlates(params) {
  return request({
    url: '/home/productionStatistics/plates',
    method: 'get',
    params,
  })
}
/**
 * ä¸­å¿ƒçŽ¯ï¼šé¡¹ç›®äº§é‡ï¼ˆç Œå—/板材)与固废处理量(粉煤灰/石膏)
 * GET /home/productionStatistics/middle
 *
 * data: flyAshMonth/Year, gypsumMonth/Year, blockMonth/Year, plateMonth/Year
 */
export function getProductionStatisticsMiddle(params) {
  return request({
    url: '/home/productionStatistics/middle',
    method: 'get',
    params,
  })
}
/**
 * å›ºåºŸå¤„理量趋势
 * GET /home/productionStatistics/solidWaste
 *
 * @param {Object} params
 * @param {string} params.dateType '1' æœˆ / '2' å¹´
 *
 * data[]: dateStr, total, flyAsh, gypsum, lime
 */
export function getProductionStatisticsSolidWaste(params) {
  return request({
    url: '/home/productionStatistics/solidWaste',
    method: 'get',
    params,
  })
}
/**
 * èƒ½è€—统计(水/电/蒸汽)
 * GET /home/productionStatistics/energy
 *
 * @param {Object} params
 * @param {string} params.dateType '1' æœˆ / '2' å¹´
 *
 * data[]: dateStr, water, electricity, steam
 */
export function getProductionStatisticsEnergy(params) {
  return request({
    url: '/home/productionStatistics/energy',
    method: 'get',
    params,
  })
}
src/views/reportAnalysis/productionStatistics/index.vue
@@ -55,11 +55,11 @@
        </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 class="material-icon">
@@ -74,16 +74,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 +109,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 +131,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,11 +162,11 @@
        </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 class="material-icon">
@@ -175,16 +181,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 +252,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 +317,73 @@
  // é€‰æ‹©å™¨æ•°æ®
  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: "--",
  });
  const blockCostChartSeries = ref({
    categories: [],
    input: [],
    output: [],
  });
  const boardCostChartSeries = ref({
    categories: [],
    input: [],
    output: [],
  });
  const productionChartSeries = ref({
    categories: [],
    values: [],
  });
  // å›ºåºŸå¤„理量折线图(/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;
@@ -335,46 +392,27 @@
  let customerTrendChartInstance = null;
  let salesRankingChartInstance = 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 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 +423,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%",
        containLabel: true,
      },
      xAxis: {
@@ -425,167 +463,31 @@
        },
        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 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,7 +499,84 @@
        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
      },
      legend: {
        data: salesAreas,
        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: "18%",
        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,
    };
  });
  // ç‰©æ–™ç”Ÿäº§é‡åˆ†æžï¼ˆæŽ¥å£æ•°æ®ï¼‰
  const productionChartOption = computed(() => {
    const periods = productionChartSeries.value.categories || [];
    const values = productionChartSeries.value.values || [];
    const lineColor = "#4A8BFF";
    const seriesName = "产量";
    const series = [
      {
        name: seriesName,
        data: values,
        type: "line",
        smooth: true,
        lineStyle: { width: getResponsiveValue(2), color: lineColor },
        itemStyle: { color: lineColor },
        areaStyle: {
          opacity: 0.3,
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: lineColor + "80" },
            { offset: 1, color: lineColor + "00" },
          ]),
        },
      },
    ];
    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: [seriesName],
        top: "10%",
        right: "1%",
        textStyle: {
@@ -636,59 +615,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 +657,7 @@
        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
      },
      legend: {
        data: customerTypes,
        data: legendNames,
        top: "10%",
        right: "1%",
        textStyle: {
@@ -739,51 +696,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 +849,221 @@
    return Math.round((baseValue * window.innerWidth) / baseWidth.value);
  };
  const mapTimeDimensionToDateType = dim => (dim === "year" ? "2" : "1");
  const productionOutputKey = {
    å…¨éƒ¨: "totalOutput",
    ç Œå—: "blockOutput",
    æ¿æ: "plateOutput",
  };
  const loadBlockCost = async () => {
    const materialName = blockSelectedMaterial.value;
    if (!materialName) {
      blockMaterialSummary.value = {
        materialName: "",
        monthlyConsumption: "--",
        yearlyConsumption: "--",
      };
      blockCostChartSeries.value = { categories: [], input: [], output: [] };
      updateCharts();
      return;
    }
    const dateType = mapTimeDimensionToDateType(blockTimeDimension.value);
    try {
      const res = await getProductionStatisticsBlocks({ materialName, dateType });
      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 = {
        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 = { categories: [], input: [], output: [] };
      updateCharts();
      return;
    }
    const dateType = mapTimeDimensionToDateType(boardTimeDimension.value);
    try {
      const res = await getProductionStatisticsPlates({ materialName, dateType });
      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 = {
        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 list = Array.isArray(res.data) ? [...res.data] : [];
      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 = { categories: [], input: [], output: [] };
        updateCharts();
      }
    } catch (e) {
      console.error(e);
    }
  };
  const loadBoardMaterials = async () => {
    try {
      const res = await getProductionMaterials({ materialType: "2" });
      const list = Array.isArray(res.data) ? [...res.data] : [];
      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 = { 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;
      const list = Array.isArray(payload[cat]) ? payload[cat] : [];
      const field = productionOutputKey[cat] || "totalOutput";
      productionChartSeries.value = {
        categories: list.map(i => i.dateStr),
        values: list.map(i => Number(i[field]) || 0),
      };
      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 = () => {
    // åˆå§‹åŒ–砌块成本图表
@@ -986,44 +1125,27 @@
  // å¤„理时间维度选择
  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 +1179,17 @@
      }, 1000);
    }
    // ç­‰å¾…DOM更新后初始化图表
    nextTick(() => {
    // ç­‰å¾…DOM更新后初始化图表并拉取接口数据
    nextTick(async () => {
      initCharts();
      await Promise.all([
        loadBlockMaterials(),
        loadBoardMaterials(),
        loadMaterialProduction(),
        loadMiddleRingStats(),
        loadSolidWasteData(),
        loadEnergyData(),
      ]);
    });
    // æ·»åŠ çª—å£å¤§å°å˜åŒ–ç›‘å¬