zhangwencui
5 小时以前 9b8563141470990db2905f27ab5e893324afeba1
销售统计看板重构
已修改1个文件
1136 ■■■■ 文件已修改
src/views/reportAnalysis/salesStatistics/index.vue 1136 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/salesStatistics/index.vue
@@ -47,22 +47,26 @@
      <!-- 左上:销量趋势 -->
      <div class="bi-panel bi-panel-top-left">
        <PanelHeader :isFullscreen="true"
                     title="销售分析-砌块" />
                     title="销量分析趋势图" />
        <div class="panel-tabs">
          <span class="tab-item active">年</span>
          <span class="tab-item">月</span>
          <span class="tab-item"
                :class="{ active: blockTimeDimension === 'year' }"
                @click="handleBlockTimeDimensionChange('year')">年</span>
          <span class="tab-item"
                :class="{ active: blockTimeDimension === 'month' }"
                @click="handleBlockTimeDimensionChange('month')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: blockProductType === '砌块' }"
                @click="handleBlockProductTypeChange('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: blockProductType === '板材' }"
                @click="handleBlockProductTypeChange('板材')">板材</span>
        </div>
        <div class="bi-panel-body">
          <div class="chart-filter-tabs">
            <span class="cf-tab active">***销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
          </div>
          <div class="chart-unit-row">
            <span>单位:立方米</span>
            <span class="dot-legend">板材</span>
          </div>
          <div ref="salesVolumeChart"
               class="echart-fill"></div>
@@ -71,22 +75,26 @@
      <!-- 右上:销售金额 -->
      <div class="bi-panel bi-panel-top-right">
        <PanelHeader :isFullscreen="true"
                     title="销售分析-板材" />
                     title="销售金额分析" />
        <div class="panel-tabs">
          <span class="tab-item active">年</span>
          <span class="tab-item">月</span>
          <span class="tab-item"
                :class="{ active: boardTimeDimension === 'year' }"
                @click="handleBoardTimeDimensionChange('year')">年</span>
          <span class="tab-item"
                :class="{ active: boardTimeDimension === 'month' }"
                @click="handleBoardTimeDimensionChange('month')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: boardProductType === '砌块' }"
                @click="handleBoardProductTypeChange('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: boardProductType === '板材' }"
                @click="handleBoardProductTypeChange('板材')">板材</span>
        </div>
        <div class="bi-panel-body">
          <div class="chart-filter-tabs">
            <span class="cf-tab active">***销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
          </div>
          <div class="chart-unit-row">
            <span>单位:件</span>
            <span class="dot-legend">板材</span>
            <span>单位:元</span>
          </div>
          <div ref="salesAmountChart"
               class="echart-fill"></div>
@@ -94,37 +102,11 @@
      </div>
      <!-- 中间中心环 -->
      <div class="center-ring">
        <!-- <img class="center-ring-bg"
             src="@/assets/BI/zonghetongbingtubiankuang@2x.png"
             alt="" /> -->
        <!-- <div class="center-ring-content"> -->
        <!-- <div class="center-ring-title">销售<br />中心</div>
          <div class="center-metric m1">
            <div class="center-metric-label">新增客户</div>
            <div class="center-metric-value">{{ centerNewCustomerCount }}</div>
            <div class="center-metric-unit">人</div>
          </div>
          <div class="center-metric m2">
            <div class="center-metric-label">成交总订单</div>
            <div class="center-metric-value">{{ completedOrders }}</div>
            <div class="center-metric-unit">单</div>
          </div>
          <div class="center-metric m3">
            <div class="center-metric-label">新增订单</div>
            <div class="center-metric-value">{{ salesOrderCount }}</div>
            <div class="center-metric-unit">单</div>
          </div>
          <div class="center-metric m4">
            <div class="center-metric-label">总销售区</div>
            <div class="center-metric-value">{{ totalSalesAreaCount }}</div>
            <div class="center-metric-unit">区</div>
          </div> -->
        <!-- </div> -->
        <div class="center-ring-box">
          <div class="center-metric m1">
            <div class="center-metric-label">新增客户</div>
            <div class="center-metric-value">{{ centerNewCustomerCount }}</div>
            <div class="center-metric-unit">人</div>
            <div class="center-metric-label">总销售金额</div>
            <div class="center-metric-value">{{ totalSalesAmount.toFixed(0) }}</div>
            <div class="center-metric-unit">万元</div>
          </div>
          <div class="center-metric m2">
            <div class="center-metric-label">成交总订单</div>
@@ -132,9 +114,9 @@
            <div class="center-metric-unit">单</div>
          </div>
          <div class="center-metric m3">
            <div class="center-metric-label">新增订单</div>
            <div class="center-metric-value">{{ salesOrderCount }}</div>
            <div class="center-metric-unit">单</div>
            <div class="center-metric-label">累计客户</div>
            <div class="center-metric-value">{{ centerNewCustomerCount }}</div>
            <div class="center-metric-unit">家</div>
          </div>
          <div class="center-metric m4">
            <div class="center-metric-label">总销售区</div>
@@ -146,21 +128,61 @@
      <!-- 左下:产品类型销量 -->
      <div class="bi-panel bi-panel-bottom-left">
        <PanelHeader :isFullscreen="true"
                     title="客户销量排名分析-砌块" />
                     title="销量数据-排名分析" />
        <div class="panel-tabs">
          <span class="tab-item active">年</span>
          <span class="tab-item">月</span>
          <span class="tab-item"
                :class="{ active: blockTimeDimension === 'year' }"
                @click="handleBlockTimeDimensionChange('year')">年</span>
          <span class="tab-item"
                :class="{ active: blockTimeDimension === 'month' }"
                @click="handleBlockTimeDimensionChange('month')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: blockProductType === '砌块' }"
                @click="handleBlockProductTypeChange('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: blockProductType === '板材' }"
                @click="handleBlockProductTypeChange('板材')">板材</span>
        </div>
        <div class="bi-panel-body">
          <div class="chart-filter-tabs">
            <span class="cf-tab active">***销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span v-for="area in salesAreas"
                  :key="area"
                  class="cf-tab"
                  :class="{ active: blockSelectedArea === area }"
                  @click="handleBlockAreaChange(area)">{{ area }}</span>
          </div>
          <div ref="productTypeChart"
               class="echart-fill"></div>
          <div class="scroll-table-container">
            <table class="scroll-table">
              <thead>
                <tr>
                  <th>排名</th>
                  <th>产品类型</th>
                  <th>年月</th>
                  <th>销售区</th>
                  <th>销量(m³)</th>
                </tr>
              </thead>
              <div class="scroll-table-content">
                <tbody ref="blockTableBody">
                  <tr :class="item.sort % 2 === 0 ? 'evenTableTr' : 'oddTableTr'"
                      v-for="(item, index) in blockSalesData"
                      :key="item.period + item.area + index">
                    <td>{{ item.sort }}</td>
                    <td>{{ item.productType }}</td>
                    <td>{{ item.period }}</td>
                    <td>{{ item.area }}</td>
                    <td>{{ item.sales }}</td>
                  </tr>
                </tbody>
              </div>
            </table>
          </div>
          <div class="panel-summary-row">
            <div class="summary-label">合计</div>
            <div class="summary-value">127384 m³</div>
          </div>
        </div>
      </div>
      <!-- 中下:新增客户分析(分产品类型趋势) -->
@@ -168,14 +190,14 @@
        <PanelHeader :isFullscreen="true"
                     title="新增客户趋势分析" />
        <div class="panel-tabs">
          <span class="tab-item active">年</span>
          <span class="tab-item">月</span>
          <span class="tab-item"
                :class="{ active: customerTimeDimension === 'year' }"
                @click="handleCustomerTimeDimensionChange('year')">年</span>
          <span class="tab-item"
                :class="{ active: customerTimeDimension === 'month' }"
                @click="handleCustomerTimeDimensionChange('month')">月</span>
        </div>
        <div class="bi-panel-body">
          <div class="chart-mini-title">
            <span class="diamond"></span>
            <span>新增客户数</span>
          </div>
          <div class="chart-unit-row chart-unit-single">
            <span>单位:人</span>
          </div>
@@ -186,21 +208,59 @@
      <!-- 右下:销售区域销量 -->
      <div class="bi-panel bi-panel-bottom-right">
        <PanelHeader :isFullscreen="true"
                     title="客户销量排名分析-板材" />
                     title="销售额数据-排名分析" />
        <div class="panel-tabs">
          <span class="tab-item active">年</span>
          <span class="tab-item">月</span>
          <span class="tab-item"
                :class="{ active: boardTimeDimension === 'year' }"
                @click="handleBoardTimeDimensionChange('year')">年</span>
          <span class="tab-item"
                :class="{ active: boardTimeDimension === 'month' }"
                @click="handleBoardTimeDimensionChange('month')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: boardProductType === '砌块' }"
                @click="handleBoardProductTypeChange('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: boardProductType === '板材' }"
                @click="handleBoardProductTypeChange('板材')">板材</span>
        </div>
        <div class="bi-panel-body">
          <div class="chart-filter-tabs">
            <span class="cf-tab active">***销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span class="cf-tab">xxx销售区</span>
            <span v-for="area in salesAreas"
                  :key="area"
                  class="cf-tab"
                  :class="{ active: boardSelectedArea === area }"
                  @click="handleBoardAreaChange(area)">{{ area }}</span>
          </div>
          <div ref="salesAreaChart"
               class="echart-fill"></div>
          <div class="scroll-table-container">
            <table class="scroll-table">
              <thead>
                <tr>
                  <th>排名</th>
                  <th>年月</th>
                  <th>销售区</th>
                  <th>销售额(万元)</th>
                </tr>
              </thead>
              <div class="scroll-table-content">
                <tbody ref="boardTableBody">
                  <tr :class="item.sort % 2 === 0 ? 'evenTableTr' : 'oddTableTr'"
                      v-for="(item, index) in boardSalesData"
                      :key="item.period + item.area + index">
                    <td>{{ item.sort }}</td>
                    <td>{{ item.period }}</td>
                    <td>{{ item.area }}</td>
                    <td>{{ item.sales }}</td>
                  </tr>
                </tbody>
              </div>
            </table>
          </div>
          <div class="panel-summary-row">
            <div class="summary-label">合计</div>
            <div class="summary-value2">127384 万元</div>
          </div>
        </div>
      </div>
    </div>
@@ -220,6 +280,7 @@
  import * as echarts from "echarts";
  import dayjs from "dayjs";
  import PanelHeader from "@/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue";
  import { findAllQualifiedStockOutRecordTypeOptions } from "../../../api/basicData/enum";
  const router = useRouter();
  const screenRoot = ref(null);
@@ -275,6 +336,53 @@
  const productTypeChart = ref(null);
  const salesAreaChart = ref(null);
  const productTypeTrendChart = ref(null);
  const blockTableBody = ref(null);
  const boardTableBody = ref(null);
  // 选择器数据
  const blockTimeDimension = ref("year");
  const blockSelectedArea = ref("全部");
  const blockProductType = ref("砌块");
  const boardTimeDimension = ref("year");
  const boardSelectedArea = ref("全部");
  const boardProductType = ref("板材");
  const customerTimeDimension = ref("year");
  const salesAreas = [
    "全部",
    "A销售区",
    "B销售区",
    "C销售区",
    "D销售区",
    "E销售区",
  ];
  // 表格数据
  const blockSalesData = ref([
    { period: "2024-01", area: "***销售区", sales: 1250, sort: 1 },
    { period: "2024-02", area: "xxx销售区", sales: 1180, sort: 2 },
    { period: "2024-03", area: "xxx销售区", sales: 1050, sort: 3 },
    { period: "2024-04", area: "xxx销售区", sales: 980, sort: 4 },
    { period: "2024-05", area: "xxx销售区", sales: 920, sort: 5 },
    { period: "2024-06", area: "***销售区", sales: 880, sort: 6 },
    { period: "2024-07", area: "xxx销售区", sales: 850, sort: 7 },
    { period: "2024-08", area: "***销售区", sales: 820, sort: 8 },
    { period: "2024-09", area: "xxx销售区", sales: 790, sort: 9 },
    { period: "2024-10", area: "***销售区", sales: 750, sort: 10 },
  ]);
  const boardSalesData = ref([
    { period: "2024-01", area: "***销售区", sales: 980, sort: 1 },
    { period: "2024-02", area: "xxx销售区", sales: 920, sort: 2 },
    { period: "2024-03", area: "***销售区", sales: 880, sort: 3 },
    { period: "2024-04", area: "xxx销售区", sales: 850, sort: 4 },
    { period: "2024-05", area: "xxx销售区", sales: 820, sort: 5 },
    { period: "2024-06", area: "***销售区", sales: 790, sort: 6 },
    { period: "2024-07", area: "xxx销售区", sales: 750, sort: 7 },
    { period: "2024-08", area: "xxx销售区", sales: 720, sort: 8 },
    { period: "2024-09", area: "***销售区", sales: 690, sort: 9 },
    { period: "2024-10", area: "xxx销售区", sales: 650, sort: 10 },
  ]);
  const cumulativeSalesVolumeChart = ref(null);
  const cumulativeSalesAmountChart = ref(null);
@@ -541,11 +649,7 @@
    return filteredData.value.reduce((sum, item) => sum + item.salesVolume, 0);
  });
  const totalSalesAmount = computed(() => {
    return filteredData.value
      .reduce((sum, item) => sum + item.salesAmount, 0)
      .toFixed(2);
  });
  const totalSalesAmount = ref(1299);
  const newCustomerCount = computed(() => {
    return filteredData.value.reduce((sum, item) => sum + item.newCustomers, 0);
@@ -594,8 +698,70 @@
  // 销量趋势图表配置
  const salesVolumeChartOption = computed(() => {
    const periods = ["6/9", "6/10", "6/11", "6/12", "6/12", "6/13"];
    const values = [132, 168, 168, 198, 168, 198];
    // 为每个销售区生成数据
    const salesAreas = [
      "全部",
      "A销售区",
      "B销售区",
      "C销售区",
      "D销售区",
      "E销售区",
    ];
    const colors = [
      "#00A4ED",
      "#34D8F7",
      "#4A8BFF",
      "#8A6BFF",
      "#C8C447",
      "#FF6B6B",
    ];
    const year = 2024;
    const periodType = blockTimeDimension.value;
    // 生成时间段
    let periods = [];
    if (periodType === "year") {
      // 年度数据:12个月
      for (let month = 1; month <= 12; month++) {
        periods.push(`${year}-${month.toString().padStart(2, "0")}`);
      }
    } else {
      // 月度数据:30天
      const month = 1;
      for (let day = 1; day <= 30; day++) {
        periods.push(
          `${year}-${month.toString().padStart(2, "0")}-${day
            .toString()
            .padStart(2, "0")}`
        );
      }
    }
    // 为每个销售区生成数据
    const series = salesAreas.map((area, index) => {
      const data = periods.map(() => {
        return periodType === "year"
          ? Math.floor(Math.random() * 500) + 800
          : Math.floor(Math.random() * 50) + 20;
      });
      return {
        name: area,
        data: data,
        type: "line",
        smooth: false,
        // symbolSize: getResponsiveValue(8),
        lineStyle: { width: getResponsiveValue(1), color: colors[index] },
        itemStyle: { color: colors[index] },
        areaStyle: {
          opacity: 0.4,
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: colors[index] + "80" },
            { offset: 1, color: colors[index] + "00" },
          ]),
        },
      };
    });
    return {
      backgroundColor: "transparent",
@@ -605,12 +771,29 @@
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: getResponsiveValue(1),
        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
        formatter: "{b}: {c} 立方米",
        formatter: function (params) {
          let result = params[0].name + "<br/>";
          params.forEach(param => {
            result += `${param.marker}${param.seriesName}: ${param.value} 立方米<br/>`;
          });
          return result;
        },
      },
      legend: {
        data: salesAreas,
        top: "10%",
        right: "1%",
        textStyle: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(9),
        },
        itemWidth: getResponsiveValue(10),
        itemHeight: getResponsiveValue(10),
      },
      grid: {
        left: "10%",
        right: "4%",
        bottom: "16%",
        left: "1%",
        right: "1%",
        bottom: "1%",
        top: "28%",
        containLabel: true,
      },
@@ -637,30 +820,75 @@
        },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series: [
        {
          data: values,
          type: "line",
          smooth: true,
          symbolSize: getResponsiveValue(8),
          lineStyle: { width: getResponsiveValue(3), color: "#00A4ED" },
          itemStyle: { color: "#00A4ED" },
          areaStyle: {
            opacity: 1,
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "rgba(0,164,237,0.35)" },
              { offset: 1, color: "rgba(0,164,237,0)" },
            ]),
          },
        },
      ],
      series: series,
    };
  });
  // 销售金额趋势图表配置
  const salesAmountChartOption = computed(() => {
    const periods = ["6/9", "6/10", "6/11", "6/12", "6/12", "6/13"];
    const values = [132, 168, 168, 198, 168, 198];
    // 为每个销售区生成数据
    const salesAreas = [
      "全部",
      "A销售区",
      "B销售区",
      "C销售区",
      "D销售区",
      "E销售区",
    ];
    const colors = [
      "#00A4ED",
      "#34D8F7",
      "#4A8BFF",
      "#8A6BFF",
      "#C8C447",
      "#FF6B6B",
    ];
    const year = 2024;
    const periodType = boardTimeDimension.value;
    // 生成时间段
    let periods = [];
    if (periodType === "year") {
      // 年度数据:12个月
      for (let month = 1; month <= 12; month++) {
        periods.push(`${year}-${month.toString().padStart(2, "0")}`);
      }
    } else {
      // 月度数据:30天
      const month = 1;
      for (let day = 1; day <= 30; day++) {
        periods.push(
          `${year}-${month.toString().padStart(2, "0")}-${day
            .toString()
            .padStart(2, "0")}`
        );
      }
    }
    // 为每个销售区生成数据
    const series = salesAreas.map((area, index) => {
      const data = periods.map(() => {
        return periodType === "year"
          ? Math.floor(Math.random() * 50000) + 80000
          : Math.floor(Math.random() * 5000) + 2000;
      });
      return {
        name: area,
        data: data,
        type: "bar",
        smooth: true,
        lineStyle: { width: getResponsiveValue(3), color: colors[index] },
        itemStyle: { color: colors[index] },
        areaStyle: {
          opacity: 0.2,
          color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
            { offset: 0, color: colors[index] + "80" },
            { offset: 1, color: colors[index] + "00" },
          ]),
        },
      };
    });
    return {
      backgroundColor: "transparent",
@@ -670,12 +898,29 @@
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: getResponsiveValue(1),
        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
        formatter: "{b}: {c} 万元",
        formatter: function (params) {
          let result = params[0].name + "<br/>";
          params.forEach(param => {
            result += `${param.marker}${param.seriesName}: ${param.value} 元<br/>`;
          });
          return result;
        },
      },
      legend: {
        data: salesAreas,
        top: "10%",
        right: "1%",
        textStyle: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(9),
        },
        itemWidth: getResponsiveValue(10),
        itemHeight: getResponsiveValue(10),
      },
      grid: {
        left: "10%",
        right: "4%",
        bottom: "16%",
        left: "1%",
        right: "1%",
        bottom: "1%",
        top: "28%",
        containLabel: true,
      },
@@ -689,6 +934,7 @@
          fontSize: getResponsiveValue(11),
          margin: getResponsiveValue(10),
        },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
@@ -701,25 +947,7 @@
        },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series: [
        {
          data: values,
          type: "line",
          smooth: true,
          symbolSize: getResponsiveValue(8),
          itemStyle: {
            color: "#00A4ED",
          },
          lineStyle: { width: getResponsiveValue(3), color: "#00A4ED" },
          areaStyle: {
            opacity: 1,
            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
              { offset: 0, color: "rgba(0,164,237,0.35)" },
              { offset: 1, color: "rgba(0,164,237,0)" },
            ]),
          },
        },
      ],
      series: series,
    };
  });
@@ -862,83 +1090,97 @@
    };
  });
  // 新增客户趋势图表配置(按产品类型多折线)
  // 新增客户趋势图表配置(按销售区和年月维度)
  const productTypeTrendChartOption = computed(() => {
    const typeOrder = ["AAA销售区", "BBB销售区", "CCC销售区", "DDD销售区"];
    const colorMap = {
      AAA销售区: "#65A0FF",
      BBB销售区: "#33F5FF",
      CCC销售区: "#FFD54A",
      DDD销售区: "#EE52FF",
    };
    const areaColorMap = {
      AAA销售区: "rgba(101,160,255,0.28)",
      BBB销售区: "rgba(51,245,255,0.30)",
      CCC销售区: "rgba(255,213,74,0.25)",
      DDD销售区: "rgba(238,82,255,0.25)",
    };
    const periods = [
      "6/9",
      "6/10",
      "6/11",
      "6/12",
      "6/12",
      "6/13",
      "6/14",
      "6/15",
    // 为每个销售区生成数据
    const salesAreas = [
      "全部",
      "A销售区",
      "B销售区",
      "C销售区",
      "D销售区",
      "E销售区",
    ];
    const map = {
      AAA销售区: [85, 112, 112, 112, 140, 112, 112, 140],
      BBB销售区: [140, 180, 180, 180, 230, 180, 180, 230],
      CCC销售区: [112, 140, 140, 140, 180, 140, 140, 180],
      DDD销售区: [200, 165, 200, 200, 165, 165, 140, 140],
    };
    const colors = [
      "#00A4ED",
      "#34D8F7",
      "#4A8BFF",
      "#8A6BFF",
      "#C8C447",
      "#FF6B6B",
    ];
    const year = 2024;
    const periodType = customerTimeDimension.value;
    const series = typeOrder.map(t => ({
      name: t,
      type: "line",
      smooth: true,
      symbolSize: getResponsiveValue(7),
      showSymbol: true,
      data: map[t] || [],
      lineStyle: { width: getResponsiveValue(3), color: colorMap[t] },
      itemStyle: { color: colorMap[t] },
      areaStyle: {
        opacity: 0.25,
        color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
          { offset: 0, color: areaColorMap[t] },
          { offset: 1, color: "rgba(0,0,0,0)" },
        ]),
      },
    }));
    // 生成时间段
    let periods = [];
    if (periodType === "year") {
      // 年度数据:12个月
      for (let month = 1; month <= 12; month++) {
        periods.push(`${year}-${month.toString().padStart(2, "0")}`);
      }
    } else {
      // 月度数据:30天
      const month = 1;
      for (let day = 1; day <= 30; day++) {
        periods.push(
          `${year}-${month.toString().padStart(2, "0")}-${day
            .toString()
            .padStart(2, "0")}`
        );
      }
    }
    // 为每个销售区生成数据
    const series = salesAreas.map((area, index) => {
      const data = periods.map(() => {
        return periodType === "year"
          ? Math.floor(Math.random() * 10) + 2
          : Math.floor(Math.random() * 3) + 1;
      });
      return {
        name: area,
        data: data,
        type: "line",
        smooth: false,
        lineStyle: { width: getResponsiveValue(1), color: colors[index] },
        itemStyle: { color: colors[index] },
      };
    });
    return {
      backgroundColor: "transparent",
      legend: {
        top: getResponsiveValue(10),
        left: "center",
        textStyle: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(11),
          padding: [0, 0, 0, getResponsiveValue(2)],
        },
        itemWidth: getResponsiveValue(12),
        itemHeight: getResponsiveValue(10),
        itemGap: getResponsiveValue(18),
      },
      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) },
        formatter: function (params) {
          let result = params[0].name + "<br/>";
          params.forEach(param => {
            result += `${param.marker}${param.seriesName}: ${param.value} 人<br/>`;
          });
          return result;
        },
      },
      legend: {
        data: salesAreas,
        top: "10%",
        right: "1%",
        textStyle: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(9),
        },
        itemWidth: getResponsiveValue(10),
        itemHeight: getResponsiveValue(10),
      },
      grid: {
        left: "10%",
        right: "6%",
        bottom: "14%",
        top: "26%",
        left: "1%",
        right: "1%",
        bottom: "1%",
        top: "28%",
        containLabel: true,
      },
      xAxis: {
@@ -951,6 +1193,7 @@
          fontSize: getResponsiveValue(11),
          margin: getResponsiveValue(10),
        },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
@@ -963,125 +1206,10 @@
        },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series,
      series: series,
    };
  });
  // 累计销量趋势图表配置
  const cumulativeSalesVolumeChartOption = computed(() => {
    // 按周期分组
    const periodMap = {};
    let cumulativeValue = 0;
    // 按周期排序
    const sortedData = [...filteredData.value].sort((a, b) =>
      a.period.localeCompare(b.period)
    );
    sortedData.forEach(item => {
      cumulativeValue += item.salesVolume;
      periodMap[item.period] = cumulativeValue;
    });
    const periods = Object.keys(periodMap).sort();
    const values = periods.map(period => periodMap[period]);
    return {
      tooltip: {
        trigger: "axis",
        formatter: "{b}: {c} 立方米",
        textStyle: { fontSize: getResponsiveValue(11) },
      },
      xAxis: {
        type: "category",
        data: periods,
        axisLabel: { fontSize: getResponsiveValue(11) },
      },
      yAxis: {
        type: "value",
        name: "累计销量(立方米)",
        axisLabel: { fontSize: getResponsiveValue(11) },
      },
      series: [
        {
          data: values,
          type: "line",
          smooth: true,
          areaStyle: {
            opacity: 0.3,
          },
          itemStyle: {
            color: "#E6A23C",
          },
          lineStyle: {
            width: getResponsiveValue(3),
          },
        },
      ],
    };
  });
  // 累计销售金额趋势图表配置
  const cumulativeSalesAmountChartOption = computed(() => {
    // 按周期分组
    const periodMap = {};
    let cumulativeValue = 0;
    // 按周期排序
    const sortedData = [...filteredData.value].sort((a, b) =>
      a.period.localeCompare(b.period)
    );
    sortedData.forEach(item => {
      cumulativeValue += item.salesAmount;
      periodMap[item.period] = cumulativeValue;
    });
    const periods = Object.keys(periodMap).sort();
    const values = periods.map(period => periodMap[period]);
    return {
      tooltip: {
        trigger: "axis",
        formatter: "{b}: {c} 万元",
        textStyle: { fontSize: getResponsiveValue(11) },
      },
      xAxis: {
        type: "category",
        data: periods,
        axisLabel: { fontSize: getResponsiveValue(11) },
      },
      yAxis: {
        type: "value",
        name: "累计销售金额(万元)",
        axisLabel: { fontSize: getResponsiveValue(11) },
      },
      series: [
        {
          data: values,
          type: "bar",
          itemStyle: {
            color: "#F56C6C",
          },
        },
      ],
    };
  });
  // 方法
  const goBack = () => {
    router.back();
  };
  const handleDateChange = () => {
    // 处理日期变化
    updateCharts();
  };
  const handleFilterChange = () => {
    // 处理筛选条件变化
    updateCharts();
  };
  const baseWidth = ref(1650);
  // 计算响应式值
  const getResponsiveValue = baseValue => {
@@ -1175,6 +1303,198 @@
    }
  };
  // 表格动画控制
  let blockScrollTimer = null;
  let boardScrollTimer = null;
  let blockCurrentIndex = 0;
  let boardCurrentIndex = 0;
  const startBlockTableScroll = () => {
    if (blockScrollTimer) {
      clearInterval(blockScrollTimer);
    }
    const scrollTable = () => {
      if (!blockTableBody.value || blockSalesData.value.length === 0) return;
      const rows = blockTableBody.value.querySelectorAll("tr");
      if (rows.length === 0) return;
      const rowHeight = rows[0].offsetHeight;
      blockTableBody.value.style.transition = "transform 0.5s ease-in-out";
      blockTableBody.value.style.transform = `translateY(-${rowHeight}px)`;
      setTimeout(() => {
        blockTableBody.value.style.transition = "none";
        blockTableBody.value.style.transform = "translateY(0)";
        const firstItem = blockSalesData.value[0];
        blockSalesData.value.shift();
        blockSalesData.value.push(firstItem);
      }, 500);
    };
    blockScrollTimer = setInterval(scrollTable, 2000);
  };
  const startBoardTableScroll = () => {
    if (boardScrollTimer) {
      clearInterval(boardScrollTimer);
    }
    const scrollTable = () => {
      if (!boardTableBody.value || boardSalesData.value.length === 0) return;
      const rows = boardTableBody.value.querySelectorAll("tr");
      if (rows.length === 0) return;
      const rowHeight = rows[0].offsetHeight;
      boardTableBody.value.style.transition = "transform 0.5s ease-in-out";
      boardTableBody.value.style.transform = `translateY(-${rowHeight}px)`;
      setTimeout(() => {
        boardTableBody.value.style.transition = "none";
        boardTableBody.value.style.transform = "translateY(0)";
        const firstItem = boardSalesData.value[0];
        boardSalesData.value.shift();
        boardSalesData.value.push(firstItem);
      }, 500);
    };
    boardScrollTimer = setInterval(scrollTable, 2000);
  };
  const stopTableScroll = () => {
    if (blockScrollTimer) {
      clearInterval(blockScrollTimer);
      blockScrollTimer = null;
    }
    if (boardScrollTimer) {
      clearInterval(boardScrollTimer);
      boardScrollTimer = null;
    }
  };
  // 处理时间维度选择
  const handleBlockTimeDimensionChange = dimension => {
    blockTimeDimension.value = dimension;
    generateBlockSalesData();
  };
  const handleBoardTimeDimensionChange = dimension => {
    boardTimeDimension.value = dimension;
    generateBoardSalesData();
  };
  // 处理产品类型选择
  const handleBlockProductTypeChange = type => {
    blockProductType.value = type;
    generateBlockSalesData();
  };
  const handleBoardProductTypeChange = type => {
    boardProductType.value = type;
    generateBoardSalesData();
  };
  // 处理销售区选择
  const handleBlockAreaChange = area => {
    blockSelectedArea.value = area;
    generateBlockSalesData();
  };
  const handleBoardAreaChange = area => {
    boardSelectedArea.value = area;
    generateBoardSalesData();
  };
  // 处理新增客户趋势时间维度切换
  const handleCustomerTimeDimensionChange = dimension => {
    customerTimeDimension.value = dimension;
    updateCharts();
  };
  // 生成砌块销售数据
  const generateBlockSalesData = () => {
    const data = [];
    const year = 2024;
    if (blockTimeDimension.value === "year") {
      // 年度数据:12个月
      for (let month = 1; month <= 12; month++) {
        const period = `${year}-${month.toString().padStart(2, "0")}`;
        data.push({
          period: period,
          area: blockSelectedArea.value,
          productType: blockProductType.value,
          sales: Math.floor(Math.random() * 500) + 800,
          sort: month,
        });
      }
    } else {
      // 月度数据:30天
      const month = 1;
      for (let day = 1; day <= 30; day++) {
        const period = `${year}-${month.toString().padStart(2, "0")}-${day
          .toString()
          .padStart(2, "0")}`;
        data.push({
          period: period,
          area: blockSelectedArea.value,
          productType: blockProductType.value,
          sales: Math.floor(Math.random() * 50) + 20,
          sort: day,
        });
      }
    }
    blockSalesData.value = data;
    // 更新图表
    updateCharts();
  };
  // 生成板材销售数据
  const generateBoardSalesData = () => {
    const data = [];
    const year = 2024;
    if (boardTimeDimension.value === "year") {
      // 年度数据:12个月
      for (let month = 1; month <= 12; month++) {
        const period = `${year}-${month.toString().padStart(2, "0")}`;
        data.push({
          period: period,
          area: boardSelectedArea.value,
          productType: boardProductType.value,
          sales: Math.floor(Math.random() * 400) + 600,
          sort: month,
        });
      }
    } else {
      // 月度数据:30天
      const month = 1;
      for (let day = 1; day <= 30; day++) {
        const period = `${year}-${month.toString().padStart(2, "0")}-${day
          .toString()
          .padStart(2, "0")}`;
        data.push({
          period: period,
          area: boardSelectedArea.value,
          productType: boardProductType.value,
          sales: Math.floor(Math.random() * 40) + 15,
          sort: day,
        });
      }
    }
    boardSalesData.value = data;
    // 更新图表
    updateCharts();
  };
  // 监听窗口大小变化
  const handleResize = () => {
    console.log("resize");
@@ -1221,9 +1541,16 @@
      endDate.format("YYYY-MM-DD"),
    ];
    // 生成初始数据
    generateBlockSalesData();
    generateBoardSalesData();
    // 等待DOM更新后初始化图表
    nextTick(() => {
      initCharts();
      // 启动表格滚动动画
      startBlockTableScroll();
      startBoardTableScroll();
    });
    // 添加窗口大小变化监听
@@ -1463,15 +1790,22 @@
    border-bottom: 0.1vh solid rgba(64, 158, 255, 0.25);
  }
  .panel-tabs {
  .panel-tabs,
  .panel-tabs2 {
    position: absolute;
    top: 0.8vh;
    right: 1.2vh;
    display: flex;
    gap: 0.6vh;
    z-index: 4;
  }
  .panel-tabs {
    right: 1.2vh;
  }
  .panel-tabs2 {
    right: 8vh;
  }
  .tab-item {
    font-size: 1.2vh;
    color: rgba(184, 200, 224, 0.75);
@@ -1479,6 +1813,7 @@
    border: 0.1vh solid rgba(64, 158, 255, 0.25);
    border-radius: 0.3vh;
    line-height: 1.4;
    cursor: pointer;
  }
  .tab-item.active {
@@ -1490,6 +1825,76 @@
  .bi-panel-body {
    flex: 1;
    padding: 0.8vh 1vh;
    position: relative;
  }
  .scroll-table-container {
    height: 33vh;
    overflow: hidden;
    position: relative;
  }
  .scroll-table {
    width: 100%;
    border-collapse: collapse;
    color: #b8c8e0;
  }
  .scroll-table th {
    /* background-color: #0e2a54; */
    padding: 1.2vh;
    text-align: left;
    font-size: 1.2vh;
    font-weight: bold;
    border: 1px solid rgba(184, 200, 224, 0.2);
  }
  .scroll-table td {
    padding: 1vh;
    font-size: 1.1vh;
    border-bottom: 1px solid rgba(184, 200, 224, 0.1);
  }
  .scroll-table tbody {
    display: block;
    height: 35vh;
    overflow: hidden;
  }
  .scroll-table thead,
  .scroll-table tbody tr {
    display: table;
    width: 100%;
    table-layout: fixed;
  }
  .scroll-table th,
  .scroll-table td {
    width: 25%;
    box-sizing: border-box;
    font-size: 1.4vh;
  }
  .scroll-table tbody tr {
    transition: all 0.5s ease-in-out;
  }
  /* .scroll-table tbody tr:nth-child(odd) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  background-color: rgba(64, 158, 255, 0.05);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                .scroll-table tbody tr:nth-child(even) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    background-color: rgba(64, 158, 255, 0.1);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      } */
  .oddTableTr {
    background-color: rgba(64, 158, 255, 0.05);
  }
  .evenTableTr {
    background-color: rgba(64, 158, 255, 0.1);
  }
  .scroll-table-container:hover tbody {
    overflow: hidden;
  }
  .echart-fill {
@@ -1501,6 +1906,7 @@
    display: flex;
    gap: 0.6vh;
    margin: 0 0 0.5vh 0;
    justify-self: end;
  }
  .cf-tab {
@@ -1510,6 +1916,7 @@
    border: 0.1vh solid rgba(64, 158, 255, 0.25);
    padding: 0.3vh 0.9vh;
    line-height: 1;
    cursor: pointer;
  }
  .cf-tab.active {
@@ -1542,11 +1949,44 @@
    gap: 0.8vh;
    font-size: 1.8vh;
    color: #d9ecff;
    font-weight: 700;
    margin: 0 0 0.8vh 0;
    line-height: 1;
  }
  /* 面板底部合计行 */
  .panel-summary-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 0.8vh 1.2vh;
    /* margin-top: 0.8vh; */
    background: #041e3c;
    border-top: 0.1vh solid rgba(64, 158, 255, 0.25);
    border-radius: 0 0 0.4vh 0.4vh;
    width: 100%;
    position: absolute;
    bottom: 0;
  }
  .summary-label {
    font-size: 1.3vh;
    font-weight: 700;
    color: #b8c8e0;
  }
  .summary-value {
    font-size: 1.4vh;
    font-weight: 800;
    color: #00a4ed;
    margin-right: 3.8vh;
    text-shadow: 0 0 1vh rgba(0, 164, 237, 0.5);
  }
  .summary-value2 {
    font-size: 1.4vh;
    font-weight: 800;
    color: #00a4ed;
    margin-right: 5.8vh;
    text-shadow: 0 0 1vh rgba(0, 164, 237, 0.5);
  }
  .diamond {
    width: 1vh;
    height: 1vh;
@@ -1589,6 +2029,11 @@
  .bi-panel-bottom-left {
    grid-column: 1;
    grid-row: 2;
    overflow-y: auto;
  }
  .bi-panel-bottom-left::-webkit-scrollbar {
    width: 0vh;
    height: 0vh;
  }
  .bi-panel-bottom-center {
@@ -1599,6 +2044,11 @@
  .bi-panel-bottom-right {
    grid-column: 3;
    grid-row: 2;
    overflow-y: auto;
  }
  .bi-panel-bottom-right::-webkit-scrollbar {
    width: 0vh;
    height: 0vh;
  }
  /* 中心环浮层(绝对定位在网格上方) */
@@ -1630,7 +2080,7 @@
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: drop-shadow(0 0 20px rgba(0, 164, 237, 0.35));
    filter: drop-shadow(0 0 2vh rgba(0, 164, 237, 0.35));
  }
  .center-ring-content {
@@ -1730,13 +2180,13 @@
  .m1 {
    top: 2.5vh;
    left: 2.3vw;
    left: 4.8vh;
    text-align: left;
  }
  .m2 {
    top: 4.1vh;
    right: 4.3vw;
    right: 8.6vh;
    text-align: right;
  }
@@ -1787,4 +2237,46 @@
      right: 4.4vh;
    }
  }
</style>
  .scroll-table-content {
    height: 39vh;
    overflow: auto;
  }
  .scroll-table-content::-webkit-scrollbar {
    width: 0;
    height: 0;
    background-color: transparent;
  }
  .total-row {
    position: absolute;
    bottom: 0vh;
    left: 0;
    width: 100%;
    display: flex;
    height: 3.5vh;
    justify-content: space-around;
    align-items: center;
    background-color: #081843;
  }
  .total-cell {
    width: 20%;
    font-size: 1.4vh;
    margin-bottom: 0.5vh;
    line-height: 3.5vh;
    padding-left: 0.8vh;
    color: #eaf6ff;
    text-shadow: 0 0 0.8vh rgba(0, 229, 255, 0.22);
    text-align: left;
    color: #c3c3c3;
  }
  .total-cell2 {
    width: 20%;
    font-size: 1.4vh;
    margin-bottom: 0.5vh;
    line-height: 3.5vh;
    color: #eaf6ff;
    text-shadow: 0 0 0.8vh rgba(0, 229, 255, 0.22);
    text-align: left;
    color: #c3c3c3;
  }
</style>