DSDSWeb/src/views/statistics/index.vue

830 lines
26 KiB
Vue
Raw Normal View History

<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
2026-04-03 02:05:29 +08:00
class="stat-card stat-card--nav stat-card--ship"
shadow="never"
@click.native="goToDetail('ships')"
>
2026-04-03 02:05:29 +08:00
<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>
2026-04-03 02:05:29 +08:00
<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>
2026-04-03 02:05:29 +08:00
<p class="stat-card__fleet" :title="shipNamesJoined">{{ shipNamesJoined }}</p>
</div>
</el-card>
</el-col>
<!-- 载人潜水器汇总统计 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
2026-04-03 02:05:29 +08:00
class="stat-card stat-card--nav stat-card--hov"
shadow="never"
@click.native="goToDetail('hov')"
>
2026-04-03 02:05:29 +08:00
<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>
2026-04-03 02:05:29 +08:00
<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>
2026-04-03 02:05:29 +08:00
<p class="stat-card__fleet" :title="hovNamesJoined">{{ hovNamesJoined }}</p>
</div>
</el-card>
</el-col>
<!-- 遥控潜水器汇总统计 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
2026-04-03 02:05:29 +08:00
class="stat-card stat-card--nav stat-card--rov"
shadow="never"
@click.native="goToDetail('rov')"
>
2026-04-03 02:05:29 +08:00
<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>
2026-04-03 02:05:29 +08:00
<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>
2026-04-03 02:05:29 +08:00
<p class="stat-card__fleet" :title="rovNamesJoined">{{ rovNamesJoined }}</p>
</div>
</el-card>
</el-col>
2026-04-03 14:14:42 +08:00
<!-- 水下自主式无人潜水器汇总统计 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
2026-04-03 02:05:29 +08:00
class="stat-card stat-card--nav stat-card--uuv"
shadow="never"
@click.native="goToDetail('auv')"
>
2026-04-03 02:05:29 +08:00
<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">
2026-04-03 14:14:42 +08:00
<h3 class="stat-card__title">水下自主式无人潜水器汇总统计</h3>
2026-04-03 02:05:29 +08:00
<p class="stat-card__meta">
<span class="stat-card__badge">{{ uuv.length }} 艘在册</span>
</p>
</div>
</div>
2026-04-03 02:05:29 +08:00
<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>
2026-04-03 02:05:29 +08:00
<p class="stat-card__fleet" :title="uuvNamesJoined">{{ uuvNamesJoined }}</p>
</div>
</el-card>
</el-col>
2026-04-03 02:05:29 +08:00
<!-- 参航单位统计无人航行潜水器 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
2026-04-03 02:05:29 +08:00
class="stat-card stat-card--nav stat-card--uuv-unit"
shadow="never"
@click.native="goToDetail('uuvUnit')"
>
2026-04-03 02:05:29 +08:00
<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>
2026-04-03 02:05:29 +08:00
<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>
2026-04-07 08:27:40 +08:00
</div>
</el-card>
</el-col>
2026-04-03 02:05:29 +08:00
<!-- 参行科学家统计无人航行潜水器 -->
<el-col :xs="24" :sm="12" :md="8">
<el-card
2026-04-03 02:05:29 +08:00
class="stat-card stat-card--nav stat-card--uuv-scientist"
shadow="never"
@click.native="goToDetail('uuvMember')"
>
2026-04-03 02:05:29 +08:00
<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>
2026-04-07 08:27:40 +08:00
2026-04-03 02:05:29 +08:00
</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>
2026-04-03 02:05:29 +08:00
<div class="stat-placeholder">
<span class="stat-placeholder__text">暂无数据</span>
</div>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script>
2026-04-03 02:05:29 +08:00
import {
reportList,
shipTotal,
platformHov,
platformRov,
platformAuv,
hovUnit,
unitMember,
unitTotal
} from '../../api/statistics'
2025-10-31 16:15:28 +08:00
import { filterDictItems } from '../../utils/index'
2026-04-03 02:05:29 +08:00
/**
* 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 {
2025-10-31 16:15:28 +08:00
ship: [],
hov: [],
rov: [],
2026-04-03 02:05:29 +08:00
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.member_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
},
/**
2026-04-07 08:27:40 +08:00
* 参航科学家卡片三项指标
2026-04-03 02:05:29 +08:00
* @returns {Array<{ label: string, display: string }>}
*/
uuvScientistMetrics() {
const c = this.memberScientistCard
if (!c) {
return [
{ label: '科学家人数', display: '—' },
{ label: '累计参加航次', display: '—' },
2026-04-07 08:27:40 +08:00
{ label: '累计参加人·天', display: '—' }
2026-04-03 02:05:29 +08:00
]
}
return [
{ label: '科学家人数', display: this.formatMetric(c.scientistCount) + ' 人' },
{ label: '累计参加航次', display: this.formatMetric(c.sumVoyage) + ' 次' },
2026-04-07 08:27:40 +08:00
{ label: '累计参加人·天', display: this.formatMetric(c.sumDays) + ' 人·天' }
2026-04-03 02:05:29 +08:00
]
}
},
2025-10-31 16:15:28 +08:00
created() {
this.getTypeList()
2026-04-03 02:05:29 +08:00
this.loadSummaries()
2025-10-31 16:15:28 +08:00
},
methods: {
goToDetail(type) {
2026-04-03 02:05:29 +08:00
this.$router.push(`/container/${type}`)
2025-10-31 16:15:28 +08:00
},
getTypeList() {
reportList().then(res => {
this.ship = filterDictItems(res.array, '科考船')
this.hov = filterDictItems(res.array, '载人潜水器')
this.rov = filterDictItems(res.array, '遥控潜水器')
this.auv = filterDictItems(res.array, '无人航行潜水器')
2025-10-31 16:15:28 +08:00
})
},
2026-04-03 02:05:29 +08:00
/**
* 并行拉取各子页汇总接口用于卡片内核心指标展示
*/
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>
2026-04-03 02:05:29 +08:00
<style scoped lang="scss">
.main-dashboard {
2026-04-03 02:05:29 +08:00
padding: 24px 20px 32px;
min-height: calc(100vh - 130px);
2026-04-03 02:05:29 +08:00
background: #e9eef4;
}
.header {
text-align: center;
2026-04-03 02:05:29 +08:00
margin-bottom: 28px;
}
.header h1 {
2026-04-03 02:05:29 +08:00
color: #0f172a;
font-size: 28px;
2026-04-03 02:05:29 +08:00
margin: 0 0 10px;
font-weight: 600;
2026-04-03 02:05:29 +08:00
letter-spacing: 0.02em;
}
.header p {
2026-04-03 02:05:29 +08:00
color: #475569;
font-size: 16px;
2026-04-03 02:05:29 +08:00
margin: 0;
line-height: 1.5;
}
.stats-grid {
margin: 0 auto;
}
.stat-card {
margin-bottom: 20px;
2026-04-03 02:05:29 +08:00
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;
2026-04-03 02:05:29 +08:00
&: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;
}
}
2026-04-03 02:05:29 +08:00
.stat-card__inner {
padding: 18px 18px 16px;
position: relative;
min-height: 280px;
display: flex;
flex-direction: column;
}
2026-04-03 02:05:29 +08:00
/* 移除顶部描边border-top */
.stat-card__top {
display: flex;
2026-04-03 02:05:29 +08:00
align-items: flex-start;
gap: 14px;
margin-bottom: 4px;
}
2026-04-03 02:05:29 +08:00
.stat-card__icon-wrap {
width: 60px;
height: 60px;
border-radius: 14px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
2026-04-03 02:05:29 +08:00
.stat-card--ship .stat-card__icon-wrap {
background: #eff6ff;
color: #1d4ed8;
}
2026-04-03 02:05:29 +08:00
.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;
2026-04-03 02:05:29 +08:00
min-width: 0;
}
2026-04-03 02:05:29 +08:00
.stat-card__title {
font-size: 18px;
font-weight: 600;
2026-04-03 02:05:29 +08:00
color: #0f172a;
margin: 0 0 6px;
line-height: 1.35;
}
2026-04-03 02:05:29 +08:00
.stat-card__meta {
margin: 0;
}
2026-04-03 02:05:29 +08:00
.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;
}
2026-04-03 02:05:29 +08:00
.stat-card__badge--muted {
color: #94a3b8;
background: #f8fafc;
}
2026-04-03 02:05:29 +08:00
.stat-metrics {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 10px;
margin-top: 14px;
flex: 1;
}
2026-04-03 02:05:29 +08:00
.stat-metrics--loading .stat-metric {
opacity: 0.55;
}
2026-04-03 02:05:29 +08:00
.stat-metric {
padding: 12px 14px;
border-radius: 8px;
background: #f8fafc;
border: 1px solid #f1f5f9;
display: flex;
flex-direction: column;
gap: 4px;
}
2026-04-03 02:05:29 +08:00
.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;
2026-04-03 02:05:29 +08:00
transform: translateY(12px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.stat-card {
2026-04-03 02:05:29 +08:00
animation: fadeInUp 0.45s ease forwards;
}
2026-04-03 02:05:29 +08:00
.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>