DSDSWeb/src/views/statistics/index.vue

830 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="main-dashboard">
<div class="header">
<h1>海洋科考数据统计分析</h1>
<p>全面展示各类科考设备与平台的运行数据统计</p>
</div>
<el-row :gutter="20" class="stats-grid">
<!-- 科考船汇总统计 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
class="stat-card stat-card--nav stat-card--ship"
shadow="never"
@click.native="goToDetail('ships')"
>
<div class="stat-card__inner">
<div class="stat-card__top">
<div class="stat-card__icon-wrap" aria-hidden="true">
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 18h18v2H3v-2zm1-2h16l-2-9H6l-2 9z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
<path d="M8 7V5a2 2 0 012-2h4a2 2 0 012 2v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</div>
<div class="stat-card__head-text">
<h3 class="stat-card__title">科考船汇总统计</h3>
<p class="stat-card__meta">
<span class="stat-card__badge">{{ ship.length }} 艘在册</span>
</p>
</div>
</div>
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
<div v-for="(m, idx) in shipMetrics" :key="'s'+idx" class="stat-metric">
<span class="stat-metric__value">{{ m.display }}</span>
<span class="stat-metric__label">{{ m.label }}</span>
</div>
</div>
<p class="stat-card__fleet" :title="shipNamesJoined">{{ shipNamesJoined }}</p>
</div>
</el-card>
</el-col>
<!-- 载人潜水器汇总统计 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
class="stat-card stat-card--nav stat-card--hov"
shadow="never"
@click.native="goToDetail('hov')"
>
<div class="stat-card__inner">
<div class="stat-card__top">
<div class="stat-card__icon-wrap" aria-hidden="true">
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<ellipse cx="12" cy="12" rx="7" ry="4" stroke="currentColor" stroke-width="1.5" />
<path d="M5 12h14M12 8v8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
<circle cx="12" cy="12" r="2" stroke="currentColor" stroke-width="1.5" />
</svg>
</div>
<div class="stat-card__head-text">
<h3 class="stat-card__title">载人潜水器汇总统计</h3>
<p class="stat-card__meta">
<span class="stat-card__badge">{{ hov.length }} 艘在册</span>
</p>
</div>
</div>
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
<div v-for="(m, idx) in platformMetrics(hovSummary)" :key="'h'+idx" class="stat-metric">
<span class="stat-metric__value">{{ m.display }}</span>
<span class="stat-metric__label">{{ m.label }}</span>
</div>
</div>
<p class="stat-card__fleet" :title="hovNamesJoined">{{ hovNamesJoined }}</p>
</div>
</el-card>
</el-col>
<!-- 遥控潜水器汇总统计 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
class="stat-card stat-card--nav stat-card--rov"
shadow="never"
@click.native="goToDetail('rov')"
>
<div class="stat-card__inner">
<div class="stat-card__top">
<div class="stat-card__icon-wrap" aria-hidden="true">
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="5" y="9" width="14" height="10" rx="2" stroke="currentColor" stroke-width="1.5" />
<path d="M9 14h6M12 11v6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
<path d="M19 13v4M3 13v4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</div>
<div class="stat-card__head-text">
<h3 class="stat-card__title">遥控潜水器汇总统计</h3>
<p class="stat-card__meta">
<span class="stat-card__badge">{{ rov.length }} 艘在册</span>
</p>
</div>
</div>
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
<div v-for="(m, idx) in platformMetrics(rovSummary)" :key="'r'+idx" class="stat-metric">
<span class="stat-metric__value">{{ m.display }}</span>
<span class="stat-metric__label">{{ m.label }}</span>
</div>
</div>
<p class="stat-card__fleet" :title="rovNamesJoined">{{ rovNamesJoined }}</p>
</div>
</el-card>
</el-col>
<!-- 水下自主式无人潜水器汇总统计 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
class="stat-card stat-card--nav stat-card--uuv"
shadow="never"
@click.native="goToDetail('auv')"
>
<div class="stat-card__inner">
<div class="stat-card__top">
<div class="stat-card__icon-wrap" aria-hidden="true">
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 12c2-4 6-6 8-6s6 2 8 6c-2 4-6 6-8 6s-6-2-8-6z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
<circle cx="12" cy="12" r="2" stroke="currentColor" stroke-width="1.5" />
</svg>
</div>
<div class="stat-card__head-text">
<h3 class="stat-card__title">水下自主式无人潜水器汇总统计</h3>
<p class="stat-card__meta">
<span class="stat-card__badge">{{ uuv.length }} 艘在册</span>
</p>
</div>
</div>
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
<div v-for="(m, idx) in platformMetrics(uuvSummary)" :key="'u'+idx" class="stat-metric">
<span class="stat-metric__value">{{ m.display }}</span>
<span class="stat-metric__label">{{ m.label }}</span>
</div>
</div>
<p class="stat-card__fleet" :title="uuvNamesJoined">{{ uuvNamesJoined }}</p>
</div>
</el-card>
</el-col>
<!-- 参航单位统计(无人航行潜水器) -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
class="stat-card stat-card--nav stat-card--uuv-unit"
shadow="never"
@click.native="goToDetail('uuvUnit')"
>
<div class="stat-card__inner">
<div class="stat-card__top">
<div class="stat-card__icon-wrap" aria-hidden="true">
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 8l9-5 9 5-9 5-9-5z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
<path d="M3 8v8l9 5 9-5V8" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
</svg>
</div>
<div class="stat-card__head-text">
<h3 class="stat-card__title">参航单位统计</h3>
<p class="stat-card__meta">
<span class="stat-card__badge">{{ uuvUnitBadge }} 家</span>
</p>
</div>
</div>
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
<div v-for="(m, idx) in uuvUnitCardMetrics" :key="'uu'+idx" class="stat-metric">
<span class="stat-metric__value">{{ m.display }}</span>
<span class="stat-metric__label">{{ m.label }}</span>
</div>
</div>
</div>
</el-card>
</el-col>
<!-- 参行科学家统计(无人航行潜水器) -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
class="stat-card stat-card--nav stat-card--uuv-scientist"
shadow="never"
@click.native="goToDetail('uuvMember')"
>
<div class="stat-card__inner">
<div class="stat-card__top">
<div class="stat-card__icon-wrap" aria-hidden="true">
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9 21h6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
<path d="M10 17l-2-2 2-6h4l2 6-2 2" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
<path d="M9 9V7a3 3 0 016 0v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</div>
<div class="stat-card__head-text">
<h3 class="stat-card__title">参航科学家统计</h3>
<p class="stat-card__meta">
<span class="stat-card__badge">{{ uuvScientistBadge }} 人</span>
</p>
</div>
</div>
<div class="stat-metrics" :class="{ 'stat-metrics--loading': summaryLoading }">
<div v-for="(m, idx) in uuvScientistMetrics" :key="'ms'+idx" class="stat-metric">
<span class="stat-metric__value">{{ m.display }}</span>
<span class="stat-metric__label">{{ m.label }}</span>
</div>
</div>
</div>
</el-card>
</el-col>
<!-- 基站汇总统计 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card class="stat-card stat-card--placeholder stat-card--station" shadow="never">
<div class="stat-card__inner">
<div class="stat-card__top">
<div class="stat-card__icon-wrap" aria-hidden="true">
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 3l8 4v6c0 5-3.5 9-8 10-4.5-1-8-5-8-10V7l8-4z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round" />
<path d="M12 8v5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
</div>
<div class="stat-card__head-text">
<h3 class="stat-card__title">基站汇总统计</h3>
<p class="stat-card__meta">
<span class="stat-card__badge stat-card__badge--muted">敬请期待</span>
</p>
</div>
</div>
<div class="stat-placeholder">
<span class="stat-placeholder__text">暂无数据</span>
</div>
</div>
</el-card>
</el-col>
<!-- 着陆器汇总统计 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card class="stat-card stat-card--placeholder stat-card--lander" shadow="never">
<div class="stat-card__inner">
<div class="stat-card__top">
<div class="stat-card__icon-wrap" aria-hidden="true">
<svg class="stat-card__icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 21s7-4.5 7-11a7 7 0 10-14 0c0 6.5 7 11 7 11z" stroke="currentColor" stroke-width="1.5" />
<circle cx="12" cy="10" r="2.5" stroke="currentColor" stroke-width="1.5" />
</svg>
</div>
<div class="stat-card__head-text">
<h3 class="stat-card__title">着陆器汇总统计</h3>
<p class="stat-card__meta">
<span class="stat-card__badge stat-card__badge--muted">敬请期待</span>
</p>
</div>
</div>
<div class="stat-placeholder">
<span class="stat-placeholder__text">暂无数据</span>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
import {
reportList,
shipTotal,
platformHov,
platformRov,
platformAuv,
hovUnit,
unitMember,
unitTotal
} from '../../api/statistics'
import { filterDictItems } from '../../utils/index'
/**
* 与 UuvMemberStatistics 默认统计单位一致
* @type {string}
*/
const DEFAULT_MEMBER_STAT_UNIT_NAME = '中国科学院深海科学与工程研究所'
/**
* @param {*} v 原始值
* @returns {number}
*/
function num(v) {
if (v == null || v === '') return 0
const n = Number(v)
return Number.isFinite(n) ? n : 0
}
/**
* 将 unitTotal 按单位行聚合为卡片用汇总(与 UuvUnitStatistics 全量口径一致)。
* @param {Object[]} list - report/unit/total.htm 的 array
* @returns {{ unitCount: number, voyageSum: number, daysSum: number, memberSum: number, drvingSum: number }|null}
*/
function aggregateUnitTotalForCard(list) {
if (!Array.isArray(list) || !list.length) return null
return list.reduce(
(acc, row) => ({
unitCount: acc.unitCount + 1,
voyageSum: acc.voyageSum + num(row.voyage_total),
daysSum: acc.daysSum + num(row.voyage_days),
memberSum: acc.memberSum + num(row.member_total),
drvingSum: acc.drvingSum + num(row.drving_total)
}),
{ unitCount: 0, voyageSum: 0, daysSum: 0, memberSum: 0, drvingSum: 0 }
)
}
/**
* 统计分析入口:展示各平台类型入口卡片及与子页一致的汇总指标
*/
export default {
name: 'MainDashboard',
data() {
return {
ship: [],
hov: [],
rov: [],
uuv: [],
/** 科考船汇总(与 ShipStatistics 顶部 banner 同源) */
shipSummary: null,
/** 载人潜水器汇总(与 HovStatistics 同源接口) */
hovSummary: null,
/** 遥控潜水器汇总 */
rovSummary: null,
/** 无人航行潜水器汇总 */
uuvSummary: null,
/** 参航科学家卡片unitMember 聚合(默认单位) */
memberScientistCard: null,
/** 参航单位统计卡片unitTotal 全表聚合(与 UuvUnitStatistics 同源) */
uuvUnitCardAgg: null,
summaryLoading: false
}
},
computed: {
shipNamesJoined() {
return this.joinReportNames(this.ship)
},
hovNamesJoined() {
return this.joinReportNames(this.hov)
},
rovNamesJoined() {
return this.joinReportNames(this.rov)
},
uuvNamesJoined() {
return this.joinReportNames(this.uuv)
},
/** 科考船四项核心指标展示行 */
shipMetrics() {
const s = this.shipSummary
if (!s) {
return [
{ label: '航次', display: '—' },
{ label: '航次天数', display: '—' },
{ label: '参航单位', display: '—' },
{ label: '数据样品', display: '—' }
]
}
return [
{ label: '航次', display: this.formatMetric(s.voyage_total) + ' 次' },
{ label: '航次天数', display: this.formatMetric(s.days_total) + ' 天' },
{ label: '参航单位', display: this.formatMetric(s.unit_total) + ' 家' },
{ label: '数据样品', display: this.formatMetric(s.dataset_total) + ' 个' }
]
},
/** 无人航行潜水器:参航单位数量(汇总接口兜底) */
uuvUnitValue() {
if (!this.uuvSummary) return '—'
return this.formatMetric(this.uuvSummary.unit_total)
},
/** 参航单位卡片角标:优先 unitTotal 家数,否则 platformAuv 汇总 */
uuvUnitBadge() {
if (this.uuvUnitCardAgg != null) {
return this.formatMetric(this.uuvUnitCardAgg.unitCount)
}
return this.uuvUnitValue
},
/**
* 参航单位统计卡片四项指标(与参航单位详情页 Banner/汇总一致)
* @returns {Array<{ label: string, display: string }>}
*/
uuvUnitCardMetrics() {
const a = this.uuvUnitCardAgg
if (!a) {
return [
{ label: '参航单位', display: '—' },
{ label: '参航人数', display: '—' },
{ label: '深潜次数', display: '—' },
{ label: '航行次数', display: '—' }
]
}
return [
{ label: '参航单位', display: this.formatMetric(a.unitCount) + ' 家' },
{ label: '参航人数', display: this.formatMetric(a.memberSum) + ' 人' },
{ label: '深潜次数', display: this.formatMetric(a.drvingSum) + ' 次' },
{ label: '航行次数', display: this.formatMetric(a.voyageSum) + ' 次' }
]
},
/** 无人航行潜水器:参航科学家数量(汇总接口兜底) */
uuvScientistValue() {
if (!this.uuvSummary) return '—'
return this.formatMetric(this.uuvSummary.driver_member_total)
},
/** 卡片角标:优先 unitMember 科学家人数,否则汇总接口人数 */
uuvScientistBadge() {
if (this.memberScientistCard && this.memberScientistCard.scientistCount != null) {
return this.formatMetric(this.memberScientistCard.scientistCount)
}
return this.uuvScientistValue
},
/**
* 参航科学家卡片三项指标
* @returns {Array<{ label: string, display: string }>}
*/
uuvScientistMetrics() {
const c = this.memberScientistCard
if (!c) {
return [
{ label: '科学家人数', display: '—' },
{ label: '累计参加航次', display: '—' },
{ label: '累计参加人·天', display: '—' }
]
}
return [
{ label: '科学家人数', display: this.formatMetric(c.scientistCount) + ' 人' },
{ label: '累计参加航次', display: this.formatMetric(c.sumVoyage) + ' 次' },
{ label: '累计参加人·天', display: this.formatMetric(c.sumDays) + ' 人·天' }
]
}
},
created() {
this.getTypeList()
this.loadSummaries()
},
methods: {
goToDetail(type) {
this.$router.push(`/container/${type}`)
},
getTypeList() {
reportList().then(res => {
this.ship = filterDictItems(res.array, '科考船')
this.hov = filterDictItems(res.array, '载人潜水器')
this.rov = filterDictItems(res.array, '遥控潜水器')
this.auv = filterDictItems(res.array, '无人航行潜水器')
})
},
/**
* 并行拉取各子页汇总接口,用于卡片内核心指标展示
*/
loadSummaries() {
this.summaryLoading = true
const pMemberCard = hovUnit()
.then(res => {
const list = res.array || []
const unit =
list.find(u => u.unit_name === DEFAULT_MEMBER_STAT_UNIT_NAME) || list[0]
if (!unit) return null
const key = unit.unit_id != null && unit.unit_id !== '' ? unit.unit_id : unit.unit_name
return unitMember({ unit: key })
})
.then(memberRes => {
if (!memberRes || !Array.isArray(memberRes.array)) return null
const arr = memberRes.array
return {
scientistCount: arr.length,
sumVoyage: arr.reduce((s, m) => s + num(m.voyage_total), 0),
sumDays: arr.reduce((s, m) => s + num(m.days_total), 0)
}
})
.catch(() => null)
const pUnitTotalCard = unitTotal({})
.then(res => aggregateUnitTotalForCard(res && res.array ? res.array : []))
.catch(() => null)
Promise.all([
shipTotal({}),
platformHov({}),
platformRov({}),
platformAuv({}),
pMemberCard,
pUnitTotalCard
])
.then(([shipRes, hovRes, rovRes, uuvRes, memberAgg, unitAgg]) => {
this.shipSummary = (shipRes.array && shipRes.array[0]) || null
this.hovSummary = (hovRes.array && hovRes.array[0]) || null
this.rovSummary = (rovRes.array && rovRes.array[0]) || null
this.uuvSummary = (uuvRes.array && uuvRes.array[0]) || null
this.memberScientistCard = memberAgg
this.uuvUnitCardAgg = unitAgg
})
.catch(() => {
this.shipSummary = null
this.hovSummary = null
this.rovSummary = null
this.uuvSummary = null
this.memberScientistCard = null
this.uuvUnitCardAgg = null
})
.finally(() => {
this.summaryLoading = false
})
},
joinReportNames(list) {
if (!list || !list.length) return '—'
return list.map(i => i.report_name).join('、')
},
/**
* 载人/遥控/无人平台卡片指标(与子页 banner 字段一致)
* @param {object|null} row - 接口 array[0]
* @returns {Array<{ label: string, display: string }>}
*/
platformMetrics(row) {
if (!row) {
return [
{ label: '潜次数', display: '—' },
{ label: '下潜人数', display: '—' },
{ label: '下潜单位', display: '—' },
{ label: '数据样品', display: '—' }
]
}
return [
{ label: '潜次数', display: this.formatMetric(row.diving_total) + ' 次' },
{ label: '下潜人数', display: this.formatMetric(row.driver_member_total) + ' 人' },
{ label: '下潜单位', display: this.formatMetric(row.unit_total) + ' 家' },
{ label: '数据样品', display: this.formatMetric(row.dataset_total) + ' 个' }
]
},
/**
* @param {string|number|null|undefined} v
* @returns {string}
*/
formatMetric(v) {
if (v === null || v === undefined || v === '') return '—'
return String(v)
}
}
}
</script>
<style scoped lang="scss">
.main-dashboard {
padding: 24px 20px 32px;
min-height: calc(100vh - 130px);
background: #e9eef4;
}
.header {
text-align: center;
margin-bottom: 28px;
}
.header h1 {
color: #0f172a;
font-size: 28px;
margin: 0 0 10px;
font-weight: 600;
letter-spacing: 0.02em;
}
.header p {
color: #475569;
font-size: 16px;
margin: 0;
line-height: 1.5;
}
.stats-grid {
margin: 0 auto;
}
.stat-card {
margin-bottom: 20px;
border-radius: 12px;
border: 1px solid #e2e8f0;
background: #fff;
min-height: 280px;
transition:
box-shadow 0.22s ease,
border-color 0.22s ease,
transform 0.22s ease;
::v-deep .el-card__body {
padding: 0;
height: 100%;
}
}
.stat-card--nav {
cursor: pointer;
&:hover {
transform: translateY(-3px);
border-color: #cbd5e1;
box-shadow: 0 14px 32px -12px rgba(15, 23, 42, 0.18);
}
}
.stat-card--placeholder {
cursor: default;
opacity: 0.92;
&:hover {
transform: none;
box-shadow: none;
border-color: #e2e8f0;
}
}
.stat-card__inner {
padding: 18px 18px 16px;
position: relative;
min-height: 280px;
display: flex;
flex-direction: column;
}
/* 移除顶部描边border-top */
.stat-card__top {
display: flex;
align-items: flex-start;
gap: 14px;
margin-bottom: 4px;
}
.stat-card__icon-wrap {
width: 60px;
height: 60px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.stat-card--ship .stat-card__icon-wrap {
background: #eff6ff;
color: #1d4ed8;
}
.stat-card--hov .stat-card__icon-wrap {
background: #f0fdfa;
color: #0d9488;
}
.stat-card--rov .stat-card__icon-wrap {
background: #f5f3ff;
color: #6d28d9;
}
.stat-card--uuv .stat-card__icon-wrap {
background: #fffbeb;
color: #b45309;
}
.stat-card--uuv-unit .stat-card__icon-wrap {
background: #eff6ff;
color: #1d4ed8;
}
.stat-card--uuv-scientist .stat-card__icon-wrap {
background: #f5f3ff;
color: #6d28d9;
}
.stat-card--station .stat-card__icon-wrap,
.stat-card--lander .stat-card__icon-wrap {
background: #f1f5f9;
color: #64748b;
}
.stat-card__icon {
width: 32px;
height: 32px;
}
.stat-card__head-text {
flex: 1;
min-width: 0;
}
.stat-card__title {
font-size: 18px;
font-weight: 600;
color: #0f172a;
margin: 0 0 6px;
line-height: 1.35;
}
.stat-card__meta {
margin: 0;
}
.stat-card__badge {
display: inline-block;
font-size: 13px;
font-weight: 500;
color: #475569;
padding: 2px 8px;
border-radius: 6px;
background: #f1f5f9;
letter-spacing: 0.02em;
}
.stat-card__badge--muted {
color: #94a3b8;
background: #f8fafc;
}
.stat-metrics {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: 14px;
flex: 1;
}
.stat-metrics--loading .stat-metric {
opacity: 0.55;
}
.stat-metric {
padding: 12px 14px;
border-radius: 8px;
background: #f8fafc;
border: 1px solid #f1f5f9;
display: flex;
flex-direction: column;
gap: 4px;
}
.stat-metric__value {
font-size: 20px;
font-weight: 700;
color: #0f172a;
line-height: 1.2;
font-variant-numeric: tabular-nums;
}
.stat-metric__label {
font-size: 13px;
font-weight: 500;
color: #64748b;
letter-spacing: 0.02em;
}
.stat-card__fleet {
margin: 12px 0 0;
font-size: 12px;
color: #64748b;
line-height: 1.45;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.stat-placeholder {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
min-height: 160px;
margin-top: 8px;
border-radius: 8px;
border: 1px dashed #cbd5e1;
background: #f8fafc;
}
.stat-placeholder__text {
font-size: 14px;
font-weight: 500;
color: #94a3b8;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.stat-card {
animation: fadeInUp 0.45s ease forwards;
}
.el-col:nth-child(1) .stat-card {
animation-delay: 0.05s;
}
.el-col:nth-child(2) .stat-card {
animation-delay: 0.1s;
}
.el-col:nth-child(3) .stat-card {
animation-delay: 0.15s;
}
.el-col:nth-child(4) .stat-card {
animation-delay: 0.2s;
}
.el-col:nth-child(5) .stat-card {
animation-delay: 0.25s;
}
.el-col:nth-child(6) .stat-card {
animation-delay: 0.3s;
}
.el-col:nth-child(7) .stat-card {
animation-delay: 0.35s;
}
.el-col:nth-child(8) .stat-card {
animation-delay: 0.4s;
}
@media (max-width: 768px) {
.stat-card {
min-height: auto;
}
.stat-card__inner {
min-height: auto;
}
}
</style>