<template>
  <div class="weather-app" ref="appRoot" v-loading="loading">
    <!-- ==================== 顶部 ==================== -->
    <header class="app-header">
      <div class="header-inner">
        <div class="header-left">
          <div class="cal-icon today-icon">
            <div class="cal-month">{{ anchorMonth }}月</div>
            <div class="cal-day">{{ padDay }}</div>
          </div>
          <div class="header-titles">
            <h1 class="page-title"><span class="title-sep"></span>历年今日 · 气象分析</h1>
            <div class="header-meta">
              <span class="meta-chip"
                ><el-icon><Location /></el-icon>{{ currentAreaLabel }}</span
              >
              <span class="meta-divider"></span>
              <span class="meta-chip"
                ><el-icon><Calendar /></el-icon>每年 <b>{{ anchorMonth }}月{{ padDay }}日</b> 这一天</span
              >
              <span class="meta-divider"></span>
              <span class="meta-chip"
                ><el-icon><Calendar /></el-icon>{{ computedStartYear }}–{{ computedEndYear }}（{{ computedSpan }}年）</span
              >
              <template v-if="hasActiveFilter">
                <span class="meta-divider"></span>
                <span class="meta-chip filter-hint"
                  ><el-icon><Filter /></el-icon>已筛选 {{ filteredRows.length }}/{{ pageState.weatherData.length }}</span
                >
              </template>
            </div>
          </div>
        </div>
        <div class="header-right">
          <el-dropdown trigger="click" @command="doExport" :disabled="exporting">
            <el-button size="small" :icon="Download" :loading="exporting"
              >{{ exporting ? "导出中…" : "导出" }}<el-icon class="el-icon--right"><ArrowDown /></el-icon
            ></el-button>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item command="png">保存为图片 (PNG)</el-dropdown-item>
                <el-dropdown-item command="pdf">导出为 PDF</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </div>
      </div>
    </header>

    <div class="app-body">
      <!-- ==================== 历年今日 Hero ==================== -->
      <section class="today-hero">
        <div class="hero-badge-row">
          <span class="hero-date-badge">每年的 {{ anchorMonth }}月{{ padDay }}日</span>
          <span class="hero-span-badge">{{ computedStartYear }}–{{ computedEndYear }}，共 {{ computedSpan }} 年</span>
        </div>
        <h2 class="hero-headline">
          历年「<b class="hero-date-em">{{ anchorMonth }}月{{ padDay }}日</b>」情况
        </h2>
        <p class="hero-desc">
          {{ currentAreaLabel }} 过去 <b>{{ computedSpan }}</b> 年间，每年此日的气象实况，共 <b>{{ pageState.weatherData.length }}</b> 条单日数据
          <template v-if="pageState.dataFromYear != null">（{{ pageState.dataFromYear }}-{{ pageState.dataToYear }}）</template>。
          其中 <b>{{ pageState.stats.rainyCount }}</b> 年当天出现降水（占比 {{ pageState.stats.rainyPct }}%），最大单日降雨
          <b>{{ pageState.stats.maxRain }}</b> mm（{{ pageState.stats.maxRainYear }}年），历史单日最高温 <b class="hot">{{ pageState.stats.maxTmax }}℃</b>（{{ pageState.stats.maxTmaxYear }}年），
          历史单日最低温 <b class="cold">{{ pageState.stats.minTmin }}℃</b>（{{ pageState.stats.minTminYear }}年）。
        </p>
        <div class="hero-stats">
          <div class="hero-stat-group" v-for="(g, gi) in pageState.statCards" :key="gi">
            <div class="hero-stat" v-for="c in g.items" :key="c.label">
              <div class="hs-label">{{ c.label }}</div>
              <div class="hs-val" :style="{ color: c.color }">
                {{ c.value }}<span class="hs-unit">{{ c.unit }}</span>
              </div>
            </div>
          </div>
        </div>
      </section>

      <!-- ==================== 气象站 + 跨度 ==================== -->
      <section class="control-bar">
        <div class="cb-group">
          <span class="cb-label"
            ><el-icon><OfficeBuilding /></el-icon> 气象站</span
          >
          <div class="chip-group">
            <span
              v-for="s in stations"
              :key="s.stationCode"
              class="chip"
              :class="{ active: selectedStation === s.stationCode }"
              @click="selectStation(s.stationCode)"
              >{{ s.stationName }}</span
            >
          </div>
        </div>
        <div class="cb-sep"></div>
        <div class="cb-group">
          <span class="cb-label"
            ><el-icon><Calendar /></el-icon> 年份跨度</span
          >
          <div class="chip-group">
            <span
              v-for="p in YEAR_PRESETS"
              :key="p"
              class="chip"
              :class="{ active: yearSpan === p }"
              @click="setYearSpan(p)"
              >{{ p }}年</span
            >
          </div>
          <el-input-number
            v-model="yearSpanInput"
            :min="1"
            :max="MAX_YEAR_SPAN"
            size="small"
            style="width: 10ch"
            controls-position="right"
          />
          <el-button size="small" plain @click="applyManualSpan">应用</el-button>
        </div>
      </section>

      <!-- ==================== 条件筛选 ==================== -->
      <section class="filter-panel">
        <div class="filter-header" @click="filterOpen = !filterOpen">
          <div class="fh-left">
            <el-icon><Operation /></el-icon><span>条件筛选</span>
            <el-badge v-if="activeFilterCount > 0" :value="activeFilterCount" type="warning" />
          </div>
          <el-icon :class="['fh-arrow', { open: filterOpen }]"><ArrowDown /></el-icon>
        </div>
        <el-collapse-transition>
          <div v-show="filterOpen" class="filter-body">
            <div class="filter-grid">
              <div class="filter-group">
                <div class="fg-title"><span class="fg-dot rain"></span> 降雨条件</div>
                <div class="fg-row">
                  <span class="fg-label">降雨量</span>
                  <el-select
                    v-model="filters.rainfall.op"
                    size="small"
                    placeholder="不限"
                    style="width: 8ch"
                  >
                    <el-option label="不限" value="" />
                    <el-option label="≥" value="gte" />
                    <el-option label="≤" value="lte" />
                    <el-option label="区间" value="range" />
                  </el-select>
                  <template v-if="filters.rainfall.op && filters.rainfall.op !== 'range'">
                    <el-input-number
                      v-model="filters.rainfall.val"
                      :min="0"
                      :max="999"
                      :step="5"
                      size="small"
                      style="width: 16ch"
                    /><span class="fg-unit">mm</span>
                  </template>
                  <template v-if="filters.rainfall.op === 'range'">
                    <el-input-number
                      v-model="filters.rainfall.min"
                      :min="0"
                      :max="999"
                      size="small"
                      style="width: 14ch"
                    />
                    <span class="fg-sep">~</span>
                    <el-input-number
                      v-model="filters.rainfall.max"
                      :min="0"
                      :max="999"
                      size="small"
                      style="width: 14ch"
                    /><span class="fg-unit">mm</span>
                  </template>
                </div>
                <div class="fg-row">
                  <span class="fg-label">雨量级别</span>
                  <div class="level-chips">
                    <span
                      v-for="lv in RAIN_FILTER_LEVELS"
                      :key="lv.label"
                      class="level-chip"
                      :class="{ active: filters.rainLevel.includes(lv.label) }"
                      @click="toggleRainLevel(lv)"
                      >{{ lv.label }}</span
                    >
                  </div>
                </div>
              </div>
              <div class="filter-group">
                <div class="fg-title"><span class="fg-dot temp"></span> 气温条件</div>
                <div class="fg-row" v-for="tf in TEMP_FILTER_FIELDS" :key="tf.key">
                  <span class="fg-label">{{ tf.label }}</span>
                  <el-select
                    v-model="filters[tf.key].op"
                    size="small"
                    placeholder="不限"
                    style="width: 8ch"
                  >
                    <el-option label="不限" value="" />
                    <el-option label="≥" value="gte" />
                    <el-option label="≤" value="lte" />
                    <el-option v-if="tf.range" label="区间" value="range" />
                  </el-select>
                  <template v-if="filters[tf.key].op && filters[tf.key].op !== 'range'">
                    <el-input-number
                      v-model="filters[tf.key].val"
                      :min="-50"
                      :max="60"
                      :step="0.5"
                      size="small"
                      style="width: 16ch"
                    /><span class="fg-unit">℃</span>
                  </template>
                  <template v-if="filters[tf.key].op === 'range'">
                    <el-input-number
                      v-model="filters[tf.key].min"
                      :min="-50"
                      :max="60"
                      size="small"
                      style="width: 14ch"
                    />
                    <span class="fg-sep">~</span>
                    <el-input-number
                      v-model="filters[tf.key].max"
                      :min="-50"
                      :max="60"
                      size="small"
                      style="width: 14ch"
                    /><span class="fg-unit">℃</span>
                  </template>
                </div>
              </div>
              <div class="filter-group">
                <div class="fg-title"><span class="fg-dot year-dot"></span> 年代筛选</div>
                <div class="fg-row">
                  <span class="fg-label">年代</span>
                  <div class="level-chips">
                    <span
                      v-for="d in availableDecades"
                      :key="d"
                      class="level-chip"
                      :class="{ active: filters.decades.includes(d) }"
                      @click="toggleDecade(d)"
                      >{{ d }}s</span
                    >
                    <span class="level-chip" @click="filters.decades = []">全部</span>
                  </div>
                </div>
              </div>
            </div>
            <div class="filter-actions">
              <div class="fa-result" v-if="hasActiveFilter">
                <el-icon><InfoFilled /></el-icon> 命中 <b>{{ filteredRows.length }}</b
                >/{{ pageState.weatherData.length }} 条<span
                  v-if="filteredRows.length === 0"
                  class="no-hit"
                  >— 无匹配</span
                >
              </div>
              <div class="fa-btns">
                <el-button size="small" @click="resetFilters"
                  ><el-icon><RefreshRight /></el-icon> 重置</el-button
                >
                <el-button size="small" type="primary" @click="filterOpen = false"
                  ><el-icon><ArrowUp /></el-icon> 收起</el-button
                >
              </div>
            </div>
          </div>
        </el-collapse-transition>
      </section>

      <!-- ==================== 主体 ==================== -->
      <el-empty v-if="!loading && pageState.weatherData.length === 0" description="暂无历史气象数据" :image-size="120" style="margin-top: 60px" />
      <div class="main-grid" v-else>
        <div class="col-main">
          <el-card class="mod-card" shadow="never">
            <template #header>
              <div class="card-hd">
                <span class="card-hd-title"
                  ><el-icon><DataAnalysis /></el-icon> 历年每个「{{ anchorMonth }}月{{ padDay }}日」单日气温与降雨</span
                >
                <span class="card-hd-unit">（降雨 mm / 气温 ℃）</span>
              </div>
            </template>
            <div ref="chartRef" class="chart-box"></div>
          </el-card>

          <el-card class="mod-card table-card" shadow="never">
            <template #header>
              <div class="table-hd">
                <div class="table-hd-left">
                  <el-icon><Tickets /></el-icon>
                  <span
                    >历年 {{ anchorMonth }}月{{ padDay }}日 单日实况
                    <template v-if="!tableExpanded">（预览前 {{ PREVIEW_COUNT }} 条）</template>
                    <template v-else>（全部 {{ displayRows.length }} 条）</template>
                  </span>
                  <el-tag
                    v-if="hasActiveFilter"
                    type="warning"
                    size="small"
                    effect="plain"
                    style="margin-left: 8px"
                    >筛选中</el-tag
                  >
                </div>
                <div class="table-hd-right">
                  <el-switch
                    v-if="hasActiveFilter"
                    v-model="showOnlyFiltered"
                    active-text="仅命中"
                    inactive-text="全部"
                    size="small"
                    style="margin-right: 12px"
                  />
                  <el-button
                    :type="tableExpanded ? 'primary' : 'default'"
                    size="small"
                    plain
                    @click="tableExpanded = !tableExpanded"
                  >
                    {{ tableExpanded ? "收起表格" : "展开全部" }}
                    <el-icon style="margin-left: 4px"
                      ><component :is="tableExpanded ? ArrowUp : ArrowDown" /></el-icon
                  ></el-button>
                </div>
              </div>
              <div v-if="hasActiveFilter" class="filter-stats-bar">
                <el-icon><DataLine /></el-icon>
                命中统计 — 平均降雨: <b class="blue">{{ fStats.avgRain }} mm</b> · 平均最高温:
                <b class="red">{{ fStats.avgTmax }}℃</b> · 最大降雨:
                <b>{{ fStats.maxRain }} mm</b>（{{ fStats.maxRainYear }}年） · 最高温极值:
                <b>{{ fStats.peakTmax }}℃</b>（{{ fStats.peakTmaxYear }}年）
              </div>
            </template>
            <el-table
              :data="visibleRows"
              border
              stripe
              :row-class-name="rowClassName"
              :default-sort="{ prop: 'year', order: 'descending' }"
              :cell-style="{ padding: '10px 0' }"
            >
              <el-table-column prop="year" label="年份" align="center" min-width="80" sortable>
                <template #default="{ row }"
                  ><span class="cell-year">{{ row.year }}</span></template
                >
              </el-table-column>
              <el-table-column label="当天日期" align="center" min-width="110">
                <template #default="{ row }"
                  ><span class="cell-date"
                    >{{ row.year }}-{{ padNum(anchorMonth) }}-{{ padDay }}</span
                  ></template
                >
              </el-table-column>
              <el-table-column prop="rainfall" label="降雨量(mm)" align="center" min-width="110" sortable>
                <template #default="{ row }">
                  <span :class="[rainValClass(row.rainfall), { extreme_cell: row._isMaxRain }]">
                    {{ fmtVal(row.rainfall) }}
                    <span v-if="row._isMaxRain" class="extreme-badge rain-badge">MAX</span>
                  </span>
                </template>
              </el-table-column>
              <el-table-column label="雨量等级" align="center" min-width="80">
                <template #default="{ row }">
                  <span>{{ rainLevelLabel(row.rainfall) }}</span>
                </template>
              </el-table-column>
              <el-table-column prop="tmax" label="最高温度(℃)" align="center" min-width="100" sortable>
                <template #default="{ row }">
                  <span :class="[tmaxValClass(row.tmax), { extreme_cell: row._isMaxTmax }]">
                    {{ fmtVal(row.tmax) }}
                    <span v-if="row._isMaxTmax" class="extreme-badge hot-badge">MAX</span>
                  </span>
                </template>
              </el-table-column>
              <el-table-column prop="tavg" label="平均温度(℃)" align="center" min-width="100" sortable>
                <template #default="{ row }">
                  <span>{{ fmtVal(row.tavg) }}</span>
                </template>
              </el-table-column>
              <el-table-column prop="tmin" label="最低温度(℃)" align="center" min-width="100" sortable>
                <template #default="{ row }">
                  <span :class="[tminValClass(row.tmin), { extreme_cell: row._isMinTmin }]">
                    {{ fmtVal(row.tmin) }}
                    <span v-if="row._isMinTmin" class="extreme-badge cold-badge">MIN</span>
                  </span>
                </template>
              </el-table-column>
              <el-table-column prop="windSpeed" label="平均风速(m/s)" align="center" min-width="110" sortable>
                <template #default="{ row }">
                  <span>{{ fmtVal(row.windSpeed) }}</span>
                </template>
              </el-table-column>
              <el-table-column label="平均风向" align="center" min-width="90">
                <template #default="{ row }">{{ windDirectionLabelLocal(row.windDirection) }}</template>
              </el-table-column>
              <el-table-column v-if="hasActiveFilter" label="匹配" align="center" min-width="65">
                <template #default="{ row }"
                  ><el-tag
                    :type="row._matched ? 'success' : 'info'"
                    size="small"
                    effect="dark"
                    >{{ row._matched ? "✓" : "—" }}</el-tag
                  ></template
                >
              </el-table-column>
            </el-table>
            <div
              v-if="!tableExpanded && displayRows.length > PREVIEW_COUNT"
              class="table-fade-hint"
            >
              <el-button type="primary" link @click="tableExpanded = true"
                >还有 {{ displayRows.length - PREVIEW_COUNT }} 条，点击展开全部
                <el-icon><ArrowDown /></el-icon
              ></el-button>
            </div>
          </el-card>
        </div>
        <div class="col-side">
          <el-card class="mod-card summary-card" shadow="never">
            <template #header
              ><span
                ><el-icon><Document /></el-icon> 摘要总结</span
              ></template
            >
            <div class="summary-text">{{ pageState.summary }}</div>
            <div v-if="hasActiveFilter && filteredRows.length > 0" class="filter-summary-box">
              <p>{{ filterSummaryText }}</p>
            </div>
            <div v-else-if="hasActiveFilter" class="filter-summary-box">
              <p>筛选条件无匹配年份，请调整筛选范围</p>
            </div>
          </el-card>
          <el-card class="mod-card rain-prob-card" shadow="never">
            <template #header>
              <span><el-icon><DataAnalysis /></el-icon> 雨量等级概率</span>
            </template>
            <div class="rp-body">
              <div class="rp-row" v-for="item in rainLevelDistribution" :key="item.label">
                <span class="rp-label">{{ item.label }}</span>
                <div class="rp-bar-track">
                  <div class="rp-bar-fill" :style="{ width: item.pct + '%' }"></div>
                </div>
                <span class="rp-pct">{{ item.pct }}%</span>
                <span class="rp-count">({{ item.count }})</span>
              </div>
            </div>
          </el-card>
          <el-card class="mod-card disaster-card" shadow="never">
            <template #header>
              <div class="card-hd">
                <span class="card-hd-title"
                  ><el-icon><WarningFilled /></el-icon> 灾情回溯</span
                >
              </div>
            </template>
            <el-empty description="暂无灾情记录（灾情模块建设中）" :image-size="60" />
          </el-card>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
// =============================================================================
//  Imports
// =============================================================================
import { ref, reactive, computed, onMounted } from "vue";
import { ElMessage } from "element-plus";
import baseService from "@/service/baseService";
import { useAppStore } from "@/store";
import {
  Location, Calendar, Filter, Operation, ArrowDown, ArrowUp,
  InfoFilled, RefreshRight, DataAnalysis, Tickets, DataLine,
  Document, Download, OfficeBuilding, WarningFilled
} from "@element-plus/icons-vue";

// Composables
import {
  RAIN_FILTER_LEVELS,
  TEMP_FILTER_FIELDS,
  YEAR_PRESETS,
  PREVIEW_COUNT,
  MIN_YEAR,
  MAX_YEAR_SPAN,
  fmtVal,
  rainLevelLabel,
  tmaxValClass,
  tminValClass,
  rainValClass
} from "@/composables/useWeatherConstants";
import { useWeatherFilter } from "@/composables/useWeatherFilter";
import { useWeatherStats, computeStats, buildStatCards, buildSummary, emptyStats } from "@/composables/useWeatherStats";
import { useWeatherChart } from "@/composables/useWeatherChart";
import { useWeatherExport } from "@/composables/useWeatherExport";
import type { WeatherDataRow } from "@/composables/useWeatherStats";
import { windDirectionCode, windDirectionLabel } from "@/utils/windDirection";

// =============================================================================
//  Date / Constants
// =============================================================================
const currentYear = new Date().getFullYear();
const now = new Date();
const appRoot = ref<HTMLElement | null>(null);
const loading = ref(false);

const anchorMonth = ref(now.getMonth() + 1);
const anchorDay = ref(now.getDate());
const padDay = computed(() => String(anchorDay.value).padStart(2, "0"));
const padNum = (n: number) => String(n).padStart(2, "0");

// =============================================================================
//  Station
// =============================================================================
const stations = ref<any[]>([]);
const selectedStation = ref("");

const currentStationName = computed(
  () => stations.value.find((s) => s.stationCode === selectedStation.value)?.stationName || "未知"
);
const currentAreaLabel = computed(() => "" + currentStationName.value);

function selectStation(code: string) {
  if (loading.value) return;
  selectedStation.value = code;
  refreshAll();
}

async function fetchStations() {
  try {
    const res = await baseService.get("/station/weatherstation/list");
    if (res.code === 0 && res.data) {
      stations.value = res.data;
      if (stations.value.length > 0 && !selectedStation.value) {
        selectedStation.value = stations.value[0].stationCode;
      }
    }
  } catch (err) {
    console.error("获取区站列表失败:", err);
  }
}

// =============================================================================
//  Year Span
// =============================================================================
const yearSpan = ref(30);
const yearSpanInput = ref(30);
const computedEndYear = computed(() => currentYear);
const computedStartYear = computed(() => currentYear - yearSpan.value + 1);
const computedSpan = computed(() => yearSpan.value);

function setYearSpan(y: number) {
  if (loading.value) return;
  yearSpan.value = y;
  yearSpanInput.value = y;
  refreshAll();
}

function applyManualSpan() {
  const v = Math.max(1, Math.min(MAX_YEAR_SPAN, yearSpanInput.value || 30));
  yearSpan.value = v;
  yearSpanInput.value = v;
  refreshAll();
}

// =============================================================================
//  Page State
// =============================================================================
const pageState = reactive({
  weatherData: [] as WeatherDataRow[],
  stats: emptyStats(),
  summary: "",
  statCards: [] as ReturnType<typeof buildStatCards>,
  disasters: [] as any[],
  dataFromYear: undefined as number | undefined,
  dataToYear: undefined as number | undefined
});

// =============================================================================
//  Wind direction
// =============================================================================
const _store = useAppStore();

function windDirectionLabelLocal(deg: number | null | string) {
  return windDirectionLabel(deg, _store.state.dicts);
}

// =============================================================================
//  Filter (via composable)
// =============================================================================
const tableExpanded = ref(false);

const {
  filterOpen,
  showOnlyFiltered,
  filters,
  availableDecades,
  hasActiveFilter,
  activeFilterCount,
  syncYearRange,
  resetFilters,
  toggleDecade,
  toggleRainLevel
} = useWeatherFilter({
  startYear: computedStartYear,
  endYear: computedEndYear
});

// =============================================================================
//  Filtered / Marked rows
// =============================================================================
const filterExtremes = computed(() => {
  if (!hasActiveFilter.value) return null;
  const d = filteredRows.value;
  if (!d.length) return null;
  const result = d.reduce(
    (acc, row) => ({
      maxRain: row.rainfall != null && (!acc.maxRain || row.rainfall > acc.maxRain.rainfall!) ? row : acc.maxRain,
      maxTmax: row.tmax != null && (!acc.maxTmax || row.tmax > acc.maxTmax.tmax!) ? row : acc.maxTmax,
      minTmin: row.tmin != null && (!acc.minTmin || row.tmin < acc.minTmin.tmin!) ? row : acc.minTmin,
      maxTavg: row.tavg != null && (!acc.maxTavg || row.tavg > acc.maxTavg.tavg!) ? row : acc.maxTavg
    }),
    { maxRain: null as WeatherDataRow | null, maxTmax: null as WeatherDataRow | null, minTmin: null as WeatherDataRow | null, maxTavg: null as WeatherDataRow | null }
  );
  return {
    maxRainYear: result.maxRain?.year ?? null,
    maxRain: result.maxRain?.rainfall ?? null,
    maxTmaxYear: result.maxTmax?.year ?? null,
    maxTmax: result.maxTmax?.tmax ?? null,
    minTminYear: result.minTmin?.year ?? null,
    minTmin: result.minTmin?.tmin ?? null,
    maxTavgYear: result.maxTavg?.year,
    maxTavg: result.maxTavg?.tavg ?? null
  };
});

const markedRows = computed<WeatherDataRow[]>(() => {
  const ex = filterExtremes.value;
  return pageState.weatherData.map((row) => {
    const rainLvl = rainLevelLabel(row.rainfall);
    let ok = true;
    if (hasActiveFilter.value) {
      ok = rowMatchesFilter(row, filters.value);
    }
    return {
      ...row,
      _rainLevel: rainLvl,
      _matched: hasActiveFilter.value ? ok : true,
      _isMaxRain: !!(ex && ok && row.year === ex.maxRainYear),
      _isMaxTmax: !!(ex && ok && row.year === ex.maxTmaxYear),
      _isMinTmin: !!(ex && ok && row.year === ex.minTminYear)
    };
  });
});

// 本地匹配包装（避免从 composable 重复导入 _rainLevel 相关逻辑）
function rowMatchesFilter(row: WeatherDataRow, f: typeof filters.value): boolean {
  if (f.rainfall.op) {
    const n = Number(row.rainfall);
    if (f.rainfall.op === "gte" && n < f.rainfall.val) return false;
    if (f.rainfall.op === "lte" && n > f.rainfall.val) return false;
    if (f.rainfall.op === "range" && (n < (f.rainfall.min ?? -Infinity) || n > (f.rainfall.max ?? Infinity))) return false;
  }
  if (f.tmaxF.op) {
    const n = Number(row.tmax);
    if (f.tmaxF.op === "gte" && n < f.tmaxF.val) return false;
    if (f.tmaxF.op === "lte" && n > f.tmaxF.val) return false;
    if (f.tmaxF.op === "range" && (n < (f.tmaxF.min ?? -Infinity) || n > (f.tmaxF.max ?? Infinity))) return false;
  }
  if (f.tminF.op) {
    const n = Number(row.tmin);
    if (f.tminF.op === "gte" && n < f.tminF.val) return false;
    if (f.tminF.op === "lte" && n > f.tminF.val) return false;
    if (f.tminF.op === "range" && (n < (f.tminF.min ?? -Infinity) || n > (f.tminF.max ?? Infinity))) return false;
  }
  if (f.tavgF.op) {
    const n = Number(row.tavg);
    if (f.tavgF.op === "gte" && n < f.tavgF.val) return false;
    if (f.tavgF.op === "lte" && n > f.tavgF.val) return false;
    if (f.tavgF.op === "range" && (n < (f.tavgF.min ?? -Infinity) || n > (f.tavgF.max ?? Infinity))) return false;
  }
  if (f.decades.length > 0 && !f.decades.includes(Math.floor(row.year / 10) * 10)) return false;
  if (f.rainLevel.length > 0 && !f.rainLevel.includes(rainLevelLabel(row.rainfall))) return false;
  return true;
}

const filteredRows = computed(() => markedRows.value.filter((r) => r._matched));
const displayRows = computed(() =>
  showOnlyFiltered.value && hasActiveFilter.value ? filteredRows.value : markedRows.value
);
const visibleRows = computed(() =>
  tableExpanded.value ? displayRows.value : displayRows.value.slice(0, PREVIEW_COUNT)
);

const fStats = computed(() => {
  const d = filteredRows.value;
  if (!d.length) return {} as Record<string, any>;
  const result = d.reduce(
    (acc, row) => ({
      sumRain: row.rainfall != null ? acc.sumRain + row.rainfall : acc.sumRain,
      sumTmax: row.tmax != null ? acc.sumTmax + row.tmax : acc.sumTmax,
      validRainCount: acc.validRainCount + (row.rainfall != null ? 1 : 0),
      validTmaxCount: acc.validTmaxCount + (row.tmax != null ? 1 : 0),
      maxRain:
        row.rainfall != null && row.rainfall > (acc.maxRain?.rainfall ?? -Infinity)
          ? { rainfall: row.rainfall, year: row.year }
          : acc.maxRain,
      maxTmax:
        row.tmax != null && row.tmax > (acc.maxTmax?.tmax ?? -Infinity)
          ? { tmax: row.tmax, year: row.year }
          : acc.maxTmax
    }),
    {
      sumRain: 0, sumTmax: 0, validRainCount: 0, validTmaxCount: 0,
      maxRain: null as any, maxTmax: null as any
    }
  );
  return {
    avgRain: result.validRainCount > 0 ? (result.sumRain / result.validRainCount).toFixed(1) : "—",
    avgTmax: result.validTmaxCount > 0 ? (result.sumTmax / result.validTmaxCount).toFixed(1) : "—",
    maxRain: result.maxRain?.rainfall ?? "—",
    maxRainYear: result.maxRain?.year ?? "—",
    peakTmax: result.maxTmax?.tmax ?? "—",
    peakTmaxYear: result.maxTmax?.year ?? "—"
  };
});

// =============================================================================
//  Stats (via composable)
// =============================================================================
const { rainLevelDistribution } = useWeatherStats(displayRows);

const filterSummaryText = computed(() => {
  if (!filteredRows.value.length) return "";
  const s = fStats.value;
  return `筛选命中 ${filteredRows.value.length} 个年份，平均降雨 ${s.avgRain}mm，平均最高温 ${s.avgTmax}℃。最大降雨 ${s.maxRainYear} 年（${s.maxRain}mm），最高温极值 ${s.peakTmaxYear} 年（${s.peakTmax}℃）。`;
});

function rowClassName({ row }: { row: WeatherDataRow }) {
  const classes: string[] = [];
  if (hasActiveFilter.value) classes.push(row._matched ? "row-hit" : "row-dim");
  if (row._isMaxRain || row._isMaxTmax || row._isMinTmin) classes.push("row-extreme");
  return classes.join(" ");
}

// =============================================================================
//  Chart (via composable)
// =============================================================================
const {
  chartRef,
  renderChart
} = useWeatherChart(
  computed(() => pageState.weatherData),
  filteredRows,
  showOnlyFiltered,
  hasActiveFilter,
  filterExtremes,
  anchorMonth,
  anchorDay
);

// =============================================================================
//  Export (via composable)
// =============================================================================
const { doExport, exporting } = useWeatherExport(appRoot, {
  expandTable: tableExpanded,
  collapseFilter: filterOpen,
  anchorMonth,
  padDay
});

// =============================================================================
//  Data Fetching
// =============================================================================
async function refreshAll() {
  loading.value = true;
  tableExpanded.value = false;

  try {
    const m = anchorMonth.value;
    const d = anchorDay.value;
    const from = computedStartYear.value;
    const to = computedEndYear.value;

    const activeStation = stations.value.find((s) => s.stationCode === selectedStation.value);
    const stationId = activeStation ? activeStation.stationCode : 0;

    const res = await baseService.post("/dailyweather/weatherdailydata/export-sum", {
      stationIds: [stationId],
      startDate: `${from}-${padNum(m)}-${padNum(d)}`,
      endtDate: `${to}-${padNum(m)}-${padNum(d)}`
    });

    if (res.code !== 0) {
      ElMessage.error(res.msg || "数据加载失败");
      return;
    }

    const rawData = res.data || [];

    const mapped: WeatherDataRow[] = rawData
      .map((row: any) => {
        const dt = new Date(row.observeDate);
        return {
          year: dt.getFullYear(),
          rainfall: row.rain2020 != null ? +row.rain2020 : null,
          tmax: row.maxTemp != null ? +row.maxTemp : null,
          tavg: row.avgTemp != null ? +row.avgTemp : null,
          tmin: row.minTemp != null ? +row.minTemp : null,
          windSpeed: row.dayAvgWindSpeed != null ? +row.dayAvgWindSpeed : null,
          windDirection: row.dayAvgWindDirection != null ? +row.dayAvgWindDirection : null,
          humidity: row.relativeHumidity != null ? +row.relativeHumidity : undefined,
          pressure: row.atmospheres != null ? +row.atmospheres : undefined
        };
      })
      .sort((a: WeatherDataRow, b: WeatherDataRow) => b.year - a.year);

    const stats = computeStats(mapped);
    const sName = currentStationName.value;

    pageState.weatherData = mapped;
    pageState.stats = stats;
    pageState.statCards = buildStatCards(stats);
    const dataYears = mapped.map((r: WeatherDataRow) => r.year).filter((y: number) => !isNaN(y));
    pageState.dataFromYear = dataYears.length ? Math.min(...dataYears) : undefined;
    pageState.dataToYear = dataYears.length ? Math.max(...dataYears) : undefined;

    pageState.summary = buildSummary(stats, {
      stationName: sName,
      fromYear: from,
      toYear: to,
      month: m,
      day: d,
      count: mapped.length,
      dataFromYear: pageState.dataFromYear,
      dataToYear: pageState.dataToYear
    });
    pageState.disasters = [];

    syncYearRange(from, to);

    // 下一帧渲染图表
    renderChart();
  } catch (err) {
    console.error("数据获取失败:", err);
    ElMessage.error("网络请求失败，请检查网络连接");
  } finally {
    loading.value = false;
  }
}

// =============================================================================
//  Lifecycle
// =============================================================================
onMounted(async () => {
  await fetchStations();
  if (stations.value.length > 0) {
    await refreshAll();
  }
});
</script>

<style scoped>
/* Google Fonts loaded via <link> in index.html — remove @import to avoid blocking render */

.weather-app {
  --blue: #2563eb;
  --blue-light: #dbeafe;
  --red: #ef4444;
  --green: #10b981;
  --amber: #f59e0b;
  --text-1: #0f172a;
  --text-2: #475569;
  --text-3: #94a3b8;
  --border: #e2e8f0;
  --bg: #f1f5f9;
  --card: #fff;
  --radius: 12px;
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.04), 0 4px 12px rgba(0, 0, 0, 0.03);
  font-family: "Noto Sans SC", system-ui, sans-serif;
  background: var(--bg);
  min-height: 100vh;
  color: var(--text-1);
}
.app-header {
  background: linear-gradient(135deg, #0f2847 0%, #1e40af 60%, #3b82f6 100%);
  color: #fff;
  padding: 10px 14px;
  position: relative;
  overflow: hidden;
}
.app-header::before {
  content: "";
  position: absolute;
  top: -60%;
  right: -5%;
  width: clamp(200px, 30vw, 450px);
  height: clamp(200px, 30vw, 450px);
  background: radial-gradient(circle, rgba(255, 255, 255, 0.06) 0%, transparent 70%);
  border-radius: 50%;
}
.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  position: relative;
  z-index: 1;
}
.header-left {
  display: flex;
  align-items: center;
  gap: 18px;
}
.cal-icon {
  width: 60px;
  height: 66px;
  background: rgba(255, 255, 255, 0.12);
  backdrop-filter: blur(6px);
  border: 2px solid rgba(255, 255, 255, 0.25);
  border-radius: 12px;
  overflow: hidden;
  flex-shrink: 0;
}
.today-icon {
  cursor: default;
}
.cal-month {
  background: rgba(255, 255, 255, 0.18);
  text-align: center;
  font-size: 12px;
  font-weight: 600;
  padding: 3px 0;
}
.cal-day {
  font-size: 26px;
  font-weight: 900;
  text-align: center;
  line-height: 40px;
  font-family: "JetBrains Mono", monospace;
}
.page-title {
  margin: 0 0 4px;
  font-size: 19px;
  font-weight: 700;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
}
.title-sep {
  opacity: 0.4;
  font-weight: 300;
}
.header-meta {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  font-size: 13px;
  opacity: 0.88;
}
.meta-divider {
  width: 1px;
  height: 14px;
  background: rgba(255, 255, 255, 0.25);
}
.meta-chip {
  display: inline-flex;
  align-items: center;
  gap: 3px;
}
.meta-chip.filter-hint {
  color: #fbbf24;
  font-weight: 600;
}
.header-right .el-button {
  --el-button-bg-color: rgba(255, 255, 255, 0.12);
  --el-button-border-color: rgba(255, 255, 255, 0.25);
  --el-button-text-color: #fff;
  --el-button-hover-bg-color: rgba(255, 255, 255, 0.22);
  --el-button-hover-border-color: rgba(255, 255, 255, 0.35);
  --el-button-hover-text-color: #fff;
}
.app-body {
  max-width: min(1400px, 98%);
  margin: 0 auto;
  padding: 20px 24px;
}
.today-hero {
  background: linear-gradient(135deg, #fefce8, #fef9c3, #fef08a);
  border: 2px solid #fbbf24;
  border-radius: var(--radius);
  padding: 22px 26px;
  margin-bottom: 16px;
  position: relative;
  overflow: hidden;
}
.today-hero::after {
  content: "";
  position: absolute;
  right: 20px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 90px;
  opacity: 0.06;
  pointer-events: none;
}
.hero-badge-row {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
.hero-date-badge,
.hero-span-badge {
  display: inline-block;
  padding: 3px 12px;
  border-radius: 16px;
  font-size: 12px;
  font-weight: 600;
}
.hero-date-badge {
  background: #fbbf24;
  color: #78350f;
}
.hero-span-badge {
  background: rgba(120, 53, 15, 0.08);
  color: #92400e;
}
.hero-headline {
  font-size: 18px;
  font-weight: 800;
  color: #78350f;
  margin: 0 0 8px;
}
.hero-date-em {
  color: #b45309;
  font-size: 20px;
}
.hero-desc {
  font-size: 13.5px;
  color: #78350f;
  line-height: 1.75;
  margin-bottom: 14px;
  position: relative;
  z-index: 1;
}
.hero-desc .hot {
  color: #dc2626;
}
.hero-desc .cold {
  color: #2563eb;
}
.hero-stats {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  position: relative;
  z-index: 1;
}
.hero-stat-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.hero-stat {
  background: rgba(255, 255, 255, 0.65);
  backdrop-filter: blur(4px);
  border: 1px solid rgba(251, 191, 36, 0.3);
  border-radius: 10px;
  padding: 10px 16px;
  min-width: clamp(80px, 10vw, 150px);
  text-align: center;
}
.hs-label {
  font-size: 11px;
  color: #92400e;
  font-weight: 500;
  margin-bottom: 2px;
}
.hs-val {
  font-size: 22px;
  font-weight: 800;
  font-family: "JetBrains Mono", monospace;
}
.hs-unit {
  font-size: 12px;
  font-weight: 400;
  opacity: 0.7;
  margin-left: 2px;
}
.control-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 20px;
  background: var(--card);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  margin-bottom: 14px;
  flex-wrap: wrap;
}
.cb-group {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.cb-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-2);
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 4px;
}
.cb-sep {
  width: 1px;
  height: 24px;
  background: var(--border);
  margin: 0 8px;
}
.chip-group {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.chip {
  padding: 5px 14px;
  border-radius: 20px;
  font-size: 12.5px;
  font-weight: 500;
  border: 1.5px solid var(--border);
  background: var(--card);
  cursor: pointer;
  transition: all 0.2s;
  color: var(--text-2);
  user-select: none;
}
.chip:hover {
  border-color: var(--blue);
  color: var(--blue);
}
.chip.active {
  background: var(--blue);
  color: #fff;
  border-color: var(--blue);
  font-weight: 600;
  box-shadow: 0 2px 8px rgba(37, 99, 235, 0.25);
}
.filter-panel {
  background: var(--card);
  border: 1.5px solid #fed7aa;
  border-radius: var(--radius);
  margin-bottom: 16px;
  overflow: hidden;
  box-shadow: var(--shadow);
}
.filter-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 13px 20px;
  cursor: pointer;
  user-select: none;
  background: linear-gradient(90deg, #fffbeb, #fff7ed);
  transition: background 0.2s;
}
.filter-header:hover {
  background: linear-gradient(90deg, #fef3c7, #ffedd5);
}
.fh-left {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 14px;
  font-weight: 600;
  color: #92400e;
}
.fh-arrow {
  transition: transform 0.3s;
  color: #92400e;
}
.fh-arrow.open {
  transform: rotate(180deg);
}
.filter-body {
  padding: 18px 20px;
}
.filter-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 16px;
  margin-bottom: 16px;
}
.filter-group {
  background: #fafafa;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
}
.fg-title {
  font-size: 13px;
  font-weight: 700;
  margin-bottom: 12px;
  display: flex;
  align-items: center;
  gap: 6px;
  padding-bottom: 8px;
  border-bottom: 1px solid #f0f0f0;
}
.fg-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.fg-dot.rain {
  background: var(--blue);
}
.fg-dot.temp {
  background: var(--red);
}
.fg-dot.year-dot {
  background: var(--green);
}
.fg-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 8px;
  flex-wrap: wrap;
}
.fg-label {
  font-size: 12px;
  color: var(--text-2);
  width: clamp(64px, 16%, 100px);
  flex-shrink: 0;
  font-weight: 500;
}
.fg-unit {
  font-size: 11px;
  color: var(--text-3);
}
.fg-sep {
  color: var(--text-3);
}
.level-chips {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
}
.level-chip {
  padding: 3px 10px;
  border-radius: 14px;
  font-size: 11.5px;
  border: 1.5px solid var(--border);
  background: #fff;
  cursor: pointer;
  transition: all 0.2s;
  color: var(--text-2);
  user-select: none;
}
.level-chip:hover {
  border-color: var(--blue);
  color: var(--blue);
}
.level-chip.active {
  background: var(--blue);
  color: #fff;
  border-color: var(--blue);
}
.filter-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-top: 12px;
  border-top: 1px dashed #e5e7eb;
  flex-wrap: wrap;
  gap: 10px;
}
.fa-result {
  font-size: 13px;
  color: var(--text-2);
  display: flex;
  align-items: center;
  gap: 4px;
}
.fa-result b {
  color: var(--blue);
  font-size: 16px;
  font-weight: 700;
}
.no-hit {
  color: var(--red);
  margin-left: 4px;
}
.fa-btns {
  display: flex;
  gap: 8px;
}

/* ==================== main grid ==================== */
.main-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(280px, 28%);
  gap: 20px;
}

/* 大屏：侧边栏更宽 */
@media (min-width: 1600px) {
  .main-grid {
    grid-template-columns: minmax(0, 1fr) minmax(320px, 26%);
  }
  .chart-box {
    height: min(55vh, 480px);
  }
}

@media (max-width: 1100px) {
  .main-grid {
    grid-template-columns: 1fr;
  }
}

/* 筛选面板在中等宽度下也单列 */
@media (max-width: 900px) {
  .filter-grid {
    grid-template-columns: 1fr;
  }
}

.mod-card {
  border-radius: var(--radius) !important;
  border: none !important;
  box-shadow: var(--shadow) !important;
  margin-bottom: 18px;
}
.card-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}
.card-hd-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  font-size: 14px;
}
.card-hd-unit {
  font-size: 12px;
  color: var(--text-3);
}
.chart-box {
  width: 100%;
  height: min(50vh, 400px);
}
.table-card {
  :deep(.el-card__body) {
    padding: 0 6px 20px;
  }
  :deep(.el-table) {
    width: 100%;
  }
}
.table-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
}
.table-hd-left {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 600;
  font-size: 14px;
}
.table-hd-right {
  display: flex;
  align-items: center;
}
.cell-year {
  font-family: "JetBrains Mono", monospace;
  font-weight: 600;
  color: var(--blue);
}
.cell-date {
  font-family: "JetBrains Mono", monospace;
  font-size: 12px;
  color: var(--text-2);
}
.val-warn {
  color: var(--amber);
  font-weight: 600;
}
.val-danger {
  color: var(--red);
  font-weight: 600;
}
.val-extreme {
  color: #b91c1c;
  font-weight: 700;
}
.val-cold {
  color: var(--blue);
  font-weight: 600;
}
.val-cold-x {
  color: #1e3a8a;
  font-weight: 700;
}
:deep(.row-hit) {
  background-color: #ecfdf5 !important;
}
:deep(.row-dim td) {
  color: #cbd5e1 !important;
  background-color: #fafafa !important;
}
:deep(.row-extreme) {
  position: relative;
}
:deep(.row-extreme td) {
  font-weight: 700 !important;
}
.extreme_cell {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.extreme-badge {
  display: inline-block;
  font-size: 9px;
  font-weight: 800;
  padding: 1px 5px;
  border-radius: 4px;
  color: #fff;
  line-height: 1.3;
  letter-spacing: 0.5px;
  vertical-align: middle;
}
.rain-badge {
  background: #7c3aed;
}
.hot-badge {
  background: #dc2626;
}
.cold-badge {
  background: #1d4ed8;
}
.table-fade-hint {
  text-align: center;
  padding: 10px 0;
  position: relative;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), rgba(255, 255, 255, 0.9) 30%, #fff 100%);
  margin-top: -30px;
}
.filter-stats-bar {
  margin-top: 10px;
  padding: 10px 0 0;
  border-top: 1px solid var(--border);
  background: linear-gradient(90deg, #ecfdf5, #eff6ff);
  font-size: 12.5px;
  color: #334155;
  display: flex;
  align-items: center;
  gap: 6px;
}
.filter-stats-bar .blue {
  color: var(--blue);
}
.filter-stats-bar .red {
  color: var(--red);
}
.summary-card {
  border-left: 4px solid var(--amber) !important;
}
.summary-card :deep(.el-card__body) {
  background: linear-gradient(135deg, #fffbeb, #fefce8);
}
.summary-text {
  font-size: 13.5px;
  line-height: 1.8;
  color: #78350f;
}
.filter-summary-box {
  margin-top: 12px;
  padding: 10px 14px;
  background: rgba(37, 99, 235, 0.05);
  border-radius: 8px;
  border-left: 3px solid var(--blue);
}
.filter-summary-box p {
  font-size: 12.5px;
  line-height: 1.7;
  color: var(--text-2);
  margin: 0;
}
.rain-prob-card {
  border-left: 4px solid var(--blue) !important;
}
.rain-prob-card :deep(.el-card__body) {
  padding: 14px 16px;
}
.rp-body {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.rp-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
}
.rp-label {
  width: clamp(36px, 18%, 56px);
  flex-shrink: 0;
  font-weight: 500;
  color: var(--text-2);
}
.rp-bar-track {
  flex: 1;
  height: 8px;
  background: #f1f5f9;
  border-radius: 4px;
  overflow: hidden;
}
.rp-bar-fill {
  height: 100%;
  border-radius: 4px;
  background: linear-gradient(90deg, #60a5fa, #2563eb);
  transition: width 0.3s ease;
}
.rp-pct {
  width: clamp(30px, 14%, 48px);
  text-align: right;
  font-weight: 600;
  font-family: "JetBrains Mono", monospace;
  font-size: 11px;
  color: var(--text-1);
}
.rp-count {
  width: clamp(32px, 16%, 52px);
  text-align: right;
  font-size: 11px;
  color: var(--text-3);
}

/* ==================== Responsive ==================== */
@media (max-width: 768px) {
  .app-header {
    padding: 12px;
  }
  .header-inner {
    flex-direction: column;
    align-items: flex-start;
  }
  .header-left {
    flex-wrap: wrap;
    gap: 10px;
  }
  .page-title {
    font-size: 16px;
  }
  .cal-icon {
    width: 48px;
    height: 54px;
  }
  .cal-day {
    font-size: 20px;
  }
  .app-body {
    padding: 12px;
  }
  .today-hero {
    padding: 14px;
  }
  .hero-headline {
    font-size: 15px;
  }
  .hero-desc {
    font-size: 12.5px;
  }
  .hero-stats {
    flex-direction: column;
  }
  .control-bar {
    flex-direction: column;
    align-items: flex-start;
  }
  .cb-sep {
    display: none;
  }
  .chart-box {
    height: min(40vh, 260px);
  }
  :deep(.el-table) {
    overflow-x: auto;
    display: block;
  }
  :deep(.el-table__body-wrapper) {
    overflow-x: auto;
  }
  .table-hd {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }
  .table-hd-left {
    font-size: 13px;
    flex-wrap: wrap;
  }
  .table-hd-right {
    width: 100%;
    justify-content: flex-start;
  }
  .header-right .el-button {
    font-size: 0;
  }
  .header-right .el-button .el-icon--right {
    margin-left: 0;
  }
}

@media (max-width: 480px) {
  .chart-box {
    height: min(35vh, 220px);
  }
  .hero-stat {
    min-width: 80px;
    padding: 8px 12px;
  }
  .hs-val {
    font-size: 18px;
  }
  .fg-label {
    width: clamp(50px, 14%, 80px);
  }
}
</style>
