yyb
5 小时以前 d648788841a802a2f56db5bd657a408b9b229d65
销售看板
已添加1个文件
已修改1个文件
2680 ■■■■ 文件已修改
src/views/reportAnalysis/salesStatistics/index copy.vue 1304 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/salesStatistics/index.vue 1376 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/reportAnalysis/salesStatistics/index copy.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,1304 @@
<template>
  <div class="sales-statistics-container">
    <div class="data-dashboard">
      <!-- é¡µé¢æ ‡é¢˜ -->
      <!-- <div class="dashboard-header">
        <div class="factory-name">销售统计看板</div>
      </div> -->
      <!-- ç­›é€‰æ¡ä»¶ -->
      <div class="filter-area">
        <div class="filter-section">
          <span class="filter-label">时间范围:</span>
          <el-date-picker v-model="dateRange"
                          type="daterange"
                          range-separator="至"
                          start-placeholder="开始日期"
                          end-placeholder="结束日期"
                          value-format="YYYY-MM-DD"
                          @change="handleDateChange"
                          style="width: 240px;" />
        </div>
        <div class="filter-section">
          <span class="filter-label">产品类型:</span>
          <el-select v-model="productType"
                     placeholder="请选择产品类型"
                     @change="handleFilterChange"
                     style="width: 160px;">
            <el-option label="全部"
                       value="" />
            <el-option label="砌块"
                       value="block" />
            <el-option label="板材"
                       value="board" />
            <el-option label="型材"
                       value="profile" />
          </el-select>
        </div>
        <div class="filter-section">
          <span class="filter-label">销售区域:</span>
          <el-select v-model="salesArea"
                     placeholder="请选择销售区域"
                     @change="handleFilterChange"
                     style="width: 160px;">
            <el-option label="全部"
                       value="" />
            <el-option label="华东"
                       value="east" />
            <el-option label="华北"
                       value="north" />
            <el-option label="华南"
                       value="south" />
            <el-option label="西南"
                       value="southwest" />
            <el-option label="西北"
                       value="northwest" />
          </el-select>
        </div>
        <div class="filter-section">
          <span class="filter-label">统计维度:</span>
          <el-select v-model="statDimension"
                     placeholder="请选择统计维度"
                     @change="handleFilterChange"
                     style="width: 160px;">
            <el-option label="月度"
                       value="month" />
            <el-option label="年度"
                       value="year" />
          </el-select>
        </div>
      </div>
      <div class="dashboard-content">
        <!-- æ ¸å¿ƒæŒ‡æ ‡å¡ç‰‡ -->
        <div class="row row-1">
          <div class="panel-card card-1">
            <div class="panel-title">合计销量</div>
            <div class="stats-grid">
              <div class="stat-item">
                <div class="stat-value sales-volume-color">{{ totalSalesVolume }}</div>
                <div class="stat-unit">立方米</div>
                <div class="stat-change">{{ salesVolumeChange }}%</div>
              </div>
            </div>
          </div>
          <div class="panel-card card-2">
            <div class="panel-title">销售金额</div>
            <div class="stats-grid">
              <div class="stat-item">
                <div class="stat-value sales-amount-color">{{ totalSalesAmount }}</div>
                <div class="stat-unit">万元</div>
                <div class="stat-change">{{ salesAmountChange }}%</div>
              </div>
            </div>
          </div>
          <div class="panel-card card-3">
            <div class="panel-title">新增客户</div>
            <div class="stats-grid">
              <div class="stat-item">
                <div class="stat-value new-customer-color">{{ newCustomerCount }}</div>
                <div class="stat-unit">个</div>
                <div class="stat-change">{{ customerCountChange }}%</div>
              </div>
            </div>
          </div>
          <div class="panel-card card-4">
            <div class="panel-title">合计客户</div>
            <div class="stats-grid">
              <div class="stat-item">
                <div class="stat-value total-customer-color">{{ totalCustomerCount }}</div>
                <div class="stat-unit">个</div>
                <div class="stat-change">{{ totalCustomerChange }}%</div>
              </div>
            </div>
          </div>
        </div>
        <!-- é”€é‡å’Œé”€å”®é‡‘额趋势 -->
        <div class="row row-2">
          <div class="panel-card card-5">
            <div class="panel-title">销量趋势</div>
            <div class="chart-container">
              <div ref="salesVolumeChart"
                   style="width: 100%; height: 100%;"></div>
            </div>
          </div>
          <div class="panel-card card-6">
            <div class="panel-title">销售金额趋势</div>
            <div class="chart-container">
              <div ref="salesAmountChart"
                   style="width: 100%; height: 100%;"></div>
            </div>
          </div>
        </div>
        <!-- ç´¯è®¡æ•°æ®è¶‹åŠ¿ -->
        <!-- <div class="row row-3">
          <div class="panel-card card-10">
            <div class="panel-title">累计销量趋势</div>
            <div class="chart-container">
              <div ref="cumulativeSalesVolumeChart"
                   style="width: 100%; height: 100%;"></div>
            </div>
          </div>
          <div class="panel-card card-11">
            <div class="panel-title">累计销售金额趋势</div>
            <div class="chart-container">
              <div ref="cumulativeSalesAmountChart"
                   style="width: 100%; height: 100%;"></div>
            </div>
          </div>
        </div> -->
        <!-- å›¾è¡¨åŒºåŸŸå’Œè¡¨æ ¼ -->
        <div class="row row-4">
          <!-- å·¦è¾¹ï¼šè¯¦ç»†æ•°æ®è¡¨æ ¼ -->
          <div class="panel-card card-9"
               style="flex: 2;">
            <div class="panel-title">销售统计详细数据</div>
            <div class="table-container">
              <el-table :data="tableData"
                        style="width: 100%">
                <el-table-column prop="productType"
                                 label="产品类型"
                                 width="120"
                                 align="center">
                  <template #default="scope">
                    <el-tag :type="getProductTypeType(scope.row.productType)">
                      {{ scope.row.productType }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="salesArea"
                                 label="销售区域"
                                 width="120"
                                 align="center">
                  <template #default="scope">
                    <el-tag :type="getSalesAreaType(scope.row.salesArea)">
                      {{ scope.row.salesArea }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="period"
                                 label="统计周期"
                                 width="120" />
                <el-table-column prop="salesVolume"
                                 label="销量(立方米)"
                                 align="right">
                  <template #default="scope">
                    <span class="data-value">{{ scope.row.salesVolume }}</span>
                  </template>
                </el-table-column>
                <el-table-column prop="salesAmount"
                                 label="销售金额(万元)"
                                 align="right">
                  <template #default="scope">
                    <span class="data-value">{{ scope.row.salesAmount }}</span>
                  </template>
                </el-table-column>
                <el-table-column prop="newCustomers"
                                 label="新增客户(个)"
                                 width="150"
                                 align="right">
                  <template #default="scope">
                    <span class="data-value">{{ scope.row.newCustomers }}</span>
                  </template>
                </el-table-column>
                <el-table-column prop="totalCustomers"
                                 label="合计客户(个)"
                                 width="150"
                                 align="right">
                  <template #default="scope">
                    <span class="data-value">{{ scope.row.totalCustomers }}</span>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </div>
          <!-- å³è¾¹ï¼šäº§å“ç±»åž‹åˆ†å¸ƒå’Œé”€å”®åŒºåŸŸåˆ†å¸ƒ -->
          <div class="chart-column"
               style="flex: 1; display: flex; flex-direction: column; gap: 20px;">
            <div class="panel-card card-7"
                 style="flex: 1;">
              <div class="panel-title">产品类型分布</div>
              <div class="chart-container">
                <div ref="productTypeChart"
                     style="width: 100%; height: 100%;"></div>
              </div>
            </div>
            <div class="panel-card card-8"
                 style="flex: 1;">
              <div class="panel-title">销售区域分布</div>
              <div class="chart-container">
                <div ref="salesAreaChart"
                     style="width: 100%; height: 100%;"></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script setup>
  import {
    ref,
    computed,
    onMounted,
    onBeforeUnmount,
    watch,
    nextTick,
  } from "vue";
  import { useRouter } from "vue-router";
  import * as echarts from "echarts";
  import dayjs from "dayjs";
  const router = useRouter();
  // ç­›é€‰æ¡ä»¶
  const dateRange = ref([]);
  const productType = ref("");
  const salesArea = ref("");
  const statDimension = ref("month");
  // å›¾è¡¨å¼•用
  const salesVolumeChart = ref(null);
  const salesAmountChart = ref(null);
  const productTypeChart = ref(null);
  const salesAreaChart = ref(null);
  const cumulativeSalesVolumeChart = ref(null);
  const cumulativeSalesAmountChart = ref(null);
  // å›¾è¡¨å®žä¾‹
  let salesVolumeChartInstance = null;
  let salesAmountChartInstance = null;
  let productTypeChartInstance = null;
  let salesAreaChartInstance = null;
  let cumulativeSalesVolumeChartInstance = null;
  let cumulativeSalesAmountChartInstance = null;
  // æ¨¡æ‹Ÿæ•°æ®
  const mockData = [
    // 2026å¹´1月数据
    {
      productType: "砌块",
      salesArea: "华东",
      period: "2026-01",
      salesVolume: 1200,
      salesAmount: 180,
      newCustomers: 5,
      totalCustomers: 120,
    },
    {
      productType: "砌块",
      salesArea: "华北",
      period: "2026-01",
      salesVolume: 800,
      salesAmount: 120,
      newCustomers: 3,
      totalCustomers: 80,
    },
    {
      productType: "砌块",
      salesArea: "华南",
      period: "2026-01",
      salesVolume: 600,
      salesAmount: 90,
      newCustomers: 2,
      totalCustomers: 60,
    },
    {
      productType: "板材",
      salesArea: "华东",
      period: "2026-01",
      salesVolume: 900,
      salesAmount: 270,
      newCustomers: 4,
      totalCustomers: 100,
    },
    {
      productType: "板材",
      salesArea: "华北",
      period: "2026-01",
      salesVolume: 500,
      salesAmount: 150,
      newCustomers: 2,
      totalCustomers: 70,
    },
    {
      productType: "型材",
      salesArea: "华东",
      period: "2026-01",
      salesVolume: 400,
      salesAmount: 200,
      newCustomers: 3,
      totalCustomers: 50,
    },
    // 2026å¹´2月数据
    {
      productType: "砌块",
      salesArea: "华东",
      period: "2026-02",
      salesVolume: 1300,
      salesAmount: 195,
      newCustomers: 4,
      totalCustomers: 124,
    },
    {
      productType: "砌块",
      salesArea: "华北",
      period: "2026-02",
      salesVolume: 850,
      salesAmount: 127.5,
      newCustomers: 2,
      totalCustomers: 82,
    },
    {
      productType: "砌块",
      salesArea: "华南",
      period: "2026-02",
      salesVolume: 650,
      salesAmount: 97.5,
      newCustomers: 1,
      totalCustomers: 61,
    },
    {
      productType: "板材",
      salesArea: "华东",
      period: "2026-02",
      salesVolume: 950,
      salesAmount: 285,
      newCustomers: 3,
      totalCustomers: 103,
    },
    {
      productType: "板材",
      salesArea: "华北",
      period: "2026-02",
      salesVolume: 550,
      salesAmount: 165,
      newCustomers: 1,
      totalCustomers: 71,
    },
    {
      productType: "型材",
      salesArea: "华东",
      period: "2026-02",
      salesVolume: 450,
      salesAmount: 225,
      newCustomers: 2,
      totalCustomers: 52,
    },
    // 2026å¹´3月数据
    {
      productType: "砌块",
      salesArea: "华东",
      period: "2026-03",
      salesVolume: 1400,
      salesAmount: 210,
      newCustomers: 6,
      totalCustomers: 130,
    },
    {
      productType: "砌块",
      salesArea: "华北",
      period: "2026-03",
      salesVolume: 900,
      salesAmount: 135,
      newCustomers: 3,
      totalCustomers: 85,
    },
    {
      productType: "砌块",
      salesArea: "华南",
      period: "2026-03",
      salesVolume: 700,
      salesAmount: 105,
      newCustomers: 2,
      totalCustomers: 63,
    },
    {
      productType: "板材",
      salesArea: "华东",
      period: "2026-03",
      salesVolume: 1000,
      salesAmount: 300,
      newCustomers: 5,
      totalCustomers: 108,
    },
    {
      productType: "板材",
      salesArea: "华北",
      period: "2026-03",
      salesVolume: 600,
      salesAmount: 180,
      newCustomers: 2,
      totalCustomers: 73,
    },
    {
      productType: "型材",
      salesArea: "华东",
      period: "2026-03",
      salesVolume: 500,
      salesAmount: 250,
      newCustomers: 3,
      totalCustomers: 55,
    },
    // è¥¿å—和西北地区数据
    {
      productType: "砌块",
      salesArea: "西南",
      period: "2026-03",
      salesVolume: 500,
      salesAmount: 75,
      newCustomers: 2,
      totalCustomers: 40,
    },
    {
      productType: "板材",
      salesArea: "西南",
      period: "2026-03",
      salesVolume: 300,
      salesAmount: 90,
      newCustomers: 1,
      totalCustomers: 30,
    },
    {
      productType: "砌块",
      salesArea: "西北",
      period: "2026-03",
      salesVolume: 400,
      salesAmount: 60,
      newCustomers: 1,
      totalCustomers: 35,
    },
    {
      productType: "板材",
      salesArea: "西北",
      period: "2026-03",
      salesVolume: 200,
      salesAmount: 60,
      newCustomers: 1,
      totalCustomers: 25,
    },
  ];
  // è®¡ç®—属性
  const filteredData = computed(() => {
    let result = [...mockData];
    // æŒ‰äº§å“ç±»åž‹ç­›é€‰
    if (productType.value) {
      result = result.filter(item => {
        const typeMap = { block: "砌块", board: "板材", profile: "型材" };
        return item.productType === typeMap[productType.value];
      });
    }
    // æŒ‰é”€å”®åŒºåŸŸç­›é€‰
    if (salesArea.value) {
      result = result.filter(item => {
        const areaMap = {
          east: "华东",
          north: "华北",
          south: "华南",
          southwest: "西南",
          northwest: "西北",
        };
        return item.salesArea === areaMap[salesArea.value];
      });
    }
    // æŒ‰æ—¶é—´èŒƒå›´ç­›é€‰
    if (dateRange.value && dateRange.value.length === 2) {
      const startDate = dayjs(dateRange.value[0]);
      const endDate = dayjs(dateRange.value[1]);
      result = result.filter(item => {
        const itemDate = dayjs(item.period);
        return (
          itemDate.isAfter(startDate.subtract(1, "day")) &&
          itemDate.isBefore(endDate.add(1, "day"))
        );
      });
    }
    return result;
  });
  // æ ¸å¿ƒæŒ‡æ ‡è®¡ç®—
  const totalSalesVolume = computed(() => {
    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 newCustomerCount = computed(() => {
    return filteredData.value.reduce((sum, item) => sum + item.newCustomers, 0);
  });
  const totalCustomerCount = computed(() => {
    // è®¡ç®—每个区域和产品类型的最大客户数
    const customerMap = {};
    filteredData.value.forEach(item => {
      const key = `${item.productType}-${item.salesArea}`;
      if (!customerMap[key] || item.totalCustomers > customerMap[key]) {
        customerMap[key] = item.totalCustomers;
      }
    });
    return Object.values(customerMap).reduce((sum, count) => sum + count, 0);
  });
  // å˜åŒ–率计算(模拟)
  const salesVolumeChange = ref("+5.2");
  const salesAmountChange = ref("+7.8");
  const customerCountChange = ref("+3.5");
  const totalCustomerChange = ref("+2.1");
  // è¡¨æ ¼æ•°æ®
  const tableData = computed(() => {
    return filteredData.value.map(item => {
      // è®¡ç®—累计值(模拟)
      const cumulativeSalesVolume = item.salesVolume * 1.5;
      const cumulativeSalesAmount = item.salesAmount * 1.5;
      const cumulativeNewCustomers = item.newCustomers * 2;
      return {
        ...item,
        cumulativeSalesVolume,
        cumulativeSalesAmount,
        cumulativeNewCustomers,
      };
    });
  });
  // é”€é‡è¶‹åŠ¿å›¾è¡¨é…ç½®
  const salesVolumeChartOption = computed(() => {
    // æŒ‰å‘¨æœŸåˆ†ç»„
    const periodMap = {};
    filteredData.value.forEach(item => {
      if (!periodMap[item.period]) {
        periodMap[item.period] = 0;
      }
      periodMap[item.period] += item.salesVolume;
    });
    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,
          lineStyle: {
            width: 3,
          },
          itemStyle: {
            color: "#409EFF",
          },
        },
      ],
    };
  });
  // é”€å”®é‡‘额趋势图表配置
  const salesAmountChartOption = computed(() => {
    // æŒ‰å‘¨æœŸåˆ†ç»„
    const periodMap = {};
    filteredData.value.forEach(item => {
      if (!periodMap[item.period]) {
        periodMap[item.period] = 0;
      }
      periodMap[item.period] += item.salesAmount;
    });
    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: "#67C23A",
          },
        },
      ],
    };
  });
  // äº§å“ç±»åž‹åˆ†å¸ƒå›¾è¡¨é…ç½®
  const productTypeChartOption = computed(() => {
    // æŒ‰äº§å“ç±»åž‹åˆ†ç»„
    const typeMap = {};
    filteredData.value.forEach(item => {
      if (!typeMap[item.productType]) {
        typeMap[item.productType] = 0;
      }
      typeMap[item.productType] += item.salesVolume;
    });
    const types = Object.keys(typeMap);
    const values = types.map(type => typeMap[type]);
    return {
      tooltip: {
        trigger: "item",
        formatter: "{b}: {c} ç«‹æ–¹ç±³ ({d}%)",
      },
      series: [
        {
          type: "pie",
          radius: "60%",
          data: types.map((type, index) => ({
            name: type,
            value: values[index],
          })),
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: "rgba(0, 0, 0, 0.5)",
            },
          },
        },
      ],
    };
  });
  // é”€å”®åŒºåŸŸåˆ†å¸ƒå›¾è¡¨é…ç½®
  const salesAreaChartOption = computed(() => {
    // æŒ‰é”€å”®åŒºåŸŸåˆ†ç»„
    const areaMap = {};
    filteredData.value.forEach(item => {
      if (!areaMap[item.salesArea]) {
        areaMap[item.salesArea] = 0;
      }
      areaMap[item.salesArea] += item.salesVolume;
    });
    const areas = Object.keys(areaMap);
    const values = areas.map(area => areaMap[area]);
    return {
      tooltip: {
        trigger: "item",
        formatter: "{b}: {c} ç«‹æ–¹ç±³ ({d}%)",
      },
      series: [
        {
          type: "pie",
          radius: "60%",
          data: areas.map((area, index) => ({
            name: area,
            value: values[index],
          })),
          emphasis: {
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: "rgba(0, 0, 0, 0.5)",
            },
          },
        },
      ],
    };
  });
  // ç´¯è®¡é”€é‡è¶‹åŠ¿å›¾è¡¨é…ç½®
  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 initCharts = () => {
    // åˆå§‹åŒ–销量趋势图表
    if (salesVolumeChart.value && !salesVolumeChartInstance) {
      salesVolumeChartInstance = echarts.init(salesVolumeChart.value);
    }
    // åˆå§‹åŒ–销售金额趋势图表
    if (salesAmountChart.value && !salesAmountChartInstance) {
      salesAmountChartInstance = echarts.init(salesAmountChart.value);
    }
    // åˆå§‹åŒ–产品类型分布图表
    if (productTypeChart.value && !productTypeChartInstance) {
      productTypeChartInstance = echarts.init(productTypeChart.value);
    }
    // åˆå§‹åŒ–销售区域分布图表
    if (salesAreaChart.value && !salesAreaChartInstance) {
      salesAreaChartInstance = echarts.init(salesAreaChart.value);
    }
    // åˆå§‹åŒ–累计销量趋势图表
    if (cumulativeSalesVolumeChart.value && !cumulativeSalesVolumeChartInstance) {
      cumulativeSalesVolumeChartInstance = echarts.init(
        cumulativeSalesVolumeChart.value
      );
    }
    // åˆå§‹åŒ–累计销售金额趋势图表
    if (cumulativeSalesAmountChart.value && !cumulativeSalesAmountChartInstance) {
      cumulativeSalesAmountChartInstance = echarts.init(
        cumulativeSalesAmountChart.value
      );
    }
    updateCharts();
  };
  // æ›´æ–°å›¾è¡¨
  const updateCharts = () => {
    // æ›´æ–°é”€é‡è¶‹åŠ¿å›¾è¡¨
    if (salesVolumeChartInstance) {
      salesVolumeChartInstance.setOption(salesVolumeChartOption.value);
    }
    // æ›´æ–°é”€å”®é‡‘额趋势图表
    if (salesAmountChartInstance) {
      salesAmountChartInstance.setOption(salesAmountChartOption.value);
    }
    // æ›´æ–°äº§å“ç±»åž‹åˆ†å¸ƒå›¾è¡¨
    if (productTypeChartInstance) {
      productTypeChartInstance.setOption(productTypeChartOption.value);
    }
    // æ›´æ–°é”€å”®åŒºåŸŸåˆ†å¸ƒå›¾è¡¨
    if (salesAreaChartInstance) {
      salesAreaChartInstance.setOption(salesAreaChartOption.value);
    }
    // æ›´æ–°ç´¯è®¡é”€é‡è¶‹åŠ¿å›¾è¡¨
    if (cumulativeSalesVolumeChartInstance) {
      cumulativeSalesVolumeChartInstance.setOption(
        cumulativeSalesVolumeChartOption.value
      );
    }
    // æ›´æ–°ç´¯è®¡é”€å”®é‡‘额趋势图表
    if (cumulativeSalesAmountChartInstance) {
      cumulativeSalesAmountChartInstance.setOption(
        cumulativeSalesAmountChartOption.value
      );
    }
  };
  // ç›‘听窗口大小变化
  const handleResize = () => {
    if (salesVolumeChartInstance) {
      salesVolumeChartInstance.resize();
    }
    if (salesAmountChartInstance) {
      salesAmountChartInstance.resize();
    }
    if (productTypeChartInstance) {
      productTypeChartInstance.resize();
    }
    if (salesAreaChartInstance) {
      salesAreaChartInstance.resize();
    }
    if (cumulativeSalesVolumeChartInstance) {
      cumulativeSalesVolumeChartInstance.resize();
    }
    if (cumulativeSalesAmountChartInstance) {
      cumulativeSalesAmountChartInstance.resize();
    }
  };
  // ç”Ÿå‘½å‘¨æœŸ
  onMounted(() => {
    // è®¾ç½®é»˜è®¤æ—¥æœŸèŒƒå›´ä¸ºæœ€è¿‘3个月
    const endDate = dayjs();
    const startDate = endDate.subtract(3, "month");
    dateRange.value = [
      startDate.format("YYYY-MM-DD"),
      endDate.format("YYYY-MM-DD"),
    ];
    // ç­‰å¾…DOM更新后初始化图表
    nextTick(() => {
      initCharts();
    });
    // æ·»åŠ çª—å£å¤§å°å˜åŒ–ç›‘å¬
    window.addEventListener("resize", handleResize);
  });
  // èŽ·å–äº§å“ç±»åž‹æ ‡ç­¾ç±»åž‹
  const getProductTypeType = type => {
    const typeMap = {
      ç Œå—: "primary",
      æ¿æ: "success",
      åž‹æ: "warning",
    };
    return typeMap[type] || "info";
  };
  // èŽ·å–é”€å”®åŒºåŸŸæ ‡ç­¾ç±»åž‹
  const getSalesAreaType = area => {
    const typeMap = {
      åŽä¸œ: "primary",
      åŽåŒ—: "success",
      åŽå—: "warning",
      è¥¿å—: "danger",
      è¥¿åŒ—: "info",
    };
    return typeMap[area] || "info";
  };
  // ç»„件卸载时销毁图表实例
  onBeforeUnmount(() => {
    if (salesVolumeChartInstance) {
      salesVolumeChartInstance.dispose();
    }
    if (salesAmountChartInstance) {
      salesAmountChartInstance.dispose();
    }
    if (productTypeChartInstance) {
      productTypeChartInstance.dispose();
    }
    if (salesAreaChartInstance) {
      salesAreaChartInstance.dispose();
    }
    if (cumulativeSalesVolumeChartInstance) {
      cumulativeSalesVolumeChartInstance.dispose();
    }
    if (cumulativeSalesAmountChartInstance) {
      cumulativeSalesAmountChartInstance.dispose();
    }
    // ç§»é™¤çª—口大小变化监听
    window.removeEventListener("resize", handleResize);
  });
</script>
<style scoped>
  /* å¤–部容器 - å æ®æ•´ä¸ªè§†å£ */
  .sales-statistics-container {
    position: relative;
    width: 100%;
    /* é¡µé¢åœ¨å¸¸è§„布局下(有顶栏)默认减去 84px,避免内容被裁切 */
    min-height: calc(100vh - 84px);
    background-color: #f5f7fa;
    overflow: hidden;
  }
  /* å†…部内容区域 - è‡ªé€‚应宽度 */
  .data-dashboard {
    position: relative;
    width: 100%;
    min-height: 100%;
    background-color: #ffffff;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
  }
  .filter-area {
    padding: 20px;
    background-color: #ffffff;
    border-bottom: 1px solid #e4e7ed;
    display: flex;
    gap: 40px;
    align-items: center;
    flex-wrap: wrap;
  }
  .filter-section {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .filter-label {
    font-size: 14px;
    font-weight: 500;
    color: #303133;
    white-space: nowrap;
  }
  .dashboard-content {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding: 20px;
    min-height: 800px;
    overflow: hidden;
  }
  /* è¡Œå¸ƒå±€ */
  .row {
    display: flex;
    gap: 20px;
    align-items: stretch;
  }
  /* ç¬¬ä¸€è¡Œï¼š4个指标卡片 */
  .row-1 {
    height: 180px;
  }
  /* ç¬¬äºŒè¡Œï¼š2个趋势图表 */
  .row-2 {
    height: 350px;
  }
  /* ç¬¬ä¸‰è¡Œï¼šç´¯è®¡æ•°æ®è¶‹åŠ¿ */
  .row-3 {
    height: 350px;
  }
  /* ç¬¬å››è¡Œï¼šè¡¨æ ¼å’Œå›¾è¡¨ */
  .row-4 {
    height: 600px;
  }
  /* å¡ç‰‡æ ·å¼ */
  .panel-card {
    background-color: #ffffff;
    border-radius: 8px;
    border: 1px solid #e4e7ed;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    transition: all 0.3s ease;
  }
  .panel-card:hover {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
    transform: translateY(-2px);
  }
  /* å¡ç‰‡å¸ƒå±€ */
  .card-1 {
    flex: 1;
  }
  .card-2 {
    flex: 1;
  }
  .card-3 {
    flex: 1;
  }
  .card-4 {
    flex: 1;
  }
  .card-5 {
    flex: 1;
  }
  .card-6 {
    flex: 1;
  }
  .card-7 {
    flex: 1;
  }
  .card-8 {
    flex: 1;
  }
  .card-9 {
    flex: 1;
  }
  .card-10 {
    flex: 1;
  }
  .card-11 {
    flex: 1;
  }
  .panel-title {
    padding: 15px 20px;
    font-size: 16px;
    font-weight: 500;
    color: #303133;
    border-bottom: 1px solid #e4e7ed;
    background-color: #fafafa;
  }
  .card-1 .panel-title {
    border-left: 4px solid #409eff;
  }
  .card-2 .panel-title {
    border-left: 4px solid #67c23a;
  }
  .card-3 .panel-title {
    border-left: 4px solid #e6a23c;
  }
  .card-4 .panel-title {
    border-left: 4px solid #f56c6c;
  }
  .card-5 .panel-title {
    border-left: 4px solid #409eff;
  }
  .card-6 .panel-title {
    border-left: 4px solid #67c23a;
  }
  .card-7 .panel-title {
    border-left: 4px solid #e6a23c;
  }
  .card-8 .panel-title {
    border-left: 4px solid #f56c6c;
  }
  .card-9 .panel-title {
    border-left: 4px solid #409eff;
  }
  .card-10 .panel-title {
    border-left: 4px solid #67c23a;
  }
  .card-11 .panel-title {
    border-left: 4px solid #e6a23c;
  }
  .chart-container {
    flex: 1;
    padding: 20px;
  }
  .table-container {
    flex: 1;
    padding: 20px;
    overflow: auto;
  }
  .stats-grid {
    flex: 1;
    padding: 15px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .stat-item {
    background-color: #fafafa;
    border-radius: 8px;
    padding: 15px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    border: 1px solid #e4e7ed;
    min-height: 80px;
    width: 100%;
  }
  .stat-value {
    font-size: 24px;
    font-weight: 600;
    color: #303133;
    margin-bottom: 5px;
  }
  .sales-volume-color {
    color: #409eff;
    text-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
  }
  .sales-amount-color {
    color: #67c23a;
    text-shadow: 0 2px 4px rgba(103, 194, 58, 0.3);
  }
  .new-customer-color {
    color: #e6a23c;
    text-shadow: 0 2px 4px rgba(230, 162, 60, 0.3);
  }
  .total-customer-color {
    color: #f56c6c;
    text-shadow: 0 2px 4px rgba(245, 108, 108, 0.3);
  }
  .stat-unit {
    font-size: 12px;
    color: #909399;
    margin-bottom: 3px;
  }
  .stat-change {
    font-size: 12px;
    color: #67c23a;
  }
  /* è¡¨æ ¼æ ·å¼ */
  :deep(.el-table) {
    border-radius: 8px;
    overflow: hidden;
  }
  :deep(.el-table th) {
    background-color: #fafafa;
    font-weight: 500;
  }
  :deep(.el-table tr:hover > td) {
    background-color: #ecf5ff;
  }
  .data-value {
    font-weight: bold;
    color: #409eff;
  }
  /* ä¸‹æ‹‰é€‰æ‹©æ¡†æ ·å¼ */
  :deep(.el-select) {
    width: 100%;
  }
  :deep(.el-date-picker) {
    width: 100%;
  }
</style>
src/views/reportAnalysis/salesStatistics/index.vue
@@ -1,235 +1,169 @@
<template>
  <div class="sales-statistics-container">
    <div class="data-dashboard">
      <!-- é¡µé¢æ ‡é¢˜ -->
      <!-- <div class="dashboard-header">
        <div class="factory-name">销售统计看板</div>
      </div> -->
      <!-- ç­›é€‰æ¡ä»¶ -->
      <div class="filter-area">
        <div class="filter-section">
          <span class="filter-label">时间范围:</span>
          <el-date-picker v-model="dateRange"
                          type="daterange"
                          range-separator="至"
                          start-placeholder="开始日期"
                          end-placeholder="结束日期"
                          value-format="YYYY-MM-DD"
                          @change="handleDateChange"
                          style="width: 240px;" />
  <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="销售看板统计" />
      <div class="bi-topbar-content">
        <div class="bi-topbar-left">
          <span class="status-sun">☀</span>
          <span>26℃</span>
          <span class="bi-topbar-sep">湿度:1</span>
        </div>
        <div class="filter-section">
          <span class="filter-label">产品类型:</span>
          <el-select v-model="productType"
                     placeholder="请选择产品类型"
                     @change="handleFilterChange"
                     style="width: 160px;">
            <el-option label="全部"
                       value="" />
            <el-option label="砌块"
                       value="block" />
            <el-option label="板材"
                       value="board" />
            <el-option label="型材"
                       value="profile" />
          </el-select>
        <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>
        <div class="filter-section">
          <span class="filter-label">销售区域:</span>
          <el-select v-model="salesArea"
                     placeholder="请选择销售区域"
                     @change="handleFilterChange"
                     style="width: 160px;">
            <el-option label="全部"
                       value="" />
            <el-option label="华东"
                       value="east" />
            <el-option label="华北"
                       value="north" />
            <el-option label="华南"
                       value="south" />
            <el-option label="西南"
                       value="southwest" />
            <el-option label="西北"
                       value="northwest" />
          </el-select>
        </div>
        <div class="filter-section">
          <span class="filter-label">统计维度:</span>
          <el-select v-model="statDimension"
                     placeholder="请选择统计维度"
                     @change="handleFilterChange"
                     style="width: 160px;">
            <el-option label="月度"
                       value="month" />
            <el-option label="年度"
                       value="year" />
          </el-select>
        <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="dashboard-content">
        <!-- æ ¸å¿ƒæŒ‡æ ‡å¡ç‰‡ -->
        <div class="row row-1">
          <div class="panel-card card-1">
            <div class="panel-title">合计销量</div>
            <div class="stats-grid">
              <div class="stat-item">
                <div class="stat-value sales-volume-color">{{ totalSalesVolume }}</div>
                <div class="stat-unit">立方米</div>
                <div class="stat-change">{{ salesVolumeChange }}%</div>
    <div class="bi-dashboard-grid">
      <!-- å·¦ä¸Šï¼šé”€é‡è¶‹åŠ¿ -->
      <div class="bi-panel bi-panel-top-left">
        <PanelHeader title="销售分析-砌块" />
        <div class="panel-tabs">
          <span class="tab-item active">å¹´</span>
          <span class="tab-item">月</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>
      </div>
      <!-- å³ä¸Šï¼šé”€å”®é‡‘额 -->
      <div class="bi-panel bi-panel-top-right">
        <PanelHeader title="销售分析-板材" />
        <div class="panel-tabs">
          <span class="tab-item active">å¹´</span>
          <span class="tab-item">月</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="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-metric m1">
            <div class="center-metric-label">新增客户</div>
            <div class="center-metric-value">{{ centerNewCustomerCount }}</div>
            <div class="center-metric-unit">人</div>
          </div>
          <div class="center-metric m2">
            <div class="center-metric-label">成交总订单</div>
            <div class="center-metric-value">{{ completedOrders }}</div>
            <div class="center-metric-unit">单</div>
          </div>
          <div class="center-metric m3">
            <div class="center-metric-label">新增订单</div>
            <div class="center-metric-value">{{ salesOrderCount }}</div>
            <div class="center-metric-unit">单</div>
          </div>
          <div class="center-metric m4">
            <div class="center-metric-label">总销售区</div>
            <div class="center-metric-value">{{ totalSalesAreaCount }}</div>
            <div class="center-metric-unit">区</div>
              </div>
            </div>
          </div>
          <div class="panel-card card-2">
            <div class="panel-title">销售金额</div>
            <div class="stats-grid">
              <div class="stat-item">
                <div class="stat-value sales-amount-color">{{ totalSalesAmount }}</div>
                <div class="stat-unit">万元</div>
                <div class="stat-change">{{ salesAmountChange }}%</div>
      <!-- å·¦ä¸‹ï¼šäº§å“ç±»åž‹é”€é‡ -->
      <div class="bi-panel bi-panel-bottom-left">
        <PanelHeader title="客户销量排名分析-砌块" />
        <div class="panel-tabs">
          <span class="tab-item active">å¹´</span>
          <span class="tab-item">月</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 ref="productTypeChart" class="echart-fill"></div>
              </div>
            </div>
      <!-- ä¸­ä¸‹ï¼šæ–°å¢žå®¢æˆ·åˆ†æžï¼ˆåˆ†äº§å“ç±»åž‹è¶‹åŠ¿ï¼‰ -->
      <div class="bi-panel bi-panel-bottom-center">
        <PanelHeader title="新增客户趋势分析" />
        <div class="panel-tabs">
          <span class="tab-item active">å¹´</span>
          <span class="tab-item">月</span>
          </div>
          <div class="panel-card card-3">
            <div class="panel-title">新增客户</div>
            <div class="stats-grid">
              <div class="stat-item">
                <div class="stat-value new-customer-color">{{ newCustomerCount }}</div>
                <div class="stat-unit">个</div>
                <div class="stat-change">{{ customerCountChange }}%</div>
        <div class="bi-panel-body">
          <div class="chart-mini-title">
            <span class="diamond"></span>
            <span>新增客户数</span>
          </div>
          <div class="chart-unit-row chart-unit-single">
            <span>单位:人</span>
          </div>
          <div ref="productTypeTrendChart" class="echart-fill"></div>
              </div>
            </div>
      <!-- å³ä¸‹ï¼šé”€å”®åŒºåŸŸé”€é‡ -->
      <div class="bi-panel bi-panel-bottom-right">
        <PanelHeader title="客户销量排名分析-板材" />
        <div class="panel-tabs">
          <span class="tab-item active">å¹´</span>
          <span class="tab-item">月</span>
          </div>
          <div class="panel-card card-4">
            <div class="panel-title">合计客户</div>
            <div class="stats-grid">
              <div class="stat-item">
                <div class="stat-value total-customer-color">{{ totalCustomerCount }}</div>
                <div class="stat-unit">个</div>
                <div class="stat-change">{{ totalCustomerChange }}%</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>
          </div>
        </div>
        <!-- é”€é‡å’Œé”€å”®é‡‘额趋势 -->
        <div class="row row-2">
          <div class="panel-card card-5">
            <div class="panel-title">销量趋势</div>
            <div class="chart-container">
              <div ref="salesVolumeChart"
                   style="width: 100%; height: 100%;"></div>
            </div>
          </div>
          <div class="panel-card card-6">
            <div class="panel-title">销售金额趋势</div>
            <div class="chart-container">
              <div ref="salesAmountChart"
                   style="width: 100%; height: 100%;"></div>
            </div>
          </div>
        </div>
        <!-- ç´¯è®¡æ•°æ®è¶‹åŠ¿ -->
        <!-- <div class="row row-3">
          <div class="panel-card card-10">
            <div class="panel-title">累计销量趋势</div>
            <div class="chart-container">
              <div ref="cumulativeSalesVolumeChart"
                   style="width: 100%; height: 100%;"></div>
            </div>
          </div>
          <div class="panel-card card-11">
            <div class="panel-title">累计销售金额趋势</div>
            <div class="chart-container">
              <div ref="cumulativeSalesAmountChart"
                   style="width: 100%; height: 100%;"></div>
            </div>
          </div>
        </div> -->
        <!-- å›¾è¡¨åŒºåŸŸå’Œè¡¨æ ¼ -->
        <div class="row row-4">
          <!-- å·¦è¾¹ï¼šè¯¦ç»†æ•°æ®è¡¨æ ¼ -->
          <div class="panel-card card-9"
               style="flex: 2;">
            <div class="panel-title">销售统计详细数据</div>
            <div class="table-container">
              <el-table :data="tableData"
                        style="width: 100%">
                <el-table-column prop="productType"
                                 label="产品类型"
                                 width="120"
                                 align="center">
                  <template #default="scope">
                    <el-tag :type="getProductTypeType(scope.row.productType)">
                      {{ scope.row.productType }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="salesArea"
                                 label="销售区域"
                                 width="120"
                                 align="center">
                  <template #default="scope">
                    <el-tag :type="getSalesAreaType(scope.row.salesArea)">
                      {{ scope.row.salesArea }}
                    </el-tag>
                  </template>
                </el-table-column>
                <el-table-column prop="period"
                                 label="统计周期"
                                 width="120" />
                <el-table-column prop="salesVolume"
                                 label="销量(立方米)"
                                 align="right">
                  <template #default="scope">
                    <span class="data-value">{{ scope.row.salesVolume }}</span>
                  </template>
                </el-table-column>
                <el-table-column prop="salesAmount"
                                 label="销售金额(万元)"
                                 align="right">
                  <template #default="scope">
                    <span class="data-value">{{ scope.row.salesAmount }}</span>
                  </template>
                </el-table-column>
                <el-table-column prop="newCustomers"
                                 label="新增客户(个)"
                                 width="150"
                                 align="right">
                  <template #default="scope">
                    <span class="data-value">{{ scope.row.newCustomers }}</span>
                  </template>
                </el-table-column>
                <el-table-column prop="totalCustomers"
                                 label="合计客户(个)"
                                 width="150"
                                 align="right">
                  <template #default="scope">
                    <span class="data-value">{{ scope.row.totalCustomers }}</span>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </div>
          <!-- å³è¾¹ï¼šäº§å“ç±»åž‹åˆ†å¸ƒå’Œé”€å”®åŒºåŸŸåˆ†å¸ƒ -->
          <div class="chart-column"
               style="flex: 1; display: flex; flex-direction: column; gap: 20px;">
            <div class="panel-card card-7"
                 style="flex: 1;">
              <div class="panel-title">产品类型分布</div>
              <div class="chart-container">
                <div ref="productTypeChart"
                     style="width: 100%; height: 100%;"></div>
              </div>
            </div>
            <div class="panel-card card-8"
                 style="flex: 1;">
              <div class="panel-title">销售区域分布</div>
              <div class="chart-container">
                <div ref="salesAreaChart"
                     style="width: 100%; height: 100%;"></div>
              </div>
            </div>
          </div>
          <div ref="salesAreaChart" class="echart-fill"></div>
        </div>
      </div>
    </div>
@@ -248,8 +182,49 @@
  import { useRouter } from "vue-router";
  import * as echarts from "echarts";
  import dayjs from "dayjs";
  import PanelHeader from "@/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue";
  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 currentDateText = computed(() => {
    const weekMap = {
      0: "星期日",
      1: "星期一",
      2: "星期二",
      3: "星期三",
      4: "星期四",
      5: "星期五",
      6: "星期六",
    }
    return `${now.value.format("YYYY-MM-DD")} ${weekMap[now.value.day()] || ""}`
  })
  let timeTicker = null
  const handleFullscreenChange = () => {
    isFullscreen.value = !!document.fullscreenElement;
    nextTick(() => {
      handleResize();
    });
  };
  const toggleFullscreen = async () => {
    const rootEl = screenRoot.value;
    if (!rootEl) return;
    try {
      if (!document.fullscreenElement) {
        await rootEl.requestFullscreen();
      } else {
        await document.exitFullscreen();
      }
    } catch (error) {
      console.error("全屏切换失败:", error);
    }
  };
  // ç­›é€‰æ¡ä»¶
  const dateRange = ref([]);
@@ -262,6 +237,7 @@
  const salesAmountChart = ref(null);
  const productTypeChart = ref(null);
  const salesAreaChart = ref(null);
  const productTypeTrendChart = ref(null);
  const cumulativeSalesVolumeChart = ref(null);
  const cumulativeSalesAmountChart = ref(null);
@@ -270,6 +246,7 @@
  let salesAmountChartInstance = null;
  let productTypeChartInstance = null;
  let salesAreaChartInstance = null;
  let productTypeTrendChartInstance = null;
  let cumulativeSalesVolumeChartInstance = null;
  let cumulativeSalesAmountChartInstance = null;
@@ -549,6 +526,12 @@
    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 salesVolumeChange = ref("+5.2");
  const salesAmountChange = ref("+7.8");
@@ -574,41 +557,55 @@
  // é”€é‡è¶‹åŠ¿å›¾è¡¨é…ç½®
  const salesVolumeChartOption = computed(() => {
    // æŒ‰å‘¨æœŸåˆ†ç»„
    const periodMap = {};
    filteredData.value.forEach(item => {
      if (!periodMap[item.period]) {
        periodMap[item.period] = 0;
      }
      periodMap[item.period] += item.salesVolume;
    });
    const periods = Object.keys(periodMap).sort();
    const values = periods.map(period => periodMap[period]);
    const periods = ["6/9", "6/10", "6/11", "6/12", "6/12", "6/13"];
    const values = [132, 168, 168, 198, 168, 198];
    return {
      backgroundColor: "transparent",
      tooltip: {
        trigger: "axis",
        backgroundColor: "rgba(0,0,0,0.55)",
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: 1,
        textStyle: { color: "#B8C8E0" },
        formatter: "{b}: {c} ç«‹æ–¹ç±³",
      },
      grid: {
        left: "10%",
        right: "4%",
        bottom: "16%",
        top: "28%",
        containLabel: true,
      },
      xAxis: {
        type: "category",
        data: periods,
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisTick: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 10 },
        splitLine: { show: false },
      },
      yAxis: {
        type: "value",
        name: "销量(立方米)",
        name: "",
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series: [
        {
          data: values,
          type: "line",
          smooth: true,
          lineStyle: {
            width: 3,
          },
          itemStyle: {
            color: "#409EFF",
          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)" },
            ]),
          },
        },
      ],
@@ -617,118 +614,249 @@
  // é”€å”®é‡‘额趋势图表配置
  const salesAmountChartOption = computed(() => {
    // æŒ‰å‘¨æœŸåˆ†ç»„
    const periodMap = {};
    filteredData.value.forEach(item => {
      if (!periodMap[item.period]) {
        periodMap[item.period] = 0;
      }
      periodMap[item.period] += item.salesAmount;
    });
    const periods = Object.keys(periodMap).sort();
    const values = periods.map(period => periodMap[period]);
    const periods = ["6/9", "6/10", "6/11", "6/12", "6/12", "6/13"];
    const values = [132, 168, 168, 198, 168, 198];
    return {
      backgroundColor: "transparent",
      tooltip: {
        trigger: "axis",
        backgroundColor: "rgba(0,0,0,0.55)",
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: 1,
        textStyle: { color: "#B8C8E0" },
        formatter: "{b}: {c} ä¸‡å…ƒ",
      },
      grid: {
        left: "10%",
        right: "4%",
        bottom: "16%",
        top: "28%",
        containLabel: true,
      },
      xAxis: {
        type: "category",
        data: periods,
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisTick: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 10 },
      },
      yAxis: {
        type: "value",
        name: "销售金额(万元)",
        name: "",
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series: [
        {
          data: values,
          type: "bar",
          type: "line",
          smooth: true,
          symbolSize: 8,
          itemStyle: {
            color: "#67C23A",
            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)" },
            ]),
          },
        },
      ],
    };
  });
  // äº§å“ç±»åž‹åˆ†å¸ƒå›¾è¡¨é…ç½®
  // äº§å“ç±»åž‹é”€é‡å›¾è¡¨é…ç½®ï¼ˆæ¨ªå‘柱状)
  const productTypeChartOption = computed(() => {
    // æŒ‰äº§å“ç±»åž‹åˆ†ç»„
    const typeMap = {};
    filteredData.value.forEach(item => {
      if (!typeMap[item.productType]) {
        typeMap[item.productType] = 0;
      }
      typeMap[item.productType] += item.salesVolume;
    });
    const types = Object.keys(typeMap);
    const values = types.map(type => typeMap[type]);
    const types = ["客户BB", "客户AA", "客户CC", "客户DD", "客户DD", "客户DD"];
    const values = [130, 120, 102, 90, 90, 70];
    const barColors = ["#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447", "#C8C447", "#C8C447"];
    return {
      backgroundColor: "transparent",
      tooltip: {
        trigger: "item",
        formatter: "{b}: {c} ç«‹æ–¹ç±³ ({d}%)",
        trigger: "axis",
        axisPointer: { type: "shadow" },
        backgroundColor: "rgba(0,0,0,0.55)",
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: 1,
        textStyle: { color: "#B8C8E0" },
        formatter: "{b}: {c} ç«‹æ–¹ç±³",
      },
      grid: {
        left: "14%",
        right: "6%",
        top: "16%",
        bottom: "8%",
        containLabel: true,
      },
      xAxis: {
        type: "value",
        axisLine: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11 },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      yAxis: {
        type: "category",
        data: types,
        axisTick: { show: false },
        axisLine: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 },
      },
      series: [
        {
          type: "pie",
          radius: "60%",
          data: types.map((type, index) => ({
            name: type,
            value: values[index],
          })),
          emphasis: {
          name: "销量(立方米)",
          type: "bar",
          barWidth: 14,
          data: values,
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: "rgba(0, 0, 0, 0.5)",
            color: params => barColors[params.dataIndex] || "#00A4ED",
            borderRadius: [6, 6, 6, 6],
            },
          label: {
            show: false,
          },
        },
      ],
    };
  });
  // é”€å”®åŒºåŸŸåˆ†å¸ƒå›¾è¡¨é…ç½®
  // é”€å”®åŒºåŸŸé”€é‡å›¾è¡¨é…ç½®ï¼ˆæ¨ªå‘柱状)
  const salesAreaChartOption = computed(() => {
    // æŒ‰é”€å”®åŒºåŸŸåˆ†ç»„
    const areaMap = {};
    filteredData.value.forEach(item => {
      if (!areaMap[item.salesArea]) {
        areaMap[item.salesArea] = 0;
      }
      areaMap[item.salesArea] += item.salesVolume;
    });
    const areas = Object.keys(areaMap);
    const values = areas.map(area => areaMap[area]);
    const areas = ["客户BB", "客户AA", "客户CC", "客户DD", "客户DD", "客户DD"];
    const values = [130, 120, 102, 90, 90, 70];
    const barColors = ["#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447", "#C8C447", "#C8C447"];
    return {
      backgroundColor: "transparent",
      tooltip: {
        trigger: "item",
        formatter: "{b}: {c} ç«‹æ–¹ç±³ ({d}%)",
        trigger: "axis",
        axisPointer: { type: "shadow" },
        backgroundColor: "rgba(0,0,0,0.55)",
        borderColor: "rgba(64,158,255,0.25)",
        borderWidth: 1,
        textStyle: { color: "#B8C8E0" },
        formatter: "{b}: {c} ç«‹æ–¹ç±³",
      },
      grid: {
        left: "14%",
        right: "6%",
        top: "16%",
        bottom: "8%",
        containLabel: true,
      },
      xAxis: {
        type: "value",
        axisLine: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11 },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      yAxis: {
        type: "category",
        data: areas,
        axisTick: { show: false },
        axisLine: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 },
      },
      series: [
        {
          type: "pie",
          radius: "60%",
          data: areas.map((area, index) => ({
            name: area,
            value: values[index],
          })),
          emphasis: {
          name: "销量(立方米)",
          type: "bar",
          barWidth: 14,
          data: values,
            itemStyle: {
              shadowBlur: 10,
              shadowOffsetX: 0,
              shadowColor: "rgba(0, 0, 0, 0.5)",
            },
            color: params => barColors[params.dataIndex] || "#00A4ED",
            borderRadius: [6, 6, 6, 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 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)" },
        ]),
      },
    }));
    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" },
      },
      grid: {
        left: "10%",
        right: "6%",
        bottom: "14%",
        top: "26%",
        containLabel: true,
      },
      xAxis: {
        type: "category",
        data: periods,
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisTick: { show: false },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 10 },
      },
      yAxis: {
        type: "value",
        name: "",
        axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } },
        axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 },
        splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } },
      },
      series,
    };
  });
@@ -864,6 +992,11 @@
      salesAreaChartInstance = echarts.init(salesAreaChart.value);
    }
    // åˆå§‹åŒ–新增客户趋势图表
    if (productTypeTrendChart.value && !productTypeTrendChartInstance) {
      productTypeTrendChartInstance = echarts.init(productTypeTrendChart.value);
    }
    // åˆå§‹åŒ–累计销量趋势图表
    if (cumulativeSalesVolumeChart.value && !cumulativeSalesVolumeChartInstance) {
      cumulativeSalesVolumeChartInstance = echarts.init(
@@ -903,6 +1036,11 @@
      salesAreaChartInstance.setOption(salesAreaChartOption.value);
    }
    // æ›´æ–°æ–°å¢žå®¢æˆ·è¶‹åŠ¿å›¾è¡¨
    if (productTypeTrendChartInstance) {
      productTypeTrendChartInstance.setOption(productTypeTrendChartOption.value);
    }
    // æ›´æ–°ç´¯è®¡é”€é‡è¶‹åŠ¿å›¾è¡¨
    if (cumulativeSalesVolumeChartInstance) {
      cumulativeSalesVolumeChartInstance.setOption(
@@ -932,6 +1070,9 @@
    if (salesAreaChartInstance) {
      salesAreaChartInstance.resize();
    }
    if (productTypeTrendChartInstance) {
      productTypeTrendChartInstance.resize();
    }
    if (cumulativeSalesVolumeChartInstance) {
      cumulativeSalesVolumeChartInstance.resize();
    }
@@ -942,6 +1083,13 @@
  // ç”Ÿå‘½å‘¨æœŸ
  onMounted(() => {
    // å¯åŠ¨é¡¶éƒ¨æ æ—¶é—´åˆ·æ–°
    if (!timeTicker) {
      timeTicker = setInterval(() => {
        now.value = dayjs();
      }, 1000);
    }
    // è®¾ç½®é»˜è®¤æ—¥æœŸèŒƒå›´ä¸ºæœ€è¿‘3个月
    const endDate = dayjs();
    const startDate = endDate.subtract(3, "month");
@@ -957,6 +1105,7 @@
    // æ·»åŠ çª—å£å¤§å°å˜åŒ–ç›‘å¬
    window.addEventListener("resize", handleResize);
    document.addEventListener("fullscreenchange", handleFullscreenChange);
  });
  // èŽ·å–äº§å“ç±»åž‹æ ‡ç­¾ç±»åž‹
@@ -983,6 +1132,11 @@
  // ç»„件卸载时销毁图表实例
  onBeforeUnmount(() => {
    if (timeTicker) {
      clearInterval(timeTicker);
      timeTicker = null;
    }
    if (salesVolumeChartInstance) {
      salesVolumeChartInstance.dispose();
    }
@@ -995,6 +1149,10 @@
    if (salesAreaChartInstance) {
      salesAreaChartInstance.dispose();
    }
    if (productTypeTrendChartInstance) {
      productTypeTrendChartInstance.dispose();
    }
    if (cumulativeSalesVolumeChartInstance) {
      cumulativeSalesVolumeChartInstance.dispose();
    }
@@ -1004,301 +1162,497 @@
    // ç§»é™¤çª—口大小变化监听
    window.removeEventListener("resize", handleResize);
    document.removeEventListener("fullscreenchange", handleFullscreenChange);
  });
</script>
<style scoped>
  /* å¤–部容器 - å æ®æ•´ä¸ªè§†å£ */
  .sales-statistics-container {
    position: relative;
    width: 100%;
    /* é¡µé¢åœ¨å¸¸è§„布局下(有顶栏)默认减去 84px,避免内容被裁切 */
    min-height: calc(100vh - 84px);
    background-color: #f5f7fa;
    overflow: hidden;
    color: #B8C8E0;
    background: #041026;
  }
  /* å†…部内容区域 - è‡ªé€‚应宽度 */
  .data-dashboard {
  .sales-statistics-container.is-fullscreen {
    min-height: 100vh;
    height: 100vh;
  }
  /* æ·±è‰²èƒŒæ™¯å›¾ */
  .bi-bg {
    position: absolute;
    inset: 0;
    background-image: url("@/assets/BI/backImage@2x.png");
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    z-index: 0;
  }
  /* é¡¶éƒ¨æ ‡é¢˜æ  */
  .bi-topbar {
    position: relative;
    width: 100%;
    min-height: 100%;
    background-color: #ffffff;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
  }
  .filter-area {
    padding: 20px;
    background-color: #ffffff;
    border-bottom: 1px solid #e4e7ed;
    z-index: 2;
    height: 58px;
    display: flex;
    gap: 40px;
    align-items: center;
    flex-wrap: wrap;
    justify-content: center;
  }
  .filter-section {
  .bi-topbar-title-bg {
    position: absolute;
    top: 0;
    left: 0;
    height: 58px;
    width: 100%;
    object-fit: cover;
    z-index: 0;
    pointer-events: none;
  }
  .bi-topbar-content {
    position: relative;
    z-index: 1;
    width: 100%;
    padding: 0 28px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .bi-topbar-title {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    font-size: 26px;
    font-weight: 800;
    letter-spacing: 1px;
    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);
  }
  .bi-topbar-left {
    position: absolute;
    left: 10px;
    display: flex;
    align-items: center;
    gap: 8px;
    color: rgba(208, 231, 255, 0.85);
    font-size: 13px;
  }
  .status-sun {
    color: #ffd85e;
    text-shadow: 0 0 10px rgba(255, 216, 94, 0.8);
    font-size: 13px;
    line-height: 1;
  }
  .bi-topbar-meta {
    position: absolute;
    right: 52px;
    top: 16px;
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.5px;
    color: rgba(208, 231, 255, 0.85);
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .filter-label {
    font-size: 14px;
    font-weight: 500;
    color: #303133;
    white-space: nowrap;
  .fullscreen-btn {
    position: absolute;
    right: 10px;
    top: 12px;
    transform: none;
    border: 1px solid rgba(64, 158, 255, 0.45);
    background: rgba(0, 164, 237, 0.14);
    color: #d0e7ff;
    width: 34px;
    height: 34px;
    border-radius: 6px;
    padding: 0;
    cursor: pointer;
    transition: all 0.2s ease;
    z-index: 10;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .dashboard-content {
  .fullscreen-btn:hover {
    background: rgba(0, 164, 237, 0.24);
    box-shadow: 0 0 12px rgba(0, 164, 237, 0.3);
  }
  .bi-topbar-sep {
    opacity: 0.7;
  }
  /* ä¸»ä½“网格布局 */
  .bi-dashboard-grid {
    position: relative;
    z-index: 1;
    z-index: 2;
    height: calc(100vh - 84px - 58px);
    min-height: 450px;
    padding: 10px 18px 14px;
    display: grid;
    grid-template-columns: 1fr 1.05fr 1fr;
    grid-template-rows: 1fr 1fr;
    gap: 12px;
  }
  .sales-statistics-container.is-fullscreen .bi-dashboard-grid {
    height: calc(100vh - 58px);
  }
  .bi-panel {
    background: rgba(3, 18, 46, 0.62);
    border: 1px solid rgba(64, 158, 255, 0.35);
    border-radius: 4px;
    overflow: hidden;
    box-shadow: 0 0 22px rgba(0, 164, 237, 0.12);
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding: 20px;
    min-height: 800px;
    overflow: hidden;
    position: relative;
  }
  /* è¡Œå¸ƒå±€ */
  .row {
  .bi-panel-title {
    height: 44px;
    display: flex;
    gap: 20px;
    align-items: stretch;
    align-items: center;
    padding: 0 18px;
    font-size: 15px;
    font-weight: 700;
    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);
  }
  /* ç¬¬ä¸€è¡Œï¼š4个指标卡片 */
  .row-1 {
    height: 180px;
  }
  /* ç¬¬äºŒè¡Œï¼š2个趋势图表 */
  .row-2 {
    height: 350px;
  }
  /* ç¬¬ä¸‰è¡Œï¼šç´¯è®¡æ•°æ®è¶‹åŠ¿ */
  .row-3 {
    height: 350px;
  }
  /* ç¬¬å››è¡Œï¼šè¡¨æ ¼å’Œå›¾è¡¨ */
  .row-4 {
    height: 600px;
  }
  /* å¡ç‰‡æ ·å¼ */
  .panel-card {
    background-color: #ffffff;
    border-radius: 8px;
    border: 1px solid #e4e7ed;
    overflow: hidden;
  .panel-tabs {
    position: absolute;
    top: 8px;
    right: 12px;
    display: flex;
    flex-direction: column;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    transition: all 0.3s ease;
    gap: 6px;
    z-index: 4;
  }
  .panel-card:hover {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
    transform: translateY(-2px);
  .tab-item {
    font-size: 12px;
    color: rgba(184, 200, 224, 0.75);
    padding: 1px 5px;
    border: 1px solid rgba(64, 158, 255, 0.25);
    border-radius: 3px;
    line-height: 1.4;
  }
  /* å¡ç‰‡å¸ƒå±€ */
  .card-1 {
  .tab-item.active {
    color: #ffffff;
    border-color: rgba(0, 164, 237, 0.65);
    background: rgba(0, 164, 237, 0.22);
  }
  .bi-panel-body {
    flex: 1;
    padding: 8px 10px;
  }
  .card-2 {
    flex: 1;
  .echart-fill {
    width: 100%;
    height: 100%;
  }
  .card-3 {
    flex: 1;
  .chart-filter-tabs {
    display: flex;
    gap: 6px;
    margin: 0 0 5px 0;
  }
  .card-4 {
    flex: 1;
  .cf-tab {
    font-size: 11px;
    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;
    line-height: 1;
  }
  .card-5 {
    flex: 1;
  .cf-tab.active {
    color: #D9ECFF;
    background: rgba(0, 108, 208, 0.85);
    border-color: rgba(64, 158, 255, 0.65);
  }
  .card-6 {
    flex: 1;
  .chart-unit-row {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
    color: rgba(208, 231, 255, 0.88);
    margin-bottom: 4px;
    padding: 0 2px;
  }
  .card-7 {
    flex: 1;
  .dot-legend::before {
    content: "";
    display: inline-block;
    width: 8px;
    height: 8px;
    background: #65A0FF;
    margin-right: 6px;
  }
  .card-8 {
    flex: 1;
  .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;
  }
  .card-9 {
    flex: 1;
  .diamond {
    width: 10px;
    height: 10px;
    background: #1E8BFF;
    transform: rotate(45deg);
    display: inline-block;
  }
  .card-10 {
    flex: 1;
  .chart-unit-single {
    justify-content: flex-start;
    margin-bottom: 2px;
  }
  .card-11 {
    flex: 1;
  .bi-panel-top-left .echart-fill,
  .bi-panel-top-right .echart-fill {
    height: calc(100% - 44px);
  }
  .panel-title {
    padding: 15px 20px;
  .bi-panel-bottom-left .echart-fill,
  .bi-panel-bottom-right .echart-fill {
    height: calc(100% - 28px);
  }
  .bi-panel-bottom-center .echart-fill {
    height: calc(100% - 44px);
  }
  .bi-panel-top-left {
    grid-column: 1;
    grid-row: 1;
    position: relative;
  }
  .bi-panel-top-right {
    grid-column: 3;
    grid-row: 1;
    position: relative;
  }
  .bi-panel-bottom-left {
    grid-column: 1;
    grid-row: 2;
  }
  .bi-panel-bottom-center {
    grid-column: 2;
    grid-row: 2;
  }
  .bi-panel-bottom-right {
    grid-column: 3;
    grid-row: 2;
  }
  /* ä¸­å¿ƒçŽ¯æµ®å±‚ï¼ˆç»å¯¹å®šä½åœ¨ç½‘æ ¼ä¸Šæ–¹ï¼‰ */
  .center-ring {
    grid-column: 2;
    grid-row: 1 / span 2;
    position: absolute;
    left:25%;
    top: 25%;
    transform: translate(-50%, -50%);
    width: 400px;
    height: 275px;
    z-index: 3;
    pointer-events: none;
  }
  .center-ring-bg {
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: drop-shadow(0 0 20px rgba(0, 164, 237, 0.35));
  }
  .center-ring-content {
    position: absolute;
    inset: 0;
  }
  .center-ring-content::before,
  .center-ring-content::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 56%;
    width: 370px;
    height: 146px;
    transform: translate(-50%, -50%) rotate(-18deg);
    border: 2px solid rgba(40, 186, 255, 0.45);
    border-radius: 50%;
    filter: drop-shadow(0 0 8px rgba(0, 164, 237, 0.35));
    opacity: 0.7;
  }
  .center-ring-content::after {
    width: 360px;
    height: 150px;
    transform: translate(-50%, -50%) rotate(26deg);
    border-color: rgba(80, 220, 255, 0.35);
    opacity: 0.55;
  }
  .center-ring-title {
    position: absolute;
    top: 116px;
    left: 50%;
    transform: translateX(-50%);
    font-size: 50px;
    line-height: 1.06;
    text-align: center;
    font-weight: 900;
    color: #EAF6FF;
    text-shadow: 0 0 34px rgba(0, 164, 237, 0.6);
    z-index: 2;
  }
  .center-ring-title::before {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: 155px;
    height: 155px;
    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);
    border-radius: 50%;
    box-shadow: 0 0 20px rgba(0, 164, 237, 0.45), inset 0 0 26px rgba(0, 164, 237, 0.2);
    z-index: -1;
  }
  .center-metric {
    position: absolute;
    width: 155px;
    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;
  }
  .center-metric-label {
    font-size: 16px;
    font-weight: 500;
    color: #303133;
    border-bottom: 1px solid #e4e7ed;
    background-color: #fafafa;
    color: rgba(234, 246, 255, 0.9);
    margin-top: 4px;
  }
  .card-1 .panel-title {
    border-left: 4px solid #409eff;
  .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;
  }
  .card-2 .panel-title {
    border-left: 4px solid #67c23a;
  .center-metric-unit {
    margin-top: 2px;
    font-size: 14px;
    color: rgba(208, 231, 255, 0.85);
  }
  .card-3 .panel-title {
    border-left: 4px solid #e6a23c;
  .m1 {
    top: -6px;
    left: -26px;
    text-align: left;
  }
  .card-4 .panel-title {
    border-left: 4px solid #f56c6c;
  .m2 {
    top: -6px;
    right: -26px;
    text-align: right;
  }
  .card-5 .panel-title {
    border-left: 4px solid #409eff;
  .m3 {
    bottom: 66px;
    left: -30px;
    text-align: left;
  }
  .card-6 .panel-title {
    border-left: 4px solid #67c23a;
  .m4 {
    bottom: 66px;
    right: -30px;
    text-align: right;
  }
  .card-7 .panel-title {
    border-left: 4px solid #e6a23c;
  @media (max-width: 1100px) {
    .bi-topbar-content {
      padding: 0 14px;
  }
  .card-8 .panel-title {
    border-left: 4px solid #f56c6c;
    .center-ring {
      left: 45.2%;
      width: 330px;
      height: 245px;
      top: 24px;
  }
  .card-9 .panel-title {
    border-left: 4px solid #409eff;
    .center-ring-title {
      top: 122px;
      font-size: 26px;
  }
  .card-10 .panel-title {
    border-left: 4px solid #67c23a;
    .m1 {
      top: 52px;
      left: 42px;
  }
  .card-11 .panel-title {
    border-left: 4px solid #e6a23c;
    .m2 {
      top: 54px;
      right: 42px;
  }
  .chart-container {
    flex: 1;
    padding: 20px;
    .m3 {
      bottom: 62px;
      left: 48px;
  }
  .table-container {
    flex: 1;
    padding: 20px;
    overflow: auto;
    .m4 {
      bottom: 68px;
      right: 44px;
  }
  .stats-grid {
    flex: 1;
    padding: 15px;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .stat-item {
    background-color: #fafafa;
    border-radius: 8px;
    padding: 15px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    border: 1px solid #e4e7ed;
    min-height: 80px;
    width: 100%;
  }
  .stat-value {
    font-size: 24px;
    font-weight: 600;
    color: #303133;
    margin-bottom: 5px;
  }
  .sales-volume-color {
    color: #409eff;
    text-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
  }
  .sales-amount-color {
    color: #67c23a;
    text-shadow: 0 2px 4px rgba(103, 194, 58, 0.3);
  }
  .new-customer-color {
    color: #e6a23c;
    text-shadow: 0 2px 4px rgba(230, 162, 60, 0.3);
  }
  .total-customer-color {
    color: #f56c6c;
    text-shadow: 0 2px 4px rgba(245, 108, 108, 0.3);
  }
  .stat-unit {
    font-size: 12px;
    color: #909399;
    margin-bottom: 3px;
  }
  .stat-change {
    font-size: 12px;
    color: #67c23a;
  }
  /* è¡¨æ ¼æ ·å¼ */
  :deep(.el-table) {
    border-radius: 8px;
    overflow: hidden;
  }
  :deep(.el-table th) {
    background-color: #fafafa;
    font-weight: 500;
  }
  :deep(.el-table tr:hover > td) {
    background-color: #ecf5ff;
  }
  .data-value {
    font-weight: bold;
    color: #409eff;
  }
  /* ä¸‹æ‹‰é€‰æ‹©æ¡†æ ·å¼ */
  :deep(.el-select) {
    width: 100%;
  }
  :deep(.el-date-picker) {
    width: 100%;
  }
</style>