src/views/reportAnalysis/salesStatistics/index.vue
@@ -1,169 +1,268 @@
<template>
  <div ref="screenRoot" class="sales-statistics-container" :class="{ 'is-fullscreen': isFullscreen }">
  <div ref="screenRoot"
       class="sales-statistics-container"
       :class="{ 'is-fullscreen': isFullscreen }">
    <div class="bi-bg"></div>
    <div class="bi-topbar">
      <img class="bi-topbar-title-bg" src="@/assets/BI/biaoti.png" alt="销售看板统计" />
      <img class="bi-topbar-title-bg"
           src="@/assets/BI/biaoti.png"
           alt="销售统计看板" />
      <div class="bi-topbar-content">
        <div class="bi-topbar-left">
          <span class="status-sun">☀</span>
          <button class="fullscreen-btn"
                  @click="toggleFullscreen"
                  :title="isFullscreen ? '退出全屏' : '全屏显示'">
            <svg v-if="!isFullscreen"
                 width="1.6vh"
                 height="1.6vh"
                 viewBox="0 0 24 24"
                 fill="none"
                 stroke="currentColor"
                 stroke-width="2">
              <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3" />
            </svg>
            <svg v-else
                 width="1.6vh"
                 height="1.6vh"
                 viewBox="0 0 24 24"
                 fill="none"
                 stroke="currentColor"
                 stroke-width="2">
              <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" />
            </svg>
          </button>
          <!-- <span class="status-sun">☀</span>
          <span>26℃</span>
          <span class="bi-topbar-sep">湿度:1</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>
          <span class="bi-topbar-date">{{ currentDateText }}</span>
        </div>
        <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? '退出全屏' : '全屏显示'">
          <svg v-if="!isFullscreen" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/>
          </svg>
          <svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
          </svg>
        </button>
      </div>
    </div>
    <div class="bi-dashboard-grid">
      <!-- 左上:销量趋势 -->
      <div class="bi-panel bi-panel-top-left">
        <PanelHeader title="销售分析-砌块" />
        <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: chartTimeDimension === '年' }"
                @click="handleChartTimeDimensionChange('年')">年</span>
          <span class="tab-item"
                :class="{ active: chartTimeDimension === '月' }"
                @click="handleChartTimeDimensionChange('月')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: chartProductType === '砌块' }"
                @click="handleChartProductTypeChange('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: chartProductType === '板材' }"
                @click="handleChartProductTypeChange('板材')">板材</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>
          <div ref="salesVolumeChart"
               class="echart-fill"></div>
        </div>
      </div>
      <!-- 右上:销售金额 -->
      <div class="bi-panel bi-panel-top-right">
        <PanelHeader title="销售分析-板材" />
        <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: chartTimeDimension2 === '年' }"
                @click="handleChartTimeDimensionChange2('年')">年</span>
          <span class="tab-item"
                :class="{ active: chartTimeDimension2 === '月' }"
                @click="handleChartTimeDimensionChange2('月')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: chartProductType2 === '砌块' }"
                @click="handleChartProductTypeChange2('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: chartProductType2 === '板材' }"
                @click="handleChartProductTypeChange2('板材')">板材</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>
          <div ref="salesAmountChart"
               class="echart-fill"></div>
        </div>
      </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-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>
            <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 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>
            <div class="center-metric-value">{{ totalSalesAreaCount }}</div>
            <div class="center-metric-unit">区</div>
            <div class="center-metric-label">销售方数</div>
            <div class="center-metric-value">{{ totalSalesAreaCount.toFixed(0) }}</div>
            <div class="center-metric-unit">方</div>
          </div>
        </div>
      </div>
      <!-- 左下:产品类型销量 -->
      <div class="bi-panel bi-panel-bottom-left">
        <PanelHeader title="客户销量排名分析-砌块" />
        <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: tableTimeDimension === '年' }"
                @click="handleTableTimeDimensionChange('年')">年</span>
          <span class="tab-item"
                :class="{ active: tableTimeDimension === '月' }"
                @click="handleTableTimeDimensionChange('月')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: tableProductType === '砌块' }"
                @click="handleTableProductTypeChange('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: tableProductType === '板材' }"
                @click="handleTableProductTypeChange('板材')">板材</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 tableSalesAreas"
                  :key="area"
                  class="cf-tab"
                  :class="{ active: tableSelectedArea === area }"
                  @click="handleTableAreaChange(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="(index + 1) % 2 === 0 ? 'evenTableTr' : 'oddTableTr'"
                      v-for="(item, index) in filteredTableSalesData"
                      :key="item.period + item.area + index">
                    <td>{{ index + 1 }}</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">{{ filteredTableSalesTotal }} m³</div>
          </div>
        </div>
      </div>
      <!-- 中下:新增客户分析(分产品类型趋势) -->
      <div class="bi-panel bi-panel-bottom-center">
        <PanelHeader title="新增客户趋势分析" />
        <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 === '年' }"
                @click="handleCustomerTimeDimensionChange('年')">年</span>
          <span class="tab-item"
                :class="{ active: customerTimeDimension === '月' }"
                @click="handleCustomerTimeDimensionChange('月')">月</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>
            <span>单位:家</span>
          </div>
          <div ref="productTypeTrendChart" class="echart-fill"></div>
          <div ref="productTypeTrendChart"
               class="echart-fill"></div>
        </div>
      </div>
      <!-- 右下:销售区域销量 -->
      <div class="bi-panel bi-panel-bottom-right">
        <PanelHeader title="客户销量排名分析-板材" />
        <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: tableTimeDimension2 === '年' }"
                @click="handleTableTimeDimensionChange2('年')">年</span>
          <span class="tab-item"
                :class="{ active: tableTimeDimension2 === '月' }"
                @click="handleTableTimeDimensionChange2('月')">月</span>
        </div>
        <div class="panel-tabs2">
          <span class="tab-item"
                :class="{ active: tableProductType2 === '砌块' }"
                @click="handleTableProductTypeChange2('砌块')">砌块</span>
          <span class="tab-item"
                :class="{ active: tableProductType2 === '板材' }"
                @click="handleTableProductTypeChange2('板材')">板材</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 tableSalesAreas"
                  :key="area"
                  class="cf-tab"
                  :class="{ active: tableSelectedArea === area }"
                  @click="handleTableAreaChange2(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>
                  <th>销售额(元)</th>
                </tr>
              </thead>
              <div class="scroll-table-content">
                <tbody ref="boardTableBody">
                  <tr :class="(index + 1) % 2 === 0 ? 'evenTableTr' : 'oddTableTr'"
                      v-for="(item, index) in filteredAmountSalesData"
                      :key="item.period + item.area + index">
                    <td>{{ index + 1 }}</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-value2">{{ filteredAmountSalesTotal }} 元</div>
          </div>
        </div>
      </div>
    </div>
@@ -183,14 +282,20 @@
  import * as echarts from "echarts";
  import dayjs from "dayjs";
  import PanelHeader from "@/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue";
  import {
    getDashboardStatistics,
    getCustomerTrends,
    getSalesAnalysisTrend,
    getSalesAmountAnalysis,
  } from "@/api/reportAnalysis/salesStatistics";
  const router = useRouter();
  const screenRoot = ref(null);
  const isFullscreen = ref(false);
  // 顶部栏时间(用于匹配BI大屏效果图)
  const now = ref(dayjs())
  const currentTime = computed(() => now.value.format("HH:mm:ss"))
  const now = ref(dayjs());
  const currentTime = computed(() => now.value.format("HH:mm:ss"));
  const currentDateText = computed(() => {
    const weekMap = {
      0: "星期日",
@@ -200,10 +305,10 @@
      4: "星期四",
      5: "星期五",
      6: "星期六",
    }
    return `${now.value.format("YYYY-MM-DD")} ${weekMap[now.value.day()] || ""}`
  })
  let timeTicker = null
    };
    return `${now.value.format("YYYY-MM-DD")} ${weekMap[now.value.day()] || ""}`;
  });
  let timeTicker = null;
  const handleFullscreenChange = () => {
    isFullscreen.value = !!document.fullscreenElement;
@@ -238,6 +343,64 @@
  const productTypeChart = ref(null);
  const salesAreaChart = ref(null);
  const productTypeTrendChart = ref(null);
  const blockTableBody = ref(null);
  const boardTableBody = ref(null);
  // 选择器数据
  // 销量分析趋势图
  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("年");
  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);
@@ -504,11 +667,10 @@
    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 centerNewCustomerCount = ref(112);
  const completedOrders = ref(1829);
  const totalSalesAreaCount = ref(12);
  const newCustomerCount = computed(() => {
    return filteredData.value.reduce((sum, item) => sum + item.newCustomers, 0);
@@ -526,17 +688,177 @@
    return Object.values(customerMap).reduce((sum, count) => sum + count, 0);
  });
  // 中间中心环指标(用于大屏展示,使用现有统计数据做映射)
  const centerNewCustomerCount = computed(() => 112);
  const completedOrders = computed(() => 1829);
  const salesOrderCount = computed(() => 34);
  const totalSalesAreaCount = computed(() => 12);
  // 客户趋势数据
  const customerTrendsData = ref([]);
  // 销量分析趋势数据
  const salesAnalysisTrendData = ref([]);
  // 销量数据统计表格数据
  const tableSalesData = ref([]);
  // 销量数据统计表格总计
  const tableSalesTotal = ref(0);
  // 动态销售区域列表
  const tableSalesAreas = ref([]);
  // 变化率计算(模拟)
  const salesVolumeChange = ref("+5.2");
  const salesAmountChange = ref("+7.8");
  const customerCountChange = ref("+3.5");
  const totalCustomerChange = ref("+2.1");
  // 获取中心看板数据
  const fetchDashboardData = async () => {
    try {
      const response = await getDashboardStatistics();
      if (response && response.data) {
        totalSalesAmount.value = response.data.price || 0;
        completedOrders.value = response.data.delivery || 0;
        centerNewCustomerCount.value = response.data.customer || 0;
        totalSalesAreaCount.value = response.data.volume || 0;
      }
    } catch (error) {
      console.error("获取中心看板数据失败:", error);
    }
  };
  // 获取客户趋势数据
  const fetchCustomerTrendsData = async () => {
    try {
      const response = await getCustomerTrends({
        days: customerTimeDimension.value,
      });
      if (response && response.data) {
        // API返回的数据结构如下:
        // {
        //   "dates": ["2026-01-01", "2025-01-01", ...],
        //   "customerTrends": [{"ALLIN": 4, "银川": 3, ...}, ...]
        // }
        customerTrendsData.value = response.data;
        updateCharts();
      }
    } catch (error) {
      console.error("获取客户趋势数据失败:", error);
    }
  };
  // 获取销量分析趋势数据
  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(() => {
@@ -555,10 +877,157 @@
    });
  });
  // 筛选后的表格数据
  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 periods = ["6/9", "6/10", "6/11", "6/12", "6/12", "6/13"];
    const values = [132, 168, 168, 198, 168, 198];
    const colors = [
      "#00A4ED",
      "#34D8F7",
      "#4A8BFF",
      "#8A6BFF",
      "#C8C447",
      "#FF6B6B",
      "#FF9500",
      "#4CD964",
      "#5AC8FA",
    ];
    const periodType = blockTimeDimension.value;
    // 生成时间段
    let periods = [];
    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 {
      // 模拟数据
      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;
        });
        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",
@@ -566,14 +1035,31 @@
        trigger: "axis",
        backgroundColor: "rgba(0,0,0,0.55)",
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: 1,
        textStyle: { color: "#B8C8E0" },
        formatter: "{b}: {c} 立方米",
        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(10),
        },
        itemWidth: getResponsiveValue(10),
        itemHeight: getResponsiveValue(10),
      },
      grid: {
        left: "10%",
        right: "4%",
        bottom: "16%",
        left: "1%",
        right: "1%",
        bottom: "1%",
        top: "28%",
        containLabel: true,
      },
@@ -582,40 +1068,76 @@
        data: periods,
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisTick: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 10 },
        axisLabel: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(11),
          margin: getResponsiveValue(10),
        },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
        name: "",
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 },
        axisLabel: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(11),
          margin: getResponsiveValue(8),
        },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series: [
        {
          data: values,
          type: "line",
          smooth: true,
          symbolSize: 8,
          lineStyle: { width: 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 { 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",
      "#4A8BFF",
      "#8A6BFF",
      "#C8C447",
      "#FF6B6B",
    ];
    // 为每个销售区生成数据
    const series = salesAreas.map((area, index) => {
      const data = customerTrends.map(item => item[area] || 0);
      return {
        name: area,
        data: data,
        type: "bar",
        smooth: true,
        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 % colors.length] + "80" },
            { offset: 1, color: colors[index % colors.length] + "00" },
          ]),
        },
      };
    });
    return {
      backgroundColor: "transparent",
@@ -623,50 +1145,58 @@
        trigger: "axis",
        backgroundColor: "rgba(0,0,0,0.55)",
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: 1,
        textStyle: { color: "#B8C8E0" },
        formatter: "{b}: {c} 万元",
        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(10),
        },
        itemWidth: getResponsiveValue(10),
        itemHeight: getResponsiveValue(10),
      },
      grid: {
        left: "10%",
        right: "4%",
        bottom: "16%",
        left: "1%",
        right: "1%",
        bottom: "1%",
        top: "28%",
        containLabel: true,
      },
      xAxis: {
        type: "category",
        data: periods,
        data: dates,
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisTick: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 10 },
        axisLabel: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(11),
          margin: getResponsiveValue(10),
        },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
        name: "",
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 },
        axisLabel: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(11),
          margin: getResponsiveValue(8),
        },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series: [
        {
          data: values,
          type: "line",
          smooth: true,
          symbolSize: 8,
          itemStyle: {
            color: "#00A4ED",
          },
          lineStyle: { width: 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,
    };
  });
@@ -674,7 +1204,14 @@
  const productTypeChartOption = computed(() => {
    const types = ["客户BB", "客户AA", "客户CC", "客户DD", "客户DD", "客户DD"];
    const values = [130, 120, 102, 90, 90, 70];
    const barColors = ["#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447", "#C8C447", "#C8C447"];
    const barColors = [
      "#34D8F7",
      "#4A8BFF",
      "#8A6BFF",
      "#C8C447",
      "#C8C447",
      "#C8C447",
    ];
    return {
      backgroundColor: "transparent",
@@ -683,8 +1220,8 @@
        axisPointer: { type: "shadow" },
        backgroundColor: "rgba(0,0,0,0.55)",
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: 1,
        textStyle: { color: "#B8C8E0" },
        borderWidth: getResponsiveValue(1),
        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
        formatter: "{b}: {c} 立方米",
      },
      grid: {
@@ -697,7 +1234,7 @@
      xAxis: {
        type: "value",
        axisLine: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11 },
        axisLabel: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      yAxis: {
@@ -705,17 +1242,26 @@
        data: types,
        axisTick: { show: false },
        axisLine: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 },
        axisLabel: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(11),
          margin: getResponsiveValue(8),
        },
      },
      series: [
        {
          name: "销量(立方米)",
          type: "bar",
          barWidth: 14,
          barWidth: getResponsiveValue(14),
          data: values,
          itemStyle: {
            color: params => barColors[params.dataIndex] || "#00A4ED",
            borderRadius: [6, 6, 6, 6],
            borderRadius: [
              getResponsiveValue(6),
              getResponsiveValue(6),
              getResponsiveValue(6),
              getResponsiveValue(6),
            ],
          },
          label: {
            show: false,
@@ -729,7 +1275,14 @@
  const salesAreaChartOption = computed(() => {
    const areas = ["客户BB", "客户AA", "客户CC", "客户DD", "客户DD", "客户DD"];
    const values = [130, 120, 102, 90, 90, 70];
    const barColors = ["#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447", "#C8C447", "#C8C447"];
    const barColors = [
      "#34D8F7",
      "#4A8BFF",
      "#8A6BFF",
      "#C8C447",
      "#C8C447",
      "#C8C447",
    ];
    return {
      backgroundColor: "transparent",
@@ -738,8 +1291,8 @@
        axisPointer: { type: "shadow" },
        backgroundColor: "rgba(0,0,0,0.55)",
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: 1,
        textStyle: { color: "#B8C8E0" },
        borderWidth: getResponsiveValue(1),
        textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
        formatter: "{b}: {c} 立方米",
      },
      grid: {
@@ -752,7 +1305,7 @@
      xAxis: {
        type: "value",
        axisLine: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11 },
        axisLabel: { color: "#B8C8E0", fontSize: getResponsiveValue(11) },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      yAxis: {
@@ -760,86 +1313,164 @@
        data: areas,
        axisTick: { show: false },
        axisLine: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 },
        axisLabel: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(11),
          margin: getResponsiveValue(8),
        },
      },
      series: [
        {
          name: "销量(立方米)",
          type: "bar",
          barWidth: 14,
          barWidth: getResponsiveValue(14),
          data: values,
          itemStyle: {
            color: params => barColors[params.dataIndex] || "#00A4ED",
            borderRadius: [6, 6, 6, 6],
            borderRadius: [
              getResponsiveValue(6),
              getResponsiveValue(6),
              getResponsiveValue(6),
              getResponsiveValue(6),
            ],
          },
        },
      ],
    };
  });
  // 新增客户趋势图表配置(按产品类型多折线)
  // 新增客户趋势图表配置(按销售区和年月维度)
  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 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",
      "#FF8B6B",
      "#FFCB6B",
      "#8BC34A",
      "#4CAF50",
    ];
    const periodType = customerTimeDimension.value;
    const series = typeOrder.map((t) => ({
      name: t,
      type: "line",
      smooth: true,
      symbolSize: 7,
      showSymbol: true,
      data: map[t] || [],
      lineStyle: { width: 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 = [];
    let salesAreas = [];
    let series = [];
    if (
      customerTrendsData.value &&
      customerTrendsData.value.dates &&
      customerTrendsData.value.customerTrends
    ) {
      // 使用API返回的数据
      periods = customerTrendsData.value.dates;
      // 提取所有销售区域
      const areaSet = new Set();
      customerTrendsData.value.customerTrends.forEach(item => {
        Object.keys(item).forEach(key => {
          areaSet.add(key);
        });
      });
      // 确保"全部"在第一个位置
      salesAreas = Array.from(areaSet).sort((a, b) => {
        if (a === "全部") return -1;
        if (b === "全部") return 1;
        return 0;
      });
      // 为每个销售区域生成数据
      series = salesAreas.map((area, index) => {
        const data = customerTrendsData.value.customerTrends.map((item, i) => {
          return item[area] || 0;
        });
        return {
          name: area,
          data: data,
          type: "line",
          smooth: false,
          lineStyle: {
            width: getResponsiveValue(1),
            color: colors[index % colors.length],
          },
          itemStyle: { color: colors[index % colors.length] },
        };
      });
    } else {
      // 模拟数据
      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")}`
          );
        }
      }
      salesAreas = [];
      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: 10,
        left: "center",
        textStyle: { color: "#B8C8E0", fontSize: 11, padding: [0, 0, 0, 2] },
        itemWidth: 12,
        itemHeight: 10,
        itemGap: 18,
      },
      tooltip: {
        trigger: "axis",
        backgroundColor: "rgba(0,0,0,0.55)",
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: 1,
        textStyle: { color: "#B8C8E0" },
        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(10),
        },
        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: {
@@ -847,127 +1478,32 @@
        data: periods,
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisTick: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 10 },
        axisLabel: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(11),
          margin: getResponsiveValue(10),
        },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
        name: "",
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 },
        axisLabel: {
          color: "#B8C8E0",
          fontSize: getResponsiveValue(11),
          margin: getResponsiveValue(8),
        },
        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} 立方米",
      },
      xAxis: {
        type: "category",
        data: periods,
      },
      yAxis: {
        type: "value",
        name: "累计销量(立方米)",
      },
      series: [
        {
          data: values,
          type: "line",
          smooth: true,
          areaStyle: {
            opacity: 0.3,
          },
          itemStyle: {
            color: "#E6A23C",
          },
          lineStyle: {
            width: 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} 万元",
      },
      xAxis: {
        type: "category",
        data: periods,
      },
      yAxis: {
        type: "value",
        name: "累计销售金额(万元)",
      },
      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 => {
    return Math.round((baseValue * window.innerWidth) / baseWidth.value);
  };
  // 初始化图表
@@ -1013,12 +1549,13 @@
    updateCharts();
  };
  // 更新图表
  const updateCharts = () => {
    // 更新销量趋势图表
    if (salesVolumeChartInstance) {
      salesVolumeChartInstance.setOption(salesVolumeChartOption.value);
      salesVolumeChartInstance.setOption(
        JSON.parse(JSON.stringify(salesVolumeChartOption.value))
      );
    }
    // 更新销售金额趋势图表
@@ -1056,8 +1593,213 @@
    }
  };
  // 表格动画控制
  let blockScrollTimer = null;
  let boardScrollTimer = null;
  let blockCurrentIndex = 0;
  let boardCurrentIndex = 0;
  const startBlockTableScroll = () => {
    if (blockScrollTimer) {
      clearInterval(blockScrollTimer);
    }
    // 只有当时间维度不是"年"时才启动滚动
    if (tableTimeDimension.value === "年") {
      return;
    }
    const scrollTable = () => {
      if (!blockTableBody.value) 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)";
        // 直接操作DOM,将第一行移到最后
        const firstRow = rows[0];
        blockTableBody.value.removeChild(firstRow);
        blockTableBody.value.appendChild(firstRow);
      }, 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;
    }
    if (amountScrollTimer.value) {
      clearInterval(amountScrollTimer.value);
      amountScrollTimer.value = null;
    }
  };
  // 处理时间维度选择
  const handleBlockTimeDimensionChange = dimension => {
    blockTimeDimension.value = dimension;
    generateBlockSalesData();
  };
  const handleBoardTimeDimensionChange = dimension => {
    boardTimeDimension.value = dimension;
    generateBoardSalesData();
  };
  const blockProductType = ref("砌块");
  // 处理产品类型选择
  const handleBlockProductTypeChange = type => {
    blockProductType.value = type;
    generateBlockSalesData();
  };
  const handleBoardProductTypeChange = type => {
    boardProductType.value = type;
    generateBoardSalesData();
  };
  const blockSelectedArea = ref("全部");
  // 处理销售区选择
  const handleBlockAreaChange = area => {
    blockSelectedArea.value = area;
    generateBlockSalesData();
  };
  const handleBoardAreaChange = area => {
    boardSelectedArea.value = area;
    generateBoardSalesData();
  };
  // 处理新增客户趋势时间维度切换
  const handleCustomerTimeDimensionChange = dimension => {
    customerTimeDimension.value = dimension;
    fetchCustomerTrendsData();
  };
  const blockTimeDimension = ref("年");
  // 生成砌块销售数据
  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");
    // 先更新图表选项,重新计算响应式值
    updateCharts();
    // 然后调整图表大小
    if (salesVolumeChartInstance) {
      salesVolumeChartInstance.resize();
    }
@@ -1082,7 +1824,7 @@
  };
  // 生命周期
  onMounted(() => {
  onMounted(async () => {
    // 启动顶部栏时间刷新
    if (!timeTicker) {
      timeTicker = setInterval(() => {
@@ -1098,14 +1840,276 @@
      endDate.format("YYYY-MM-DD"),
    ];
    // 生成初始数据
    generateBlockSalesData();
    generateBoardSalesData();
    // 获取数据
    await fetchDashboardData();
    await fetchCustomerTrendsData();
    await fetchSalesAnalysisTrendData();
    await fetchTableSalesData();
    await fetchSalesAmountChartData();
    await fetchSalesAmountTableData();
    // 等待DOM更新后初始化图表
    nextTick(() => {
      initCharts();
      // 启动表格滚动动画
      startBlockTableScroll();
      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();
  });
  // 获取产品类型标签类型
@@ -1170,9 +2174,9 @@
  .sales-statistics-container {
    position: relative;
    width: 100%;
    min-height: calc(100vh - 84px);
    min-height: calc(100vh - 8.4vh);
    overflow: hidden;
    color: #B8C8E0;
    color: #b8c8e0;
    background: #041026;
  }
@@ -1185,7 +2189,7 @@
  .bi-bg {
    position: absolute;
    inset: 0;
    background-image: url("@/assets/BI/backImage@2x.png");
    /* background-image: url("@/assets/BI/backImage@2x.png"); */
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
@@ -1196,7 +2200,7 @@
  .bi-topbar {
    position: relative;
    z-index: 2;
    height: 58px;
    height: 5.8vh;
    display: flex;
    align-items: center;
    justify-content: center;
@@ -1206,7 +2210,7 @@
    position: absolute;
    top: 0;
    left: 0;
    height: 58px;
    height: 8vh;
    width: 100%;
    object-fit: cover;
    z-index: 0;
@@ -1217,7 +2221,7 @@
    position: relative;
    z-index: 1;
    width: 100%;
    padding: 0 28px;
    padding: 0 2.8vh;
    display: flex;
    align-items: center;
    justify-content: center;
@@ -1227,58 +2231,57 @@
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    font-size: 26px;
    font-size: 2.6vh;
    font-weight: 800;
    letter-spacing: 1px;
    background: linear-gradient(180deg, #ffffff 0%, #B8DFFF 100%);
    letter-spacing: 0.1vh;
    background: linear-gradient(180deg, #ffffff 0%, #b8dfff 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    text-shadow: 0 0 26px rgba(0, 164, 237, 0.55);
    text-shadow: 0 0 2.6vh rgba(0, 164, 237, 0.55);
  }
  .bi-topbar-left {
    position: absolute;
    left: 10px;
    left: 1vh;
    display: flex;
    align-items: center;
    gap: 8px;
    gap: 0.8vh;
    color: rgba(208, 231, 255, 0.85);
    font-size: 13px;
    font-size: 1.3vh;
  }
  .status-sun {
    color: #ffd85e;
    text-shadow: 0 0 10px rgba(255, 216, 94, 0.8);
    font-size: 13px;
    text-shadow: 0 0 1vh rgba(255, 216, 94, 0.8);
    font-size: 1.3vh;
    line-height: 1;
  }
  .bi-topbar-meta {
    position: absolute;
    right: 52px;
    top: 16px;
    font-size: 12px;
    right: 5.2vh;
    /* top: 1.6vh; */
    font-size: 1.2vh;
    font-weight: 500;
    letter-spacing: 0.5px;
    letter-spacing: 0.05vh;
    color: rgba(208, 231, 255, 0.85);
    display: flex;
    align-items: center;
    gap: 10px;
    gap: 1vh;
  }
  .fullscreen-btn {
    position: absolute;
    right: 10px;
    top: 12px;
    bottom: -1vh;
    transform: none;
    border: 1px solid rgba(64, 158, 255, 0.45);
    border: 0.1vh solid rgba(64, 158, 255, 0.45);
    background: rgba(0, 164, 237, 0.14);
    color: #d0e7ff;
    width: 34px;
    height: 34px;
    border-radius: 6px;
    width: 3.4vh;
    height: 3.4vh;
    border-radius: 0.6vh;
    padding: 0;
    cursor: pointer;
    transition: all 0.2s ease;
@@ -1290,7 +2293,7 @@
  .fullscreen-btn:hover {
    background: rgba(0, 164, 237, 0.24);
    box-shadow: 0 0 12px rgba(0, 164, 237, 0.3);
    box-shadow: 0 0 1.2vh rgba(0, 164, 237, 0.3);
  }
  .bi-topbar-sep {
@@ -1301,62 +2304,70 @@
  .bi-dashboard-grid {
    position: relative;
    z-index: 2;
    height: calc(100vh - 84px - 58px);
    min-height: 450px;
    padding: 10px 18px 14px;
    height: calc(100vh - 8.4vh - 5.8vh);
    min-height: 45vh;
    padding: 1vh 1.8vh 1.4vh;
    display: grid;
    grid-template-columns: 1fr 1.05fr 1fr;
    grid-template-rows: 1fr 1fr;
    gap: 12px;
    gap: 1.2vh;
  }
  .sales-statistics-container.is-fullscreen .bi-dashboard-grid {
    height: calc(100vh - 58px);
    height: calc(100vh - 5.8vh);
  }
  .bi-panel {
    background: rgba(3, 18, 46, 0.62);
    border: 1px solid rgba(64, 158, 255, 0.35);
    border-radius: 4px;
    border: 0.1vh solid rgba(64, 158, 255, 0.35);
    border-radius: 0.4vh;
    overflow: hidden;
    box-shadow: 0 0 22px rgba(0, 164, 237, 0.12);
    box-shadow: 0 0 2.2vh rgba(0, 164, 237, 0.12);
    display: flex;
    flex-direction: column;
    position: relative;
  }
  .bi-panel-title {
    height: 44px;
    height: 4.4vh;
    display: flex;
    align-items: center;
    padding: 0 18px;
    font-size: 15px;
    padding: 0 1.8vh;
    font-size: 1.5vh;
    font-weight: 700;
    color: #B8C8E0;
    color: #b8c8e0;
    background: linear-gradient(
      90deg,
      rgba(0, 164, 237, 0.2),
      rgba(0, 164, 237, 0.04)
    );
    border-bottom: 1px solid rgba(64, 158, 255, 0.25);
    border-bottom: 0.1vh solid rgba(64, 158, 255, 0.25);
  }
  .panel-tabs {
  .panel-tabs,
  .panel-tabs2 {
    position: absolute;
    top: 8px;
    right: 12px;
    top: 0.8vh;
    display: flex;
    gap: 6px;
    gap: 0.6vh;
    z-index: 4;
  }
  .panel-tabs {
    right: 1.2vh;
  }
  .panel-tabs2 {
    right: 8vh;
  }
  .tab-item {
    font-size: 12px;
    font-size: 1.2vh;
    color: rgba(184, 200, 224, 0.75);
    padding: 1px 5px;
    border: 1px solid rgba(64, 158, 255, 0.25);
    border-radius: 3px;
    padding: 0.1vh 0.5vh;
    border: 0.1vh solid rgba(64, 158, 255, 0.25);
    border-radius: 0.3vh;
    line-height: 1.4;
    cursor: pointer;
  }
  .tab-item.active {
@@ -1367,7 +2378,77 @@
  .bi-panel-body {
    flex: 1;
    padding: 8px 10px;
    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 {
@@ -1377,21 +2458,23 @@
  .chart-filter-tabs {
    display: flex;
    gap: 6px;
    margin: 0 0 5px 0;
    gap: 0.6vh;
    margin: 0 0 0.5vh 0;
    justify-self: end;
  }
  .cf-tab {
    font-size: 11px;
    font-size: 1.1vh;
    color: rgba(184, 200, 224, 0.68);
    background: rgba(18, 56, 106, 0.65);
    border: 1px solid rgba(64, 158, 255, 0.25);
    padding: 3px 9px;
    border: 0.1vh solid rgba(64, 158, 255, 0.25);
    padding: 0.3vh 0.9vh;
    line-height: 1;
    cursor: pointer;
  }
  .cf-tab.active {
    color: #D9ECFF;
    color: #d9ecff;
    background: rgba(0, 108, 208, 0.85);
    border-color: rgba(64, 158, 255, 0.65);
  }
@@ -1399,57 +2482,90 @@
  .chart-unit-row {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
    font-size: 1.2vh;
    color: rgba(208, 231, 255, 0.88);
    margin-bottom: 4px;
    padding: 0 2px;
    margin-bottom: 0.4vh;
    padding: 0 0.2vh;
  }
  .dot-legend::before {
    content: "";
    display: inline-block;
    width: 8px;
    height: 8px;
    background: #65A0FF;
    margin-right: 6px;
    width: 0.8vh;
    height: 0.8vh;
    background: #65a0ff;
    margin-right: 0.6vh;
  }
  .chart-mini-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 18px;
    color: #D9ECFF;
    font-weight: 700;
    margin: 0 0 8px 0;
    line-height: 1;
    gap: 0.8vh;
    font-size: 1.8vh;
    color: #d9ecff;
  }
  /* 面板底部合计行 */
  .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: 1.8vh;
    text-shadow: 0 0 1vh rgba(0, 164, 237, 0.5);
  }
  .diamond {
    width: 10px;
    height: 10px;
    background: #1E8BFF;
    width: 1vh;
    height: 1vh;
    background: #1e8bff;
    transform: rotate(45deg);
    display: inline-block;
  }
  .chart-unit-single {
    justify-content: flex-start;
    margin-bottom: 2px;
    margin-bottom: 0.2vh;
  }
  .bi-panel-top-left .echart-fill,
  .bi-panel-top-right .echart-fill {
    height: calc(100% - 44px);
    height: calc(100% - 4.4vh);
  }
  .bi-panel-bottom-left .echart-fill,
  .bi-panel-bottom-right .echart-fill {
    height: calc(100% - 28px);
    height: calc(100% - 2.8vh);
  }
  .bi-panel-bottom-center .echart-fill {
    height: calc(100% - 44px);
    height: calc(100% - 4.4vh);
  }
  .bi-panel-top-left {
@@ -1467,6 +2583,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 {
@@ -1477,6 +2598,11 @@
  .bi-panel-bottom-right {
    grid-column: 3;
    grid-row: 2;
    overflow-y: auto;
  }
  .bi-panel-bottom-right::-webkit-scrollbar {
    width: 0vh;
    height: 0vh;
  }
  /* 中心环浮层(绝对定位在网格上方) */
@@ -1484,20 +2610,31 @@
    grid-column: 2;
    grid-row: 1 / span 2;
    position: absolute;
    left:25%;
    background: url("@/assets/BI/imageSS@2x.png") no-repeat bottom center;
    background-size: 100% 30%;
    left: 25%;
    top: 25%;
    transform: translate(-50%, -50%);
    width: 400px;
    height: 275px;
    width: 60vh;
    height: 40.5vh;
    z-index: 3;
    pointer-events: none;
  }
  .center-ring-box {
    position: absolute;
    /* inset: 0; */
    height: 100%;
    width: 100%;
    /* background-color: #fff; */
    background: url("@/assets/BI/imageSStop.png") no-repeat center center;
    background-size: 80% 90%;
  }
  .center-ring-bg {
    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 {
@@ -1511,18 +2648,18 @@
    position: absolute;
    left: 50%;
    top: 56%;
    width: 370px;
    height: 146px;
    width: 37vh;
    height: 14.6vh;
    transform: translate(-50%, -50%) rotate(-18deg);
    border: 2px solid rgba(40, 186, 255, 0.45);
    border: 0.2vh solid rgba(40, 186, 255, 0.45);
    border-radius: 50%;
    filter: drop-shadow(0 0 8px rgba(0, 164, 237, 0.35));
    filter: drop-shadow(0 0 0.8vh rgba(0, 164, 237, 0.35));
    opacity: 0.7;
  }
  .center-ring-content::after {
    width: 360px;
    height: 150px;
    width: 36vh;
    height: 15vh;
    transform: translate(-50%, -50%) rotate(26deg);
    border-color: rgba(80, 220, 255, 0.35);
    opacity: 0.55;
@@ -1530,15 +2667,15 @@
  .center-ring-title {
    position: absolute;
    top: 116px;
    top: 50%;
    left: 50%;
    transform: translateX(-50%);
    font-size: 50px;
    line-height: 1.06;
    transform: translate(-50%, -50%);
    font-size: 3.6vh;
    line-height: 1.05;
    text-align: center;
    font-weight: 900;
    color: #EAF6FF;
    text-shadow: 0 0 34px rgba(0, 164, 237, 0.6);
    color: #eaf6ff;
    text-shadow: 0 0 2.2vh rgba(0, 164, 237, 0.55);
    z-index: 2;
  }
@@ -1547,112 +2684,153 @@
    position: absolute;
    left: 50%;
    top: 50%;
    width: 155px;
    height: 155px;
    width: 15.5vh;
    height: 15.5vh;
    transform: translate(-50%, -50%);
    background: radial-gradient(circle, rgba(43, 199, 255, 0.26) 0%, rgba(8, 28, 61, 0.86) 70%);
    border: 2px solid rgba(39, 198, 255, 0.46);
    background: radial-gradient(
      circle,
      rgba(43, 199, 255, 0.26) 0%,
      rgba(8, 28, 61, 0.86) 70%
    );
    border: 0.2vh solid rgba(39, 198, 255, 0.46);
    border-radius: 50%;
    box-shadow: 0 0 20px rgba(0, 164, 237, 0.45), inset 0 0 26px rgba(0, 164, 237, 0.2);
    box-shadow: 0 0 2vh rgba(0, 164, 237, 0.45),
      inset 0 0 2.6vh rgba(0, 164, 237, 0.2);
    z-index: -1;
  }
  .center-metric {
    position: absolute;
    width: 155px;
    width: 15.5vh;
    z-index: 3;
    text-align: center;
  }
  .center-metric::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 104px;
    height: 104px;
    transform: translate(-50%, -50%);
    border-radius: 50%;
    border: 2px solid rgba(71, 223, 255, 0.4);
    background: radial-gradient(circle, rgba(37, 177, 255, 0.25) 0%, rgba(8, 33, 69, 0.55) 70%);
    box-shadow: 0 0 18px rgba(0, 164, 237, 0.35), inset 0 0 18px rgba(0, 164, 237, 0.2);
    z-index: -1;
    height: 12vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }
  .center-metric-label {
    font-size: 16px;
    font-size: 1.2vh;
    font-weight: 500;
    color: rgba(234, 246, 255, 0.9);
    margin-top: 4px;
    margin-top: 0;
  }
  .center-metric-value {
    font-size: 48px;
    font-weight: 700;
    color: #EAF6FF;
    text-shadow: 0 0 8px rgba(0, 229, 255, 0.22);
    line-height: 1.1;
    font-size: 3.4vh;
    font-weight: 800;
    color: #eaf6ff;
    text-shadow: 0 0 0.8vh rgba(0, 229, 255, 0.22);
    line-height: 1;
  }
  .center-metric-unit {
    margin-top: 2px;
    font-size: 14px;
    margin-top: 0;
    font-size: 1.2vh;
    color: rgba(208, 231, 255, 0.85);
  }
  .m1 {
    top: -6px;
    left: -26px;
    top: 2.5vh;
    left: 4.8vh;
    text-align: left;
  }
  .m2 {
    top: -6px;
    right: -26px;
    top: 4.1vh;
    right: 8.6vh;
    text-align: right;
  }
  .m3 {
    bottom: 66px;
    left: -30px;
    bottom: 7.9vh;
    left: 4vh;
    text-align: left;
  }
  .m4 {
    bottom: 66px;
    right: -30px;
    bottom: 7vh;
    right: 5.4vh;
    text-align: right;
  }
  @media (max-width: 1100px) {
    .bi-topbar-content {
      padding: 0 14px;
      padding: 0 1.4vh;
    }
    .center-ring {
      left: 45.2%;
      width: 330px;
      height: 245px;
      top: 24px;
      width: 33vh;
      height: 24.5vh;
      top: 2.4vh;
    }
    .center-ring-title {
      top: 122px;
      font-size: 26px;
      top: 50%;
      font-size: 2.8vh;
      transform: translate(-50%, -50%);
    }
    .center-metric {
      height: 10.5vh;
    }
    .m1 {
      top: 52px;
      left: 42px;
      top: 5.2vh;
      left: 4.2vh;
    }
    .m2 {
      top: 54px;
      right: 42px;
      top: 5.4vh;
      right: 4.2vh;
    }
    .m3 {
      bottom: 62px;
      left: 48px;
      bottom: 6.2vh;
      left: 4.8vh;
    }
    .m4 {
      bottom: 68px;
      right: 44px;
      bottom: 6.8vh;
      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>