<template>
  <div class="container">
    <div class="page-toolbar">
      <div class="toolbar-left">
        <span class="toolbar-label">当前设备</span>
        <a-select
          v-model="selectedServiceIdentity"
          class="server-select"
          placeholder="选择存储设备"
          allow-search
          allow-clear
          :loading="dropdownLoading"
          :filter-option="false"
          @search="onMonitorSelectSearch"
          @change="onDeviceSelectChange"
        >
          <a-option
            v-for="item in storageOptions"
            :key="item.service_identity"
            :value="item.service_identity"
            :label="`${item.name} ${item.type} ${item.service_identity}`"
          >
            <div class="server-option">
              <span class="server-option-name">{{ item.name }}</span>
              <span class="server-option-ip">{{ item.type }} · {{ item.service_identity }}</span>
            </div>
          </a-option>
        </a-select>
      </div>
      <div v-if="activeStorage" class="toolbar-meta text-muted">
        {{ activeStorage.collect_method === 'snmp' ? 'SNMP' : 'API' }} ·
        {{ activeStorage.server_identity || '未绑定主机' }}
      </div>
    </div>

    <div v-if="globalHint" class="global-hint text-muted">{{ globalHint }}</div>

    <!-- 行 A：控制器关联主机 + 控制器状态 -->
    <a-spin :loading="summaryLoading" style="width: 100%">
      <a-row :gutter="16" class="stats-row">
        <a-col :xs="24" :sm="12" :lg="4">
          <a-card class="stats-card" :bordered="false">
            <div class="stats-content">
              <div class="stats-icon stats-icon-primary">
                <icon-dashboard />
              </div>
              <div class="stats-info">
                <div class="stats-title">CPU 使用率</div>
                <div class="stats-value">{{ formatPercent(cpuUsage) }}</div>
                <div class="stats-desc">{{ hostSummaryHint }}</div>
              </div>
            </div>
          </a-card>
        </a-col>
        <a-col :xs="24" :sm="12" :lg="4">
          <a-card class="stats-card" :bordered="false">
            <div class="stats-content">
              <div class="stats-icon stats-icon-cyan">
                <icon-storage />
              </div>
              <div class="stats-info">
                <div class="stats-title">内存使用率</div>
                <div class="stats-value">{{ formatPercent(memUsage) }}</div>
                <div class="stats-desc">{{ hostSummaryHint }}</div>
              </div>
            </div>
          </a-card>
        </a-col>
        <a-col :xs="24" :sm="12" :lg="4">
          <a-card class="stats-card" :bordered="false">
            <div class="stats-content">
              <div class="stats-icon stats-icon-purple">
                <icon-code-square />
              </div>
              <div class="stats-info">
                <div class="stats-title">虚拟内存使用率</div>
                <div class="stats-value">{{ formatPercent(swapUsage) }}</div>
                <div class="stats-desc">{{ swapHint }}</div>
              </div>
            </div>
          </a-card>
        </a-col>
        <a-col :xs="24" :sm="12" :lg="4">
          <a-card class="stats-card" :bordered="false">
            <div class="stats-content">
              <div class="stats-icon stats-icon-green">
                <icon-drive-file />
              </div>
              <div class="stats-info">
                <div class="stats-title">系统盘占用</div>
                <div class="stats-value">{{ formatPercent(diskUsedPercent) }}</div>
                <div class="stats-desc">{{ diskUsedDesc }}</div>
              </div>
            </div>
          </a-card>
        </a-col>
        <a-col :xs="24" :sm="12" :lg="4">
          <a-card class="stats-card" :bordered="false">
            <div class="stats-content">
              <div class="stats-icon stats-icon-success">
                <icon-folder />
              </div>
              <div class="stats-info">
                <div class="stats-title">系统盘可用率</div>
                <div class="stats-value">{{ formatPercent(diskAvailPercent) }}</div>
                <div class="stats-desc">剩余空间占比（/）</div>
              </div>
            </div>
          </a-card>
        </a-col>
        <a-col :xs="24" :sm="12" :lg="4">
          <a-card class="stats-card" :bordered="false">
            <div class="stats-content">
              <div class="stats-icon stats-icon-warning">
                <icon-check-circle-fill />
              </div>
              <div class="stats-info">
                <div class="stats-title">控制器状态</div>
                <div class="stats-value">
                  <a-tag :color="controllerStatusColor">{{ controllerStatusText }}</a-tag>
                </div>
                <div class="stats-desc">存储服务探活</div>
              </div>
            </div>
          </a-card>
        </a-col>
      </a-row>
    </a-spin>

    <!-- 行 B：当前设备采集详情 + 存储指标趋势（折线图），左右等高 -->
    <a-row :gutter="16" class="chart-row chart-row-equal">
      <a-col :xs="24" :lg="10" class="chart-pair-col">
        <a-card title="当前设备采集详情" :bordered="false" class="chart-pair-card detail-side-card">
          <template v-if="activeStorage">
            <a-descriptions :column="1" size="small" bordered>
              <a-descriptions-item label="名称">{{ activeStorage.name }}</a-descriptions-item>
              <a-descriptions-item label="标识">{{ activeStorage.service_identity }}</a-descriptions-item>
              <a-descriptions-item label="类型">{{ activeStorage.type }}</a-descriptions-item>
              <a-descriptions-item label="周期采集">
                <a-tag :color="activeStorage.collect_on ? 'green' : 'gray'">
                  {{ activeStorage.collect_on ? '开启' : '关闭' }}
                </a-tag>
                <span class="text-muted">· {{ activeStorage.collect_interval || '-' }}s</span>
              </a-descriptions-item>
              <a-descriptions-item label="最近检查">
                {{ formatDateTime(activeStorage.last_check_time) }}
              </a-descriptions-item>
              <a-descriptions-item label="响应耗时">
                {{ activeStorage.response_time ? `${activeStorage.response_time.toFixed(1)} ms` : '-' }}
              </a-descriptions-item>
              <a-descriptions-item label="连续错误">{{ activeStorage.continuous_errors ?? 0 }}</a-descriptions-item>
              <a-descriptions-item label="最近结果">
                <span class="collect-result">{{ activeStorage.collect_last_result || '-' }}</span>
              </a-descriptions-item>
            </a-descriptions>
          </template>
          <a-empty v-else description="请选择存储设备" />
        </a-card>
      </a-col>
      <a-col :xs="24" :lg="14" class="chart-pair-col">
        <a-card :bordered="false" class="chart-pair-card trend-side-card">
          <template #title>
            <div class="card-header">
              <div class="card-title">存储指标趋势</div>
              <div class="card-subtitle">近 24 小时 · ECharts 折线图（原始样本按小时取 max 聚合展示）</div>
            </div>
          </template>
          <template #extra>
            <a-space size="small" wrap align="center">
              <span class="text-muted">指标</span>
              <a-select
                v-model="trendSelectedMetric"
                class="trend-metric-select"
                placeholder="选择指标"
                allow-search
                :loading="metricsOptionsLoading"
                :options="trendMetricOptions"
                :disabled="!trendMetricOptions.length"
                @change="refreshTimeseries"
              />
              <a-button type="outline" size="small" @click="refreshTimeseries">刷新曲线</a-button>
            </a-space>
          </template>
          <a-spin :loading="chartLoading" class="chart-spin chart-spin--fill">
            <div class="chart-content-wrapper">
              <p v-if="chartHint" class="chart-hint text-muted">{{ chartHint }}</p>
              <div class="chart-container chart-container--line">
                <!-- Chart 为 ECharts 封装；单序列 type: 'line' 折线 -->
                <Chart v-if="hasChartSeries" :options="ioChartOptions" />
                <a-empty v-else description="暂无趋势数据或指标未上报" />
              </div>
            </div>
          </a-spin>
        </a-card>
      </a-col>
    </a-row>
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import dayjs from 'dayjs'
import { Message } from '@arco-design/web-vue'
import { useDebounceFn } from '@vueuse/core'
import { IconDashboard, IconStorage, IconCodeSquare, IconDriveFile, IconFolder, IconCheckCircleFill } from '@arco-design/web-vue/es/icon'
import Chart from '@/components/chart/index.vue'
import {
  fetchStorageList,
  fetchStorageMonitorList,
  fetchStorageMonitorSummary,
  fetchStorageMonitorCollectedMetrics,
  fetchStorageMetricsLatest,
  fetchStorageMetricsTimeseries,
  type StorageItem,
  type StorageMonitorOptionItem,
  type StorageMonitorSummaryPayload,
} from '@/api/ops/storage'

/** 趋势图当前选中的指标名（选项来自「按设备 id 最新一批」接口，无 id 时回退 latest） */
const trendSelectedMetric = ref('')
const trendMetricOptions = ref<{ label: string; value: string }[]>([])
const metricsOptionsLoading = ref(false)

const dropdownLoading = ref(false)
const summaryLoading = ref(false)
const chartLoading = ref(false)

const storageList = ref<StorageItem[]>([])
const storageOptions = ref<StorageMonitorOptionItem[]>([])
const selectedServiceIdentity = ref<string | undefined>(undefined)

const monitorSummary = ref<StorageMonitorSummaryPayload | null>(null)

const chartSeriesPoints = ref<{ time: string; value: number }[]>([])

let pollTimer: ReturnType<typeof setInterval> | null = null

const activeStorage = computed(() => storageList.value.find((s) => s.service_identity === selectedServiceIdentity.value))

const globalHint = computed(() => {
  const m = monitorSummary.value
  if (m && m.total_devices > 0) {
    return `全量：${m.total_devices} 台 · 在线 ${m.online_count} · 离线 ${m.offline_count}${
      m.other_count > 0 ? ` · 其它 ${m.other_count}` : ''
    }`
  }
  const n = storageList.value.length
  if (!n) return ''
  const online = storageList.value.filter((s) => s.status === 'online').length
  const offline = storageList.value.filter((s) => s.status === 'offline').length
  return `全量：${n} 台 · 在线 ${online} · 离线 ${offline}`
})

function formatDateTime(v: string | undefined) {
  if (!v) return '-'
  const d = dayjs(v)
  return d.isValid() ? d.format('YYYY-MM-DD HH:mm') : v
}

function formatPercent(v: number | null | undefined) {
  if (v === null || v === undefined || Number.isNaN(v)) return '-'
  return `${v.toFixed(1)}%`
}

const currentHost = computed(() => monitorSummary.value?.current?.host)

const hostSummaryHint = computed(() => {
  if (!monitorSummary.value?.current?.server_identity) return '未绑定 server_identity'
  const h = currentHost.value
  if (!h) return '未关联主机摘要'
  if (!h.has_data) return '主机暂无采集数据'
  return '关联主机最新批次'
})

const cpuUsage = computed(() => currentHost.value?.cpu_usage_percent ?? null)
const memUsage = computed(() => currentHost.value?.memory_usage_percent ?? null)
const swapUsage = computed(() => currentHost.value?.swap_usage_percent ?? null)

const swapHint = computed(() => {
  const h = currentHost.value
  if (!h || h.swap_usage_percent === null || h.swap_usage_percent === undefined) {
    return '无 Swap 或未上报'
  }
  return 'Swap 使用率'
})

const diskUsedPercent = computed(() => currentHost.value?.disk_root_used_percent ?? null)

const diskUsedDesc = computed(() => {
  const p = diskUsedPercent.value
  if (p === null || p === undefined) return '-'
  return `已用 ${p.toFixed(1)}%`
})

const diskAvailPercent = computed(() => {
  const u = diskUsedPercent.value
  if (u === null || u === undefined) return null
  return Math.max(0, Math.min(100, 100 - u))
})

const controllerStatusText = computed(() => {
  const s = monitorSummary.value?.current?.controller_status ?? activeStorage.value?.status
  switch (s) {
    case 'online':
      return '在线'
    case 'offline':
      return '离线'
    case 'error':
      return '异常'
    default:
      return '未知'
  }
})

const controllerStatusColor = computed(() => {
  const s = monitorSummary.value?.current?.controller_status ?? activeStorage.value?.status
  if (s === 'online') return 'green'
  if (s === 'offline') return 'red'
  if (s === 'error') return 'orange'
  return 'gray'
})

const chartHint = computed(() => {
  if (!selectedServiceIdentity.value) return ''
  if (!trendMetricOptions.value.length) {
    return '该设备暂无已上报的指标批次，无法选择指标；请确认采集已写入。'
  }
  if (!trendSelectedMetric.value) return '请选择要展示的指标。'
  if (!chartSeriesPoints.value.length) {
    return '若需曲线，请确保近 24h 已写入当前所选指标。'
  }
  return ''
})

const hasChartSeries = computed(() => chartSeriesPoints.value.length > 0)

const trendSeriesDisplayName = computed(() => {
  const v = trendSelectedMetric.value
  if (!v) return '指标'
  const opt = trendMetricOptions.value.find((o) => o.value === v)
  return opt?.label ?? v
})

/** ECharts 折线图：单序列 type: 'line'，由 @/components/chart 渲染 */
const ioChartOptions = computed(() => {
  const pts = chartSeriesPoints.value
  const labels = pts.map((p) => dayjs(p.time).format('MM-DD HH:mm'))
  const values = pts.map((p) => p.value)
  const name = trendSeriesDisplayName.value
  return {
    tooltip: { trigger: 'axis' },
    legend: { show: true, data: [name] },
    grid: { left: '3%', right: '4%', bottom: '40px', containLabel: true },
    xAxis: {
      type: 'category',
      boundaryGap: false,
      data: labels,
      axisLabel: {
        interval: 'auto',
        fontSize: 11,
      },
    },
    yAxis: { type: 'value', name: '指标值' },
    series: [
      {
        name,
        type: 'line',
        smooth: true,
        connectNulls: true,
        data: values,
        lineStyle: { width: 2, color: '#165dff' },
        itemStyle: { color: '#165dff' },
        showSymbol: values.length <= 48,
      },
    ],
  }
})

function syncOptionsFromList() {
  storageOptions.value = storageList.value.map((r) => ({
    service_identity: r.service_identity,
    name: r.name,
    type: r.type,
    server_identity: r.server_identity || '',
    status: r.status || '',
  }))
}

function mergeMonitorOptionAsRow(opt: StorageMonitorOptionItem) {
  if (storageList.value.some((s) => s.service_identity === opt.service_identity)) return
  storageList.value.push({
    id: 0,
    created_at: '',
    updated_at: '',
    deleted_at: null,
    service_identity: opt.service_identity,
    oid: '',
    server_identity: opt.server_identity,
    name: opt.name,
    category: 'storage',
    type: opt.type,
    description: '',
    enabled: true,
    interval: 60,
    extra: '{}',
    tags: '',
    status_url: '',
    agent_config: '',
    collect_method: 'api',
    snmp_target: '',
    snmp_port: 161,
    snmp_version: 'v2c',
    snmp_community: '',
    snmp_v3_security_level: '',
    snmp_v3_security_name: '',
    snmp_v3_auth_protocol: '',
    snmp_v3_auth_password: '',
    snmp_v3_priv_protocol: '',
    snmp_v3_priv_password: '',
    snmp_v3_context_name: '',
    snmp_timeout_ms: 3000,
    snmp_retries: 1,
    snmp_oids: '[]',
    collect_on: false,
    collect_args: '',
    collect_interval: 60,
    collect_last_result: '',
    status: opt.status,
    status_code: 0,
    status_message: '',
    response_time: 0,
    last_check_time: '',
    last_online_time: null,
    last_offline_time: null,
    continuous_errors: 0,
    uptime: 0,
  })
}

function onDeviceSelectChange(sid: string | undefined) {
  if (!sid) return
  if (!storageList.value.some((s) => s.service_identity === sid)) {
    const opt = storageOptions.value.find((o) => o.service_identity === sid)
    if (opt) mergeMonitorOptionAsRow(opt)
  }
}

const onMonitorSelectSearch = useDebounceFn(async (inputValue: string) => {
  const q = (inputValue || '').trim()
  dropdownLoading.value = true
  try {
    if (!q) {
      syncOptionsFromList()
      return
    }
    const res: any = await fetchStorageMonitorList({ keyword: q, limit: 200 })
    if (res.code === 0 && res.details?.data) {
      storageOptions.value = res.details.data
    }
  } catch {
    /* ignore */
  } finally {
    dropdownLoading.value = false
  }
}, 350)

async function loadStorageList() {
  try {
    const res: any = await fetchStorageList({ page: 1, size: 100 })
    if (res.code !== 0) {
      Message.error(res.message || '加载存储列表失败')
      storageList.value = []
      storageOptions.value = []
      return
    }
    const page = res.details ?? res
    const list: StorageItem[] = page?.data ?? []
    storageList.value = list
    syncOptionsFromList()
    if (selectedServiceIdentity.value && !list.some((s) => s.service_identity === selectedServiceIdentity.value)) {
      selectedServiceIdentity.value = undefined
    }
    if (!selectedServiceIdentity.value && list.length > 0) {
      selectedServiceIdentity.value = list[0].service_identity
    }
  } catch (e: any) {
    Message.error(e?.message || '加载存储列表失败')
    storageList.value = []
    storageOptions.value = []
  }
}

async function refreshCollectedMetricNames() {
  trendMetricOptions.value = []
  const sid = selectedServiceIdentity.value?.trim()
  if (!sid) {
    trendSelectedMetric.value = ''
    return
  }
  metricsOptionsLoading.value = true
  try {
    type MRow = { metric_name: string; metric_unit?: string }
    let list: MRow[] = []
    const devId = activeStorage.value?.id
    if (devId && devId > 0) {
      const res: any = await fetchStorageMonitorCollectedMetrics(devId)
      if (res.code === 0 && res.details?.metrics?.length) {
        list = res.details.metrics
      }
    }
    if (!list.length) {
      const res: any = await fetchStorageMetricsLatest(sid)
      if (res.code === 0 && res.details?.metrics?.length) {
        list = res.details.metrics
      }
    }
    trendMetricOptions.value = list.map((m) => ({
      value: m.metric_name,
      label: m.metric_unit ? `${m.metric_name} (${m.metric_unit})` : m.metric_name,
    }))
    const names = new Set(list.map((m) => m.metric_name))
    const cur = trendSelectedMetric.value.trim()
    if (cur && names.has(cur)) {
      // 保持用户选择
    } else if (names.has('read_iops')) {
      trendSelectedMetric.value = 'read_iops'
    } else if (list.length) {
      trendSelectedMetric.value = list[0].metric_name
    } else {
      trendSelectedMetric.value = ''
    }
  } catch {
    trendMetricOptions.value = []
    trendSelectedMetric.value = ''
  } finally {
    metricsOptionsLoading.value = false
  }
}

async function refreshMonitorSummary() {
  summaryLoading.value = true
  try {
    const sid = selectedServiceIdentity.value?.trim() || undefined
    const res: any = await fetchStorageMonitorSummary(sid)
    if (res.code === 0) {
      monitorSummary.value = res.details ?? null
    } else {
      monitorSummary.value = null
    }
  } catch {
    monitorSummary.value = null
  } finally {
    summaryLoading.value = false
  }
}

/** 将原始 metrics 按自然小时桶取 metric_value 最大值，供折线图横轴为整点 */
function bucketStorageMetricsHourlyMax(metrics: Array<{ timestamp: string; metric_value: number }>): { time: string; value: number }[] {
  if (!metrics?.length) return []
  const sorted = [...metrics].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())
  const hourToMax = new Map<string, number>()
  for (const p of sorted) {
    const hourKey = dayjs(p.timestamp).startOf('hour').toISOString()
    const v = p.metric_value
    const prev = hourToMax.get(hourKey)
    hourToMax.set(hourKey, prev === undefined ? v : Math.max(prev, v))
  }
  return Array.from(hourToMax.entries())
    .sort((a, b) => new Date(a[0]).getTime() - new Date(b[0]).getTime())
    .map(([time, value]) => ({ time, value }))
}

async function refreshTimeseries() {
  chartSeriesPoints.value = []
  const svc = selectedServiceIdentity.value
  if (!svc) return
  const metric = trendSelectedMetric.value.trim()
  if (!metric) return
  const end = dayjs()
  const start = end.subtract(24, 'hour')
  const startStr = start.toISOString()
  const endStr = end.toISOString()
  chartLoading.value = true
  try {
    const q = { start_time: startStr, end_time: endStr, metric_name: metric }
    const r: any = await fetchStorageMetricsTimeseries(svc, q)
    if (r.code === 0 && r.details?.metrics?.length) {
      chartSeriesPoints.value = bucketStorageMetricsHourlyMax(r.details.metrics)
    }
  } catch {
    chartSeriesPoints.value = []
  } finally {
    chartLoading.value = false
  }
}

async function refreshSelectionDetails() {
  await refreshCollectedMetricNames()
  await Promise.all([refreshMonitorSummary(), refreshTimeseries()])
}

watch(selectedServiceIdentity, () => {
  void refreshSelectionDetails()
})

onMounted(async () => {
  await loadStorageList()
  pollTimer = setInterval(() => {
    void loadStorageList().then(() => {
      void refreshSelectionDetails()
    })
  }, 30000)
})

onUnmounted(() => {
  if (pollTimer) {
    clearInterval(pollTimer)
    pollTimer = null
  }
})
</script>

<script lang="ts">
export default {
  name: 'StorageMonitor',
}
</script>

<style scoped lang="less">
.container {
  padding: 16px;
}

.page-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}

.toolbar-left {
  display: flex;
  align-items: center;
  gap: 12px;
}

.toolbar-label {
  font-size: 14px;
  color: var(--color-text-2);
  white-space: nowrap;
}

.server-select {
  min-width: 260px;
  max-width: 400px;
}

.server-option {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
}

.server-option-name {
  font-size: 14px;
  color: var(--color-text-1);
}

.server-option-ip {
  font-size: 12px;
  color: var(--color-text-3);
  font-family: var(--font-family-mono);
}

.toolbar-meta {
  font-size: 13px;
}

.global-hint {
  margin-bottom: 12px;
  font-size: 12px;
}

.text-muted {
  color: var(--color-text-3);
  font-size: 12px;
}

.stats-row {
  margin-bottom: 16px;
}

.stats-card {
  height: 100%;

  :deep(.arco-card-body) {
    padding: 16px;
  }

  .stats-content {
    display: flex;
    align-items: flex-start;
    gap: 12px;
  }

  .stats-icon {
    width: 48px;
    height: 48px;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 24px;

    &-primary {
      background-color: rgba(22, 93, 255, 0.1);
      color: rgb(var(--primary-6));
    }

    &-cyan {
      background-color: rgba(20, 201, 201, 0.1);
      color: #14c9c9;
    }

    &-green {
      background-color: rgba(0, 180, 42, 0.1);
      color: rgb(var(--success-6));
    }

    &-purple {
      background-color: rgba(114, 46, 209, 0.1);
      color: #722ed1;
    }

    &-success {
      background-color: rgba(0, 180, 42, 0.12);
      color: rgb(var(--success-6));
    }

    &-warning {
      background-color: rgba(255, 125, 0, 0.12);
      color: rgb(var(--warning-6));
    }
  }

  .stats-info {
    flex: 1;
    min-width: 0;
  }

  .stats-title {
    font-size: 14px;
    color: var(--color-text-3);
    margin-bottom: 4px;
  }

  .stats-value {
    font-size: 22px;
    font-weight: 600;
    color: var(--color-text-1);
    line-height: 1.2;
    margin-bottom: 4px;
  }

  .stats-desc {
    font-size: 12px;
    color: var(--color-text-3);
    line-height: 1.5;
  }
}

.chart-row {
  margin-bottom: 16px;
}

.chart-row-equal {
  align-items: stretch;

  > .arco-col {
    display: flex;
  }
}

.chart-pair-col {
  display: flex;
  min-height: 400px;
}

.chart-pair-card {
  flex: 1;
  width: 100%;
  display: flex;
  flex-direction: column;
  min-height: 400px;

  :deep(.arco-card-body) {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
  }
}

.detail-side-card :deep(.arco-card-body) {
  overflow-y: auto;
}

.trend-metric-select {
  min-width: 200px;
  max-width: 280px;
}

.card-header {
  .card-title {
    font-size: 14px;
    font-weight: 500;
    color: var(--color-text-1);
  }

  .card-subtitle {
    font-size: 12px;
    color: var(--color-text-3);
    margin-top: 2px;
  }
}

.chart-spin {
  width: 100%;
  min-height: 280px;
}

.chart-spin--fill {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
}

.chart-content-wrapper {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  position: relative;
}

.chart-hint {
  margin-bottom: 8px;
  flex-shrink: 0;
}

.chart-container {
  min-height: 280px;
}

.chart-container--line {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.chart-container--line :deep(.chart-container-inner) {
  flex: 1;
  min-height: 0;
}

.chart-container--line :deep(.vue-echarts) {
  flex: 1;
  min-height: 0;
}

.collect-result {
  word-break: break-all;
  font-size: 12px;
}
</style>
