zhangwencui
6 小时以前 01051a30308b5bc5138179ddfe2b16edcc58be8d
销售统计看板修改
已修改2个文件
806 ■■■■ 文件已修改
src/api/reportAnalysis/salesStatistics.js 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/salesStatistics/index.vue 790 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/api/reportAnalysis/salesStatistics.js
@@ -14,4 +14,20 @@
    url: '/home/total',
    method: 'get'
  })
}
// 销量分析趋势图
export function getSalesAnalysisTrend(params) {
  return request({
    url: '/home/salesAnalysis',
    method: 'get',
    params: params
  })
}
// 销售金额分析
export function getSalesAmountAnalysis(params) {
  return request({
    url: '/home/salesAmount',
    method: 'get',
    params: params
  })
}
src/views/reportAnalysis/salesStatistics/index.vue
@@ -6,7 +6,7 @@
    <div class="bi-topbar">
      <img class="bi-topbar-title-bg"
           src="@/assets/BI/biaoti.png"
           alt="销售看板统计" />
           alt="销售统计看板" />
      <div class="bi-topbar-content">
        <div class="bi-topbar-left">
          <button class="fullscreen-btn"
@@ -35,7 +35,7 @@
          <span>26℃</span>
          <span class="bi-topbar-sep">湿度:1</span> -->
        </div>
        <div class="bi-topbar-title">销售看板统计</div>
        <div class="bi-topbar-title">销售统计看板</div>
        <div class="bi-topbar-meta">
          <span class="bi-topbar-time">{{ currentTime }}</span>
          <span class="bi-topbar-sep">|</span>
@@ -50,19 +50,19 @@
                     title="销量分析趋势图" />
        <div class="panel-tabs">
          <span class="tab-item"
                :class="{ active: blockTimeDimension === 'year' }"
                @click="handleBlockTimeDimensionChange('year')">年</span>
                :class="{ active: chartTimeDimension === '年' }"
                @click="handleChartTimeDimensionChange('年')">年</span>
          <span class="tab-item"
                :class="{ active: blockTimeDimension === 'month' }"
                @click="handleBlockTimeDimensionChange('month')">月</span>
                :class="{ active: chartTimeDimension === '月' }"
                @click="handleChartTimeDimensionChange('月')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: blockProductType === '砌块' }"
                @click="handleBlockProductTypeChange('砌块')">砌块</span>
                :class="{ active: chartProductType === '砌块' }"
                @click="handleChartProductTypeChange('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: blockProductType === '板材' }"
                @click="handleBlockProductTypeChange('板材')">板材</span>
                :class="{ active: chartProductType === '板材' }"
                @click="handleChartProductTypeChange('板材')">板材</span>
        </div>
        <div class="bi-panel-body">
          <div class="chart-unit-row">
@@ -78,19 +78,19 @@
                     title="销售金额分析" />
        <div class="panel-tabs">
          <span class="tab-item"
                :class="{ active: boardTimeDimension === 'year' }"
                @click="handleBoardTimeDimensionChange('year')">年</span>
                :class="{ active: chartTimeDimension2 === '年' }"
                @click="handleChartTimeDimensionChange2('年')">年</span>
          <span class="tab-item"
                :class="{ active: boardTimeDimension === 'month' }"
                @click="handleBoardTimeDimensionChange('month')">月</span>
                :class="{ active: chartTimeDimension2 === '月' }"
                @click="handleChartTimeDimensionChange2('月')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: boardProductType === '砌块' }"
                @click="handleBoardProductTypeChange('砌块')">砌块</span>
                :class="{ active: chartProductType2 === '砌块' }"
                @click="handleChartProductTypeChange2('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: boardProductType === '板材' }"
                @click="handleBoardProductTypeChange('板材')">板材</span>
                :class="{ active: chartProductType2 === '板材' }"
                @click="handleChartProductTypeChange2('板材')">板材</span>
        </div>
        <div class="bi-panel-body">
          <div class="chart-unit-row">
@@ -131,27 +131,27 @@
                     title="销量数据统计" />
        <div class="panel-tabs">
          <span class="tab-item"
                :class="{ active: blockTimeDimension === 'year' }"
                @click="handleBlockTimeDimensionChange('year')">年</span>
                :class="{ active: tableTimeDimension === '年' }"
                @click="handleTableTimeDimensionChange('年')">年</span>
          <span class="tab-item"
                :class="{ active: blockTimeDimension === 'month' }"
                @click="handleBlockTimeDimensionChange('month')">月</span>
                :class="{ active: tableTimeDimension === '月' }"
                @click="handleTableTimeDimensionChange('月')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: blockProductType === '砌块' }"
                @click="handleBlockProductTypeChange('砌块')">砌块</span>
                :class="{ active: tableProductType === '砌块' }"
                @click="handleTableProductTypeChange('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: blockProductType === '板材' }"
                @click="handleBlockProductTypeChange('板材')">板材</span>
                :class="{ active: tableProductType === '板材' }"
                @click="handleTableProductTypeChange('板材')">板材</span>
        </div>
        <div class="bi-panel-body">
          <div class="chart-filter-tabs">
            <span v-for="area in salesAreas"
            <span v-for="area in tableSalesAreas"
                  :key="area"
                  class="cf-tab"
                  :class="{ active: blockSelectedArea === area }"
                  @click="handleBlockAreaChange(area)">{{ area }}</span>
                  :class="{ active: tableSelectedArea === area }"
                  @click="handleTableAreaChange(area)">{{ area }}</span>
          </div>
          <div class="scroll-table-container">
            <table class="scroll-table">
@@ -166,10 +166,10 @@
              </thead>
              <div class="scroll-table-content">
                <tbody ref="blockTableBody">
                  <tr :class="item.sort % 2 === 0 ? 'evenTableTr' : 'oddTableTr'"
                      v-for="(item, index) in blockSalesData"
                  <tr :class="(index + 1) % 2 === 0 ? 'evenTableTr' : 'oddTableTr'"
                      v-for="(item, index) in filteredTableSalesData"
                      :key="item.period + item.area + index">
                    <td>{{ item.sort }}</td>
                    <td>{{ index + 1 }}</td>
                    <td>{{ item.productType }}</td>
                    <td>{{ item.period }}</td>
                    <td>{{ item.area }}</td>
@@ -181,7 +181,7 @@
          </div>
          <div class="panel-summary-row">
            <div class="summary-label">合计</div>
            <div class="summary-value">127384 m³</div>
            <div class="summary-value">{{ filteredTableSalesTotal }} m³</div>
          </div>
        </div>
      </div>
@@ -211,44 +211,46 @@
                     title="销售额数据统计" />
        <div class="panel-tabs">
          <span class="tab-item"
                :class="{ active: boardTimeDimension === 'year' }"
                @click="handleBoardTimeDimensionChange('year')">年</span>
                :class="{ active: tableTimeDimension2 === '年' }"
                @click="handleTableTimeDimensionChange2('年')">年</span>
          <span class="tab-item"
                :class="{ active: boardTimeDimension === 'month' }"
                @click="handleBoardTimeDimensionChange('month')">月</span>
                :class="{ active: tableTimeDimension2 === '月' }"
                @click="handleTableTimeDimensionChange2('月')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: boardProductType === '砌块' }"
                @click="handleBoardProductTypeChange('砌块')">砌块</span>
                :class="{ active: tableProductType2 === '砌块' }"
                @click="handleTableProductTypeChange2('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: boardProductType === '板材' }"
                @click="handleBoardProductTypeChange('板材')">板材</span>
                :class="{ active: tableProductType2 === '板材' }"
                @click="handleTableProductTypeChange2('板材')">板材</span>
        </div>
        <div class="bi-panel-body">
          <div class="chart-filter-tabs">
            <span v-for="area in salesAreas"
            <span v-for="area in tableSalesAreas"
                  :key="area"
                  class="cf-tab"
                  :class="{ active: boardSelectedArea === area }"
                  @click="handleBoardAreaChange(area)">{{ area }}</span>
                  :class="{ active: tableSelectedArea === area }"
                  @click="handleTableAreaChange2(area)">{{ area }}</span>
          </div>
          <div class="scroll-table-container">
            <table class="scroll-table">
              <thead>
                <tr>
                  <th>序号</th>
                  <th>产品类型</th>
                  <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"
                  <tr :class="(index + 1) % 2 === 0 ? 'evenTableTr' : 'oddTableTr'"
                      v-for="(item, index) in filteredAmountSalesData"
                      :key="item.period + item.area + index">
                    <td>{{ item.sort }}</td>
                    <td>{{ index + 1 }}</td>
                    <td>{{ item.productType }}</td>
                    <td>{{ item.period }}</td>
                    <td>{{ item.area }}</td>
                    <td>{{ item.sales }}</td>
@@ -259,7 +261,7 @@
          </div>
          <div class="panel-summary-row">
            <div class="summary-label">合计</div>
            <div class="summary-value2">127384 万元</div>
            <div class="summary-value2">{{ filteredAmountSalesTotal }} 元</div>
          </div>
        </div>
      </div>
@@ -283,6 +285,8 @@
  import {
    getDashboardStatistics,
    getCustomerTrends,
    getSalesAnalysisTrend,
    getSalesAmountAnalysis,
  } from "@/api/reportAnalysis/salesStatistics";
  const router = useRouter();
@@ -343,10 +347,21 @@
  const boardTableBody = ref(null);
  // 选择器数据
  const blockTimeDimension = ref("year");
  const blockSelectedArea = ref("全部");
  const blockProductType = ref("砌块");
  const boardTimeDimension = ref("year");
  // 销量分析趋势图
  const chartTimeDimension = ref("年");
  const chartTimeDimension2 = ref("年");
  const chartSelectedArea = ref("全部");
  const chartProductType = ref("砌块");
  const chartProductType2 = ref("砌块");
  // 销量数据统计
  const tableTimeDimension = ref("年");
  const tableSelectedArea = ref("全部");
  const tableSelectedArea2 = ref("全部");
  const tableProductType = ref("砌块");
  const boardTimeDimension = ref("年");
  const boardSelectedArea = ref("全部");
  const boardProductType = ref("板材");
  const customerTimeDimension = ref("年");
@@ -675,6 +690,14 @@
  // 客户趋势数据
  const customerTrendsData = ref([]);
  // 销量分析趋势数据
  const salesAnalysisTrendData = ref([]);
  // 销量数据统计表格数据
  const tableSalesData = ref([]);
  // 销量数据统计表格总计
  const tableSalesTotal = ref(0);
  // 动态销售区域列表
  const tableSalesAreas = ref([]);
  // 变化率计算(模拟)
  const salesVolumeChange = ref("+5.2");
@@ -717,6 +740,126 @@
    }
  };
  // 获取销量分析趋势数据
  const fetchSalesAnalysisTrendData = async () => {
    try {
      const response = await getSalesAnalysisTrend({
        type: chartProductType.value, // 砌块或板材
        days: chartTimeDimension.value, // 年或月
      });
      if (response && response.data) {
        // API返回的数据结构如下:
        // {
        //   "dates": ["2026-01-01", "2025-01-01", ...],
        //   "customerTrends": [{"内蒙古": 470, "银川": 3600, ...}, ...]
        // }
        salesAnalysisTrendData.value = response.data;
        updateCharts();
      }
    } catch (error) {
      console.error("获取销量分析趋势数据失败:", error);
    }
  };
  // 获取销量数据统计表格数据
  const fetchTableSalesData = async () => {
    try {
      const response = await getSalesAnalysisTrend({
        type: tableProductType.value, // 砌块或板材
        days: tableTimeDimension.value, // 年或月
      });
      if (response && response.data) {
        // API返回的数据结构如下:
        // {
        //   "dates": ["2026-01-01", "2025-01-01", ...],
        //   "customerTrends": [{"内蒙古": 470, "银川": 3600, ...}, ...]
        // }
        updateTableSalesData(response.data);
      }
    } catch (error) {
      console.error("获取销量数据统计表格数据失败:", error);
    }
  };
  // 更新销量数据统计表格
  const updateTableSalesData = data => {
    if (!data || !data.dates || !data.customerTrends) {
      return;
    }
    console.log(data, "datas");
    const dates = data.dates;
    const customerTrends = data.customerTrends;
    const tableData = [];
    let total = 0;
    const areaSet = new Set();
    dates.forEach((date, index) => {
      const trend = customerTrends[index];
      if (trend) {
        // 提取所有销售区域
        Object.keys(trend).forEach(area => {
          if (area !== "全部") {
            areaSet.add(area);
            const sales = trend[area] || 0;
            tableData.push({
              period: date,
              area: area,
              productType: tableProductType.value,
              sales: sales,
              sort: tableData.length + 1,
            });
            total += sales;
          }
        });
      }
    });
    // 更新销售区域列表,添加"全部"选项
    tableSalesAreas.value = ["全部", ...Array.from(areaSet)];
    // 确保 tableSelectedArea 在销售区域列表中
    if (
      tableSalesAreas.value.length > 0 &&
      !tableSalesAreas.value.includes(tableSelectedArea.value)
    ) {
      tableSelectedArea.value = "全部";
    }
    console.log(tableData);
    tableSalesData.value = tableData;
    tableSalesTotal.value = total;
  };
  // 处理图表时间维度变化
  const handleChartTimeDimensionChange = dimension => {
    chartTimeDimension.value = dimension;
    fetchSalesAnalysisTrendData();
  };
  // 处理图表产品类型变化
  const handleChartProductTypeChange = type => {
    chartProductType.value = type;
    fetchSalesAnalysisTrendData();
  };
  // 处理表格时间维度变化
  const handleTableTimeDimensionChange = dimension => {
    tableTimeDimension.value = dimension;
    fetchTableSalesData();
    // 重新启动滚动,根据时间维度决定是否滚动
    startBlockTableScroll();
  };
  // 处理表格产品类型变化
  const handleTableProductTypeChange = type => {
    tableProductType.value = type;
    fetchTableSalesData();
  };
  // 处理表格销售区域变化
  const handleTableAreaChange = area => {
    tableSelectedArea.value = area;
  };
  // 表格数据
  const tableData = computed(() => {
    return filteredData.value.map(item => {
@@ -734,17 +877,45 @@
    });
  });
  // 筛选后的表格数据
  const filteredTableSalesData = computed(() => {
    if (tableSelectedArea.value === "全部") {
      // 按年月分组汇总数据
      const groupedData = {};
      tableSalesData.value.forEach(item => {
        const key = item.period;
        if (!groupedData[key]) {
          groupedData[key] = {
            period: item.period,
            area: "全部",
            productType: item.productType,
            sales: 0,
            sort: 0,
          };
        }
        groupedData[key].sales += item.sales;
      });
      // 转换为数组并按年月排序
      return Object.values(groupedData).sort((a, b) => {
        return new Date(b.period) - new Date(a.period);
      });
    } else {
      return tableSalesData.value.filter(
        item => item.area === tableSelectedArea.value
      );
    }
  });
  // 筛选后的表格数据总计
  const filteredTableSalesTotal = computed(() => {
    return filteredTableSalesData.value.reduce(
      (total, item) => total + item.sales,
      0
    );
  });
  // 销量趋势图表配置
  const salesVolumeChartOption = computed(() => {
    // 为每个销售区生成数据
    const salesAreas = [
      "全部",
      "A销售区",
      "B销售区",
      "C销售区",
      "D销售区",
      "E销售区",
    ];
    const colors = [
      "#00A4ED",
      "#34D8F7",
@@ -752,54 +923,111 @@
      "#8A6BFF",
      "#C8C447",
      "#FF6B6B",
      "#FF9500",
      "#4CD964",
      "#5AC8FA",
    ];
    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")}`);
    let salesAreas = [];
    let series = [];
    if (
      salesAnalysisTrendData.value &&
      salesAnalysisTrendData.value.dates &&
      salesAnalysisTrendData.value.customerTrends
    ) {
      // 使用API返回的日期
      periods = salesAnalysisTrendData.value.dates;
      // 提取销售区域
      const customerTrends = salesAnalysisTrendData.value.customerTrends;
      if (customerTrends.length > 0) {
        // 提取销售区域并确保"全部"在第一个位置
        const allAreas = Object.keys(customerTrends[0]);
        salesAreas = allAreas.sort((a, b) => {
          if (a === "全部") return -1;
          if (b === "全部") return 1;
          return 0;
        });
        // 为每个销售区生成数据
        series = salesAreas.map((area, index) => {
          const data = customerTrends.map(trend => trend[area] || 0);
          return {
            name: area,
            data: data,
            type: "line",
            smooth: false,
            // symbolSize: getResponsiveValue(8),
            lineStyle: {
              width: getResponsiveValue(1),
              color: colors[index % colors.length],
            },
            itemStyle: { color: colors[index % colors.length] },
            areaStyle: {
              opacity: 0.4,
              color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
                { offset: 0, color: colors[index % colors.length] + "80" },
                { offset: 1, color: colors[index % colors.length] + "00" },
              ]),
            },
          };
        });
      }
    } 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")}`
        );
      // 模拟数据
      salesAreas = [
        "全部",
        "A销售区",
        "B销售区",
        "C销售区",
        "D销售区",
        "E销售区",
      ];
      const year = 2024;
      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")}`
          );
        }
      }
    }
      // 为每个销售区生成数据
      series = salesAreas.map((area, index) => {
        const data = periods.map(() => {
          return periodType === "year"
            ? Math.floor(Math.random() * 500) + 800
            : Math.floor(Math.random() * 50) + 20;
        });
    // 为每个销售区生成数据
    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 {
        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",
@@ -864,15 +1092,20 @@
  // 销售金额趋势图表配置
  const salesAmountChartOption = computed(() => {
    // 为每个销售区生成数据
    const salesAreas = [
      "全部",
      "A销售区",
      "B销售区",
      "C销售区",
      "D销售区",
      "E销售区",
    ];
    const { dates = [], customerTrends = [] } = salesAmountChartData.value;
    // 提取所有销售区域
    const areaSet = new Set();
    customerTrends.forEach(item => {
      Object.keys(item).forEach(key => areaSet.add(key));
    });
    // 确保"全部"在第一个位置
    const salesAreas = Array.from(areaSet).sort((a, b) => {
      if (a === "全部") return -1;
      if (b === "全部") return 1;
      return 0;
    });
    const colors = [
      "#00A4ED",
      "#34D8F7",
@@ -881,48 +1114,26 @@
      "#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;
      });
      const data = customerTrends.map(item => item[area] || 0);
      return {
        name: area,
        data: data,
        type: "bar",
        smooth: true,
        lineStyle: { width: getResponsiveValue(3), color: colors[index] },
        itemStyle: { color: colors[index] },
        lineStyle: {
          width: getResponsiveValue(3),
          color: colors[index % colors.length],
        },
        itemStyle: { color: colors[index % colors.length] },
        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" },
            { offset: 0, color: colors[index % colors.length] + "80" },
            { offset: 1, color: colors[index % colors.length] + "00" },
          ]),
        },
      };
@@ -937,7 +1148,7 @@
        borderWidth: getResponsiveValue(1),
        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
        formatter: function (params) {
          let result = params[0].name + "<br/>";
          let result = params[0]?.name + "<br/>" || "";
          params.forEach(param => {
            result += `${param.marker}${param.seriesName}: ${param.value} 元<br/>`;
          });
@@ -964,7 +1175,7 @@
      },
      xAxis: {
        type: "category",
        data: periods,
        data: dates,
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisTick: { show: false },
        axisLabel: {
@@ -1164,7 +1375,12 @@
          areaSet.add(key);
        });
      });
      salesAreas = Array.from(areaSet);
      // 确保"全部"在第一个位置
      salesAreas = Array.from(areaSet).sort((a, b) => {
        if (a === "全部") return -1;
        if (b === "全部") return 1;
        return 0;
      });
      // 为每个销售区域生成数据
      series = salesAreas.map((area, index) => {
@@ -1382,15 +1598,18 @@
  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;
    // 只有当时间维度不是"年"时才启动滚动
    if (tableTimeDimension.value === "年") {
      return;
    }
    const scrollTable = () => {
      if (!blockTableBody.value) return;
      const rows = blockTableBody.value.querySelectorAll("tr");
      if (rows.length === 0) return;
@@ -1403,9 +1622,10 @@
        blockTableBody.value.style.transition = "none";
        blockTableBody.value.style.transform = "translateY(0)";
        const firstItem = blockSalesData.value[0];
        blockSalesData.value.shift();
        blockSalesData.value.push(firstItem);
        // 直接操作DOM,将第一行移到最后
        const firstRow = rows[0];
        blockTableBody.value.removeChild(firstRow);
        blockTableBody.value.appendChild(firstRow);
      }, 500);
    };
@@ -1450,6 +1670,10 @@
      clearInterval(boardScrollTimer);
      boardScrollTimer = null;
    }
    if (amountScrollTimer.value) {
      clearInterval(amountScrollTimer.value);
      amountScrollTimer.value = null;
    }
  };
  // 处理时间维度选择
@@ -1462,7 +1686,7 @@
    boardTimeDimension.value = dimension;
    generateBoardSalesData();
  };
  const blockProductType = ref("砌块");
  // 处理产品类型选择
  const handleBlockProductTypeChange = type => {
    blockProductType.value = type;
@@ -1473,7 +1697,7 @@
    boardProductType.value = type;
    generateBoardSalesData();
  };
  const blockSelectedArea = ref("全部");
  // 处理销售区选择
  const handleBlockAreaChange = area => {
    blockSelectedArea.value = area;
@@ -1490,6 +1714,7 @@
    customerTimeDimension.value = dimension;
    fetchCustomerTrendsData();
  };
  const blockTimeDimension = ref("年");
  // 生成砌块销售数据
  const generateBlockSalesData = () => {
@@ -1622,18 +1847,269 @@
    // 获取数据
    await fetchDashboardData();
    await fetchCustomerTrendsData();
    await fetchSalesAnalysisTrendData();
    await fetchTableSalesData();
    await fetchSalesAmountChartData();
    await fetchSalesAmountTableData();
    // 等待DOM更新后初始化图表
    nextTick(() => {
      initCharts();
      // 启动表格滚动动画
      startBlockTableScroll();
      startBoardTableScroll();
      startAmountTableScroll();
    });
    // 添加窗口大小变化监听
    window.addEventListener("resize", handleResize);
    document.addEventListener("fullscreenchange", handleFullscreenChange);
  });
  // 监听图表时间维度和产品类型变化
  watch([chartTimeDimension, chartProductType], async () => {
    await fetchSalesAnalysisTrendData();
  });
  // 监听表格时间维度和产品类型变化
  watch([tableTimeDimension, tableProductType], async () => {
    await fetchTableSalesData();
  });
  // 销售金额分析图表数据(右上)
  const salesAmountChartData = ref({
    dates: [],
    customerTrends: [],
  });
  // 销售额数据统计表格数据(右下)
  const salesAmountTableData = ref({
    dates: [],
    customerTrends: [],
  });
  // 销售额数据统计表格筛选状态(右下)
  const tableTimeDimension2 = ref("年");
  const tableProductType2 = ref("砌块");
  // 销售额数据统计表格数据
  const amountSalesData = ref([]);
  const amountScrollTimer = ref(null);
  // 获取销售金额分析图表数据(右上)
  const fetchSalesAmountChartData = async () => {
    try {
      const response = await getSalesAmountAnalysis({
        type: chartProductType2.value,
        days: chartTimeDimension2.value,
      });
      if (response?.data) {
        salesAmountChartData.value = response.data;
        updateCharts();
      }
    } catch (error) {
      console.error("获取销售金额分析图表数据失败:", error);
      // 使用模拟数据
      salesAmountChartData.value = {
        dates: [
          "2026-01-01",
          "2025-01-01",
          "2024-01-01",
          "2023-01-01",
          "2022-01-01",
        ],
        customerTrends: [
          { 内蒙古: 100, 银川: 200, 自提: 300, 其他: 150, 全部: 750 },
          { 内蒙古: 80, 银川: 180, 自提: 280, 其他: 130, 全部: 670 },
          { 内蒙古: 90, 银川: 190, 自提: 290, 其他: 140, 全部: 710 },
          { 内蒙古: 70, 银川: 170, 自提: 270, 其他: 120, 全部: 630 },
          { 内蒙古: 110, 银川: 210, 自提: 310, 其他: 160, 全部: 790 },
        ],
      };
    }
  };
  // 获取销售额数据统计表格数据(右下)
  const fetchSalesAmountTableData = async () => {
    try {
      const response = await getSalesAmountAnalysis({
        type: tableProductType2.value,
        days: tableTimeDimension2.value,
      });
      if (response?.data) {
        salesAmountTableData.value = response.data;
        updateAmountSalesData();
      }
    } catch (error) {
      console.error("获取销售额数据统计表格数据失败:", error);
      // 使用模拟数据
      salesAmountTableData.value = {
        dates: [
          "2026-01-01",
          "2025-01-01",
          "2024-01-01",
          "2023-01-01",
          "2022-01-01",
        ],
        customerTrends: [
          { 内蒙古: 100, 银川: 200, 自提: 300, 其他: 150, 全部: 750 },
          { 内蒙古: 80, 银川: 180, 自提: 280, 其他: 130, 全部: 670 },
          { 内蒙古: 90, 银川: 190, 自提: 290, 其他: 140, 全部: 710 },
          { 内蒙古: 70, 银川: 170, 自提: 270, 其他: 120, 全部: 630 },
          { 内蒙古: 110, 银川: 210, 自提: 310, 其他: 160, 全部: 790 },
        ],
      };
      updateAmountSalesData();
    }
  };
  // 更新销售金额分析表格数据
  const updateAmountSalesData = () => {
    const data = [];
    const { dates, customerTrends } = salesAmountTableData.value;
    // 提取所有销售区域
    const areaSet = new Set();
    customerTrends.forEach(item => {
      Object.keys(item).forEach(key => areaSet.add(key));
    });
    // 更新销售区域列表,确保"全部"在第一位
    tableSalesAreas.value = [
      "全部",
      ...Array.from(areaSet).filter(area => area !== "全部"),
    ];
    // 确保选中的区域在列表中
    if (!tableSalesAreas.value.includes(tableSelectedArea.value)) {
      tableSelectedArea.value = "全部";
    }
    // 生成表格数据
    dates.forEach((date, index) => {
      const trends = customerTrends[index] || {};
      Object.keys(trends).forEach(area => {
        data.push({
          period: date,
          area: area,
          productType: tableProductType2.value,
          sales: trends[area],
          sort: data.length + 1,
        });
      });
    });
    amountSalesData.value = data;
  };
  // 筛选后的销售金额分析表格数据
  const filteredAmountSalesData = computed(() => {
    if (tableSelectedArea.value === "全部") {
      // 按年月分组汇总数据
      const groupedData = {};
      amountSalesData.value.forEach(item => {
        const key = item.period;
        if (!groupedData[key]) {
          groupedData[key] = {
            period: item.period,
            area: "全部",
            productType: tableProductType2.value,
            sales: 0,
          };
        }
        groupedData[key].sales += item.sales;
      });
      // 转换为数组并按年月排序
      return Object.values(groupedData).sort((a, b) => {
        return new Date(b.period) - new Date(a.period);
      });
    } else {
      return amountSalesData.value.filter(
        item => item.area === tableSelectedArea.value
      );
    }
  });
  // 销售金额分析表格总计
  const filteredAmountSalesTotal = computed(() => {
    return filteredAmountSalesData.value.reduce(
      (total, item) => total + item.sales,
      0
    );
  });
  // 处理销售金额分析图表时间维度变化(右上)
  const handleChartTimeDimensionChange2 = dimension => {
    chartTimeDimension2.value = dimension;
    fetchSalesAmountChartData();
  };
  // 处理销售金额分析图表产品类型变化(右上)
  const handleChartProductTypeChange2 = type => {
    chartProductType2.value = type;
    fetchSalesAmountChartData();
  };
  // 处理销售额数据统计表格时间维度变化(右下)
  const handleTableTimeDimensionChange2 = dimension => {
    tableTimeDimension2.value = dimension;
    fetchSalesAmountTableData();
    // 重新启动滚动,根据时间维度决定是否滚动
    startAmountTableScroll();
  };
  // 处理销售额数据统计表格产品类型变化(右下)
  const handleTableProductTypeChange2 = type => {
    tableProductType2.value = type;
    fetchSalesAmountTableData();
  };
  // 处理销售额数据统计表格销售区变化(右下)
  const handleTableAreaChange2 = area => {
    tableSelectedArea.value = area;
  };
  // 启动销售金额分析表格滚动
  const startAmountTableScroll = () => {
    if (amountScrollTimer.value) {
      clearInterval(amountScrollTimer.value);
    }
    // 只有当时间维度不是"年"时才启动滚动
    if (tableTimeDimension2.value === "年") {
      return;
    }
    const scrollTable = () => {
      if (!boardTableBody.value) 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)";
        // 直接操作DOM,将第一行移到最后
        const firstRow = rows[0];
        boardTableBody.value.removeChild(firstRow);
        boardTableBody.value.appendChild(firstRow);
      }, 500);
    };
    amountScrollTimer.value = setInterval(scrollTable, 2000);
  };
  // 监听销售金额分析图表时间维度和产品类型变化(右上)
  watch([chartTimeDimension2, chartProductType2], async () => {
    // await fetchSalesAmountChartData();
  });
  // 监听销售额数据统计表格时间维度和产品类型变化(右下)
  watch([tableTimeDimension2, tableProductType2], async () => {
    // await fetchSalesAmountTableData();
  });
  // 获取产品类型标签类型
@@ -1958,12 +2434,12 @@
  }
  /* .scroll-table tbody tr:nth-child(odd) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                background-color: rgba(64, 158, 255, 0.05);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  background-color: rgba(64, 158, 255, 0.05);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  }
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              .scroll-table tbody tr:nth-child(even) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  background-color: rgba(64, 158, 255, 0.1);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    } */
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                .scroll-table tbody tr:nth-child(even) {
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    background-color: rgba(64, 158, 255, 0.1);
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      } */
  .oddTableTr {
    background-color: rgba(64, 158, 255, 0.05);
  }
@@ -2062,7 +2538,7 @@
    font-size: 1.4vh;
    font-weight: 800;
    color: #00a4ed;
    margin-right: 5.8vh;
    margin-right: 1.8vh;
    text-shadow: 0 0 1vh rgba(0, 164, 237, 0.5);
  }
  .diamond {