spring
7 小时以前 519211ac232866afe6b081ae4a97916ad5f1d7d2
src/views/salesManagement/indicatorStats/index.vue
@@ -1,99 +1,161 @@
<template>
  <div class="app-container indicator-stats">
    <el-card class="box-card">
      <!-- KPI 汇总 -->
      <el-row :gutter="20" class="stats-row">
        <el-col :span="8">
          <div class="stat-card">
            <div class="stat-icon" style="background: #ecf5ff;">
              <el-icon :size="30" color="#409eff"><Document /></el-icon>
            </div>
            <div class="stat-content">
              <div class="stat-value">{{ indicatorKpis.orderCount.toLocaleString() }}</div>
              <div class="stat-label">订单数量</div>
    <!-- KPI 汇总 -->
    <el-row :gutter="20" class="stats-row">
      <el-col :xs="24" :sm="12" :md="8">
        <div class="stat-card stat-card-blue">
          <div class="stat-icon-wrapper">
            <div class="stat-icon">
              <el-icon :size="32"><Document /></el-icon>
            </div>
          </div>
        </el-col>
        <el-col :span="8">
          <div class="stat-card">
            <div class="stat-icon" style="background: #f0f9ff;">
              <el-icon :size="30" color="#67c23a"><Tickets /></el-icon>
            </div>
            <div class="stat-content">
              <div class="stat-value">¥{{ indicatorKpis.salesAmount.toLocaleString() }}</div>
              <div class="stat-label">销售额</div>
          <div class="stat-content">
            <div class="stat-value">{{ indicatorKpis.orderCount.toLocaleString() }}</div>
            <div class="stat-label">订单数量</div>
          </div>
          <div class="stat-bg-decoration"></div>
        </div>
      </el-col>
      <el-col :xs="24" :sm="12" :md="8">
        <div class="stat-card stat-card-green">
          <div class="stat-icon-wrapper">
            <div class="stat-icon">
              <el-icon :size="32"><Tickets /></el-icon>
            </div>
          </div>
        </el-col>
        <el-col :span="8">
          <div class="stat-card">
            <div class="stat-icon" style="background: #fef0f0;">
              <el-icon :size="30" color="#e6a23c"><Van /></el-icon>
            </div>
            <div class="stat-content">
              <div class="stat-value">{{ indicatorKpis.shipmentRate }}%</div>
              <div class="stat-label">发货率</div>
          <div class="stat-content">
            <div class="stat-value">¥{{ indicatorKpis.salesAmount.toLocaleString() }}</div>
            <div class="stat-label">销售额</div>
          </div>
          <div class="stat-bg-decoration"></div>
        </div>
      </el-col>
      <el-col :xs="24" :sm="12" :md="8">
        <div class="stat-card stat-card-orange">
          <div class="stat-icon-wrapper">
            <div class="stat-icon">
              <el-icon :size="32"><Van /></el-icon>
            </div>
          </div>
        </el-col>
      </el-row>
          <div class="stat-content">
            <div class="stat-value">{{ indicatorKpis.shipRate }}%</div>
            <div class="stat-label">发货率</div>
          </div>
          <div class="stat-bg-decoration"></div>
        </div>
      </el-col>
    </el-row>
      <!-- 维度筛选 -->
      <el-row :gutter="20" class="search-row">
        <el-col :span="6">
          <el-select v-model="indicatorFilter.product" placeholder="产品" clearable>
            <el-option label="全部产品" value="" />
            <el-option label="P.O 42.5普通硅酸盐水泥" value="P.O 42.5普通硅酸盐水泥" />
            <el-option label="P.S 32.5矿渣硅酸盐水泥" value="P.S 32.5矿渣硅酸盐水泥" />
            <el-option label="P.C 32.5复合硅酸盐水泥" value="P.C 32.5复合硅酸盐水泥" />
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="indicatorFilter.customer" placeholder="客户" clearable>
            <el-option label="全部客户" value="" />
            <el-option label="华东建材集团" value="华东建材集团" />
            <el-option label="长江混凝土公司" value="长江混凝土公司" />
            <el-option label="浦江水泥制品厂" value="浦江水泥制品厂" />
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-select v-model="indicatorFilter.region" placeholder="区域" clearable>
            <el-option label="全部区域" value="" />
            <el-option label="华东地区" value="华东地区" />
            <el-option label="华南地区" value="华南地区" />
            <el-option label="华北地区" value="华北地区" />
          </el-select>
        </el-col>
        <el-col :span="6">
          <el-date-picker v-model="indicatorFilter.dateRange" type="daterange" range-separator="至"
                          start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" style="width: 100%" />
        </el-col>
        <el-col :span="24" style="text-align: right; margin-top: 10px;">
          <el-button type="primary" @click="applyIndicatorFilter">查询</el-button>
          <el-button @click="resetIndicatorFilter">重置</el-button>
          <el-button @click="exportIndicatorTable">导出报表</el-button>
          <el-button @click="exportIndicatorChart">导出图表</el-button>
        </el-col>
      </el-row>
      <!-- 图表区 -->
      <div class="chart-container">
        <div ref="indicatorChartRef" style="width: 100%; height: 360px;"></div>
    <!-- 图表区(包含筛选条件) -->
    <el-card class="chart-card" shadow="hover">
      <template #header>
        <div class="card-header">
          <div class="header-left">
            <span class="card-title">销售趋势分析</span>
            <span class="card-subtitle">筛选条件仅影响下方图表数据</span>
          </div>
        </div>
      </template>
      <!-- 图表筛选条件 -->
      <div class="chart-filter-section">
        <el-row :gutter="16" class="search-row">
          <el-col :xs="24" :sm="12" :md="6">
            <div class="filter-item">
              <label class="filter-label">产品类别</label>
              <el-tree-select
                v-model="indicatorFilter.productCategory"
                placeholder="请选择产品类别"
                clearable
                check-strictly
                :data="productOptions"
                :render-after-expand="false"
                style="width: 100%"
              />
            </div>
          </el-col>
          <el-col :xs="24" :sm="12" :md="6">
            <div class="filter-item">
              <label class="filter-label">客户</label>
              <el-select
                v-model="indicatorFilter.customerName"
                placeholder="请选择客户"
                clearable
                filterable
                style="width: 100%"
              >
                <el-option
                  v-for="item in customerOption"
                  :key="item.id"
                  :label="item.customerName"
                  :value="item.customerName"
                />
              </el-select>
            </div>
          </el-col>
          <el-col :xs="24" :sm="12" :md="6">
            <div class="filter-item">
              <label class="filter-label">日期范围</label>
              <el-date-picker
                v-model="indicatorFilter.dateRange"
                type="daterange"
                range-separator="至"
                start-placeholder="开始日期"
                end-placeholder="结束日期"
                value-format="YYYY-MM-DD"
                style="width: 100%"
              />
            </div>
          </el-col>
          <el-col :xs="24" :sm="12" :md="6">
            <div class="filter-item filter-buttons">
              <el-button type="primary" :loading="loading" @click="applyIndicatorFilter">
                <el-icon><Search /></el-icon>
                查询图表
              </el-button>
              <el-button @click="resetIndicatorFilter">
                <el-icon><Refresh /></el-icon>
                重置
              </el-button>
            </div>
          </el-col>
        </el-row>
      </div>
      <!-- 业绩统计(团队维度,无个人姓名) -->
      <el-table v-if="showTeamPerformance" :data="teamPerformanceList" border stripe style="margin-top: 20px;">
        <el-table-column prop="team" label="销售团队"/>
        <el-table-column prop="orderCount" label="订单数"/>
        <el-table-column prop="salesAmount" label="销售额">
      <!-- 图表展示区 -->
      <div class="chart-container" v-loading="loading">
        <div ref="indicatorChartRef" class="chart-wrapper"></div>
      </div>
    </el-card>
    <!-- 业绩统计(团队维度,无个人姓名) -->
    <el-card v-if="showTeamPerformance" class="table-card" shadow="hover">
      <template #header>
        <div class="card-header">
          <span class="card-title">团队业绩统计</span>
        </div>
      </template>
      <el-table
        :data="teamPerformanceList"
        border
        stripe
        style="width: 100%"
        :header-cell-style="{ background: '#f5f7fa', color: '#606266', fontWeight: 'bold' }"
      >
        <el-table-column prop="team" label="销售团队" min-width="120"/>
        <el-table-column prop="orderCount" label="订单数" align="right" min-width="100"/>
        <el-table-column prop="salesAmount" label="销售额" align="right" min-width="140">
          <template #default="scope">¥{{ scope.row.salesAmount.toLocaleString() }}</template>
        </el-table-column>
        <el-table-column prop="shipmentRate" label="发货率">
          <template #default="scope">{{ scope.row.shipmentRate }}%</template>
        <el-table-column prop="shipRate" label="发货率" align="right" min-width="100">
          <template #default="scope">{{ scope.row.shipRate }}%</template>
        </el-table-column>
        <el-table-column prop="attainment" label="目标达成率">
        <el-table-column prop="attainment" label="目标达成率" align="center" min-width="120">
          <template #default="scope">
            <el-tag :type="scope.row.attainment >= 100 ? 'success' : scope.row.attainment >= 80 ? 'warning' : 'danger'">
            <el-tag
              :type="scope.row.attainment >= 100 ? 'success' : scope.row.attainment >= 80 ? 'warning' : 'danger'"
              effect="dark"
            >
              {{ scope.row.attainment }}%
            </el-tag>
          </template>
@@ -104,35 +166,209 @@
</template>
<script setup>
import { ref, reactive, onMounted, nextTick } from 'vue'
import { Document, Van, Tickets } from '@element-plus/icons-vue'
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'
import { Document, Van, Tickets, Search, Refresh } from '@element-plus/icons-vue'
import * as echarts from 'echarts'
import { getTotalStatistics, getStatisticsTable } from '@/api/salesManagement/indicatorStats'
import { productTreeList } from '@/api/basicData/product.js'
import { customerList } from '@/api/salesManagement/salesLedger.js'
import { ElMessage } from 'element-plus'
const indicatorKpis = reactive({
  orderCount: 1280,
  salesAmount: 9650000,
  shipmentRate: 89.2
  orderCount: 0,
  salesAmount: 0,
  shipRate: 0
})
// 是否展示销售团队明细表,按需开启
const showTeamPerformance = ref(false)
const loading = ref(false)
const indicatorFilter = reactive({
  product: '',
  customer: '',
  region: '',
  productCategory: '',
  customerName: '',
  dateRange: []
})
const indicatorChartRef = ref(null)
let indicatorChart = null
const productOptions = ref([])
const customerOption = ref([])
const teamPerformanceList = ref([
  { team: '华东大区', orderCount: 320, salesAmount: 2850000, shipmentRate: 90, attainment: 105 },
  { team: '华北大区', orderCount: 280, salesAmount: 2150000, shipmentRate: 86, attainment: 92 },
  { team: '华南大区', orderCount: 210, salesAmount: 1850000, shipmentRate: 88, attainment: 78 },
  { team: '西南大区', orderCount: 180, salesAmount: 1500000, shipmentRate: 83, attainment: 74 }
  { team: '华东大区', orderCount: 320, salesAmount: 2850000, shipRate: 90, attainment: 105 },
  { team: '华北大区', orderCount: 280, salesAmount: 2150000, shipRate: 86, attainment: 92 },
  { team: '华南大区', orderCount: 210, salesAmount: 1850000, shipRate: 88, attainment: 78 },
  { team: '西南大区', orderCount: 180, salesAmount: 1500000, shipRate: 83, attainment: 74 }
])
// 转换产品树数据,将 id 改为 value
function convertIdToValue(data) {
  return data.map((item) => {
    const { id, children, ...rest } = item
    const newItem = {
      ...rest,
      value: id, // 将 id 改为 value
    }
    if (children && children.length > 0) {
      newItem.children = convertIdToValue(children)
    }
    return newItem
  })
}
// 获取产品树数据
const getProductOptions = () => {
  return productTreeList().then((res) => {
    productOptions.value = convertIdToValue(res)
  }).catch((error) => {
    console.error('获取产品树失败:', error)
    ElMessage.error('获取产品类别失败')
  })
}
// 获取客户列表
const getCustomerList = () => {
  return customerList().then((res) => {
    customerOption.value = res || []
  }).catch((error) => {
    console.error('获取客户列表失败:', error)
    ElMessage.error('获取客户列表失败')
  })
}
// 根据 id 查找产品类别名称
const findNodeLabelById = (nodes, id) => {
  if (!id) return null
  for (let i = 0; i < nodes.length; i++) {
    if (nodes[i].value === id) {
      return nodes[i].label
    }
    if (nodes[i].children && nodes[i].children.length > 0) {
      const found = findNodeLabelById(nodes[i].children, id)
      if (found) return found
    }
  }
  return null
}
// 获取头部统计数据
const fetchTotalStatistics = async () => {
  try {
    loading.value = true
    const params = {}
    if (indicatorFilter.customerName) {
      params.customerName = indicatorFilter.customerName
    }
    if (indicatorFilter.productCategory) {
      // 根据 id 查找产品类别名称
      const categoryName = findNodeLabelById(productOptions.value, indicatorFilter.productCategory)
      if (categoryName) {
        params.productCategory = categoryName
      }
    }
    if (indicatorFilter.dateRange && indicatorFilter.dateRange.length === 2) {
      params.entryDateStart = indicatorFilter.dateRange[0]
      params.entryDateEnd = indicatorFilter.dateRange[1]
    }
    const res = await getTotalStatistics(params)
    if (res && res.data) {
      indicatorKpis.orderCount = res.data.total || 0
      indicatorKpis.salesAmount = res.data.contractAmountTotal || 0
      // 发货率如果接口没有返回,保持原值或设为0
      // indicatorKpis.shipRate = res.data.shipRate || 0
    }
  } catch (error) {
    console.error('获取头部统计失败:', error)
    ElMessage.error('获取统计数据失败')
  } finally {
    loading.value = false
  }
}
// 获取柱状图数据
const fetchStatisticsTable = async () => {
  try {
    loading.value = true
    const params = {}
    if (indicatorFilter.customerName) {
      params.customerName = indicatorFilter.customerName
    }
    if (indicatorFilter.productCategory) {
      // 根据 id 查找产品类别名称
      const categoryName = findNodeLabelById(productOptions.value, indicatorFilter.productCategory)
      if (categoryName) {
        params.productCategory = categoryName
      }
    }
    if (indicatorFilter.dateRange && indicatorFilter.dateRange.length === 2) {
      params.entryDateStart = indicatorFilter.dateRange[0]
      params.entryDateEnd = indicatorFilter.dateRange[1]
    }
    const res = await getStatisticsTable(params)
    if (res && res.data) {
      updateChart(res.data)
    }
  } catch (error) {
    console.error('获取图表数据失败:', error)
    ElMessage.error('获取图表数据失败')
  } finally {
    loading.value = false
  }
}
// 更新图表
const updateChart = (chartData) => {
  if (!indicatorChartRef.value) return
  if (indicatorChart) indicatorChart.dispose()
  indicatorChart = echarts.init(indicatorChartRef.value)
  // 根据接口返回的数据结构更新图表
  // 接口返回: dateList, orderCountList, salesAmountList
  const option = {
    title: { text: '多维度销售指标趋势', left: 'center' },
    tooltip: { trigger: 'axis' },
    legend: { data: ['订单数', '销售额'], top: 30 },
    grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true },
    xAxis: {
      type: 'category',
      data: chartData.dateList || []
    },
    yAxis: [
      { type: 'value', name: '金额', position: 'left', axisLabel: { formatter: '{value}' } },
      {
        type: 'value',
        name: '数量',
        position: 'right',
        minInterval: 1,
        axisLabel: {
          formatter: (value) => {
            const intValue = Math.round(value)
            return intValue.toString()
          }
        }
      }
    ],
    series: [
      {
        name: '订单数',
        type: 'line',
        yAxisIndex: 1,
        data: chartData.orderCountList || [],
        itemStyle: { color: '#409eff' }
      },
      {
        name: '销售额',
        type: 'bar',
        yAxisIndex: 0,
        data: chartData.salesAmountList || [],
        itemStyle: { color: '#67c23a' }
      }
    ]
  }
  indicatorChart.setOption(option)
}
const initIndicatorChart = () => {
  if (!indicatorChartRef.value) return
@@ -141,91 +377,382 @@
  const option = {
    title: { text: '多维度销售指标趋势', left: 'center' },
    tooltip: { trigger: 'axis' },
    legend: { data: ['订单数', '销售额', '发货率'], top: 30 },
    grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true },
    xAxis: { type: 'category', data: ['2024-12', '2025-01', '2025-02', '2025-03', '2025-04', '2025-05'] },
    legend: { data: ['订单数', '销售额'], top: 30 },
    grid: { left: '3%', right: '8%', bottom: '3%', containLabel: true },
    xAxis: { type: 'category', data: [] },
    yAxis: [
      { type: 'value', name: '数量/金额', axisLabel: { formatter: '{value}' } },
      { type: 'value', name: '比例(%)', min: 0, max: 100, axisLabel: { formatter: '{value}%' } }
      { type: 'value', name: '金额', position: 'left', axisLabel: { formatter: '{value}' } },
      {
        type: 'value',
        name: '数量',
        position: 'right',
        minInterval: 1,
        axisLabel: {
          formatter: (value) => {
            const intValue = Math.round(value)
            return intValue.toString()
          }
        }
      }
    ],
    series: [
      { name: '订单数', type: 'bar', data: [180, 220, 210, 260, 205, 225], itemStyle: { color: '#409eff' } },
      { name: '销售额', type: 'bar', data: [820, 950, 910, 1080, 980, 1020], itemStyle: { color: '#67c23a' } },
      { name: '发货率', type: 'line', yAxisIndex: 1, data: [86, 89, 88, 91, 87, 90], itemStyle: { color: '#e6a23c' } }
      { name: '订单数', type: 'line', yAxisIndex: 1, data: [], itemStyle: { color: '#409eff' } },
      { name: '销售额', type: 'bar', yAxisIndex: 0, data: [], itemStyle: { color: '#67c23a' } }
    ]
  }
  indicatorChart.setOption(option)
}
const applyIndicatorFilter = () => {
  const random = (base, delta) => {
    const v = base + Math.round((Math.random() - 0.5) * delta)
    return v < 0 ? 0 : v
  }
  indicatorKpis.orderCount = random(1280, 120)
  indicatorKpis.salesAmount = random(9650000, 350000)
  indicatorKpis.shipmentRate = (85 + Math.random() * 10).toFixed(1) * 1
  setTimeout(() => initIndicatorChart(), 200)
const applyIndicatorFilter = async () => {
  // 筛选条件只影响图表数据,不影响KPI汇总
  await fetchStatisticsTable()
}
const resetIndicatorFilter = () => {
  indicatorFilter.product = ''
  indicatorFilter.customer = ''
  indicatorFilter.region = ''
  indicatorFilter.productCategory = ''
  indicatorFilter.customerName = ''
  indicatorFilter.dateRange = []
  applyIndicatorFilter()
}
const exportIndicatorTable = () => {
  const header = ['销售团队', '订单数', '销售额', '发货率(%)', '目标达成率(%)']
  const rows = teamPerformanceList.value.map(r => [
    r.team,
    r.orderCount,
    r.salesAmount,
    r.shipmentRate,
    r.attainment
  ])
  const csv = [header, ...rows].map(r => r.join(',')).join('\n')
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = '指标统计-团队业绩.csv'
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
  URL.revokeObjectURL(url)
}
const exportIndicatorChart = () => {
  if (!indicatorChart) return
  const url = indicatorChart.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: '#fff' })
  const link = document.createElement('a')
  link.href = url
  link.download = '指标统计-图表.png'
  document.body.appendChild(link)
  link.click()
  document.body.removeChild(link)
// 窗口大小变化时调整图表大小
const handleResize = () => {
  if (indicatorChart) {
    indicatorChart.resize()
  }
}
onMounted(() => {
  nextTick(() => initIndicatorChart())
  nextTick(() => {
    initIndicatorChart()
    getProductOptions()
    getCustomerList()
    fetchTotalStatistics()
    fetchStatisticsTable()
  })
  // 监听窗口大小变化
  window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
  // 移除窗口大小变化监听器
  window.removeEventListener('resize', handleResize)
  // 销毁图表实例
  if (indicatorChart) {
    indicatorChart.dispose()
    indicatorChart = null
  }
})
</script>
<style scoped>
<style scoped lang="scss">
.indicator-stats {
  padding: 0;
  padding: 20px;
  background: #f5f7fa;
  min-height: calc(100vh - 84px);
}
.box-card { border: none; box-shadow: none; }
.search-row { margin-bottom: 20px; }
.stats-row { margin-bottom: 24px; }
.stat-card { display: flex; align-items: center; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); }
.stat-icon { width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; border-radius: 8px; margin-right: 16px; }
.stat-content { flex: 1; }
.stat-value { font-size: 28px; font-weight: bold; color: #303133; margin-bottom: 4px; }
.stat-label { font-size: 14px; color: #909399; }
.chart-container { margin: 20px 0; padding: 20px; background: #fff; border-radius: 8px; box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); }
.page-header {
  margin-bottom: 24px;
  padding: 20px 0;
  .page-title {
    font-size: 24px;
    font-weight: 600;
    color: #303133;
    margin: 0 0 8px 0;
  }
  .page-desc {
    font-size: 14px;
    color: #909399;
    margin: 0;
  }
}
.stats-row {
  margin-bottom: 24px;
}
.stat-card {
  position: relative;
  display: flex;
  align-items: center;
  padding: 24px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.08);
  transition: all 0.3s ease;
  overflow: hidden;
  &:hover {
    transform: translateY(-4px);
    box-shadow: 0 8px 24px 0 rgba(0, 0, 0, 0.12);
  }
  .stat-icon-wrapper {
    margin-right: 20px;
    .stat-icon {
      width: 64px;
      height: 64px;
      display: flex;
      align-items: center;
      justify-content: center;
      border-radius: 12px;
      transition: all 0.3s ease;
    }
  }
  .stat-content {
    flex: 1;
    z-index: 1;
    .stat-value {
      font-size: 32px;
      font-weight: 700;
      color: #303133;
      margin-bottom: 8px;
      line-height: 1.2;
    }
    .stat-label {
      font-size: 14px;
      color: #909399;
      font-weight: 500;
    }
  }
  .stat-bg-decoration {
    position: absolute;
    right: -20px;
    top: -20px;
    width: 120px;
    height: 120px;
    border-radius: 50%;
    opacity: 0.1;
    z-index: 0;
  }
  &.stat-card-blue {
    .stat-icon {
      background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
      color: #fff;
    }
    .stat-bg-decoration {
      background: #409eff;
    }
  }
  &.stat-card-green {
    .stat-icon {
      background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
      color: #fff;
    }
    .stat-bg-decoration {
      background: #67c23a;
    }
  }
  &.stat-card-orange {
    .stat-icon {
      background: linear-gradient(135deg, #e6a23c 0%, #ebb563 100%);
      color: #fff;
    }
    .stat-bg-decoration {
      background: #e6a23c;
    }
  }
}
.chart-card,
.table-card {
  margin-bottom: 20px;
  border-radius: 12px;
  border: none;
  :deep(.el-card__header) {
    padding: 18px 20px;
    border-bottom: 1px solid #ebeef5;
    background: linear-gradient(135deg, #f5f7fa 0%, #ffffff 100%);
  }
  :deep(.el-card__body) {
    padding: 0;
  }
}
.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  .header-left {
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .card-title {
    font-size: 16px;
    font-weight: 600;
    color: #303133;
  }
  .card-subtitle {
    font-size: 12px;
    color: #909399;
    font-weight: normal;
  }
}
.chart-filter-section {
  padding: 20px;
  background: #fafbfc;
  border-bottom: 1px solid #ebeef5;
  margin-bottom: 0;
}
.search-row {
  .filter-item {
    margin-bottom: 0;
    .filter-label {
      display: block;
      font-size: 13px;
      color: #606266;
      margin-bottom: 8px;
      font-weight: 500;
    }
    &.filter-buttons {
      display: flex;
      align-items: flex-end;
      gap: 10px;
      padding-top: 28px;
      .el-button {
        flex: 1;
        font-size: 14px;
      }
    }
  }
}
.chart-container {
  width: 100%;
  overflow: hidden;
  position: relative;
  padding: 20px;
  background: #fff;
  .chart-wrapper {
    width: 100%;
    height: 420px;
    min-width: 0;
  }
}
.table-card {
  :deep(.el-table) {
    border-radius: 8px;
    overflow: hidden;
  }
  :deep(.el-table__header-wrapper) {
    .el-table__header {
      th {
        background: #f5f7fa;
        color: #606266;
        font-weight: 600;
      }
    }
  }
  :deep(.el-table__body-wrapper) {
    .el-table__body {
      tr:hover {
        background-color: #f5f7fa;
      }
    }
  }
}
// 响应式设计
@media (max-width: 768px) {
  .indicator-stats {
    padding: 12px;
  }
  .stat-card {
    padding: 20px;
    .stat-content .stat-value {
      font-size: 24px;
    }
    .stat-icon-wrapper .stat-icon {
      width: 56px;
      height: 56px;
    }
  }
  .chart-filter-section {
    padding: 16px;
  }
  .search-row {
    .filter-item.filter-buttons {
      padding-top: 0;
      margin-top: 12px;
    }
  }
  .chart-container {
    padding: 16px;
    .chart-wrapper {
      height: 320px;
    }
  }
  .card-header {
    .header-left {
      .card-title {
        font-size: 15px;
      }
      .card-subtitle {
        font-size: 11px;
      }
    }
  }
}
@media (max-width: 576px) {
  .page-header {
    .page-title {
      font-size: 20px;
    }
    .page-desc {
      font-size: 12px;
    }
  }
  .stat-card {
    flex-direction: column;
    text-align: center;
    .stat-icon-wrapper {
      margin-right: 0;
      margin-bottom: 12px;
    }
  }
}
</style>