736 lines
18 KiB
Vue
736 lines
18 KiB
Vue
<template>
|
||
<view class="page">
|
||
<scroll-view scroll-y class="page-scroll" refresher-enabled :refresher-triggered="refreshing" @refresherrefresh="handleRefresh">
|
||
<common-hero title="数据统计与报表中心" :use-image-bg="true"></common-hero>
|
||
<view class="hero-card">
|
||
<view class="hero-title">挑战杯数据统计</view>
|
||
<view class="hero-actions">
|
||
<picker class="picker-wrap" mode="selector" :range="yearOptionLabels" :value="selectedYearIndex" @change="handleYearChange">
|
||
<view class="action-pill">
|
||
<text>{{ selectedYear ? `${selectedYear} 年` : '全部年份' }}</text>
|
||
<text class="action-pill-arrow">▼</text>
|
||
</view>
|
||
</picker>
|
||
<view class="action-pill action-pill--ghost" :class="{ 'action-pill--loading': loading }" @tap="loadData()">
|
||
{{ loading ? '刷新中' : '刷新数据' }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-if="stats.awardCountDescription" class="alert-card">
|
||
<view class="alert-icon">i</view>
|
||
<view class="alert-text">{{ stats.awardCountDescription }}</view>
|
||
</view>
|
||
|
||
<view v-if="loading && !hasLoaded" class="state-card">
|
||
<view class="state-title">正在加载统计数据...</view>
|
||
<view class="state-desc">请稍候,正在同步挑战杯报表中心数据。</view>
|
||
</view>
|
||
|
||
<view v-else-if="loadError" class="state-card state-card--error">
|
||
<view class="state-title">加载失败</view>
|
||
<view class="state-desc">{{ loadError }}</view>
|
||
<view class="state-action" @tap="loadData()">重新加载</view>
|
||
</view>
|
||
|
||
<block v-else>
|
||
<view class="metrics-grid">
|
||
<view class="metric-card">
|
||
<view class="metric-label">申报作品数量</view>
|
||
<view class="metric-value">{{ summary.declarationCount }}</view>
|
||
<view class="metric-foot">当前筛选范围内的项目总数</view>
|
||
</view>
|
||
<view class="metric-card metric-card--accent">
|
||
<view class="metric-label">参与申报人数</view>
|
||
<view class="metric-value">{{ summary.participantCount }}</view>
|
||
<view class="metric-foot">按团队成员去重统计</view>
|
||
</view>
|
||
<view class="metric-card metric-card--warm">
|
||
<view class="metric-label">指导老师数量</view>
|
||
<view class="metric-value">{{ summary.advisorCount }}</view>
|
||
<view class="metric-foot">按指导老师去重统计</view>
|
||
</view>
|
||
<view class="metric-card metric-card--success">
|
||
<view class="metric-label">获奖数量</view>
|
||
<view class="metric-value">{{ summary.awardCount }}</view>
|
||
<view class="metric-foot">当前按审核通过作品数统计</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="mini-metrics">
|
||
<view class="mini-metric-card">
|
||
<text>申报学校数量</text>
|
||
<text class="mini-metric-value">{{ summary.schoolCount }}</text>
|
||
</view>
|
||
<view class="mini-metric-card">
|
||
<text>项目类型数</text>
|
||
<text class="mini-metric-value">{{ stats.typeDistribution.length }}</text>
|
||
</view>
|
||
<view class="mini-metric-card">
|
||
<text>项目分组数</text>
|
||
<text class="mini-metric-value">{{ stats.groupDistribution.length }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="section-card">
|
||
<view class="section-head">
|
||
<view>
|
||
<view class="section-title">年度申报趋势</view>
|
||
<view class="section-subtitle">展示各年度挑战杯作品申报数量变化</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="stats.yearDistribution.length" class="trend-list">
|
||
<view v-for="item in stats.yearDistribution" :key="item.label" class="trend-item">
|
||
<view class="trend-top">
|
||
<view class="trend-label">{{ item.label }}</view>
|
||
<view class="trend-value">{{ item.value }}</view>
|
||
</view>
|
||
<view class="bar-track">
|
||
<view class="bar-fill bar-fill--blue" :style="{ width: getBarWidth(item.value, maxYearValue) }"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty-card">暂无年度数据</view>
|
||
</view>
|
||
|
||
<view class="section-card">
|
||
<view class="section-head">
|
||
<view>
|
||
<view class="section-title">项目类型分布</view>
|
||
<view class="section-subtitle">按项目类型统计申报数量</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="stats.typeDistribution.length" class="distribution-list">
|
||
<view v-for="item in stats.typeDistribution" :key="item.label" class="distribution-item">
|
||
<view class="distribution-main">
|
||
<view class="distribution-label">{{ item.label }}</view>
|
||
<view class="distribution-value">{{ item.value }}</view>
|
||
</view>
|
||
<view class="bar-track">
|
||
<view class="bar-fill bar-fill--cyan" :style="{ width: getBarWidth(item.value, maxTypeValue) }"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty-card">暂无类型分布</view>
|
||
</view>
|
||
|
||
<view class="section-card">
|
||
<view class="section-head">
|
||
<view>
|
||
<view class="section-title">项目分组分布</view>
|
||
<view class="section-subtitle">按项目分组统计申报数量</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="stats.groupDistribution.length" class="distribution-list">
|
||
<view v-for="item in stats.groupDistribution" :key="item.label" class="distribution-item">
|
||
<view class="distribution-main">
|
||
<view class="distribution-label">{{ item.label }}</view>
|
||
<view class="distribution-value">{{ item.value }}</view>
|
||
</view>
|
||
<view class="bar-track">
|
||
<view class="bar-fill bar-fill--orange" :style="{ width: getBarWidth(item.value, maxGroupValue) }"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty-card">暂无分组分布</view>
|
||
</view>
|
||
|
||
<view class="section-card">
|
||
<view class="section-head">
|
||
<view>
|
||
<view class="section-title">学校申报排行</view>
|
||
<view class="section-subtitle">展示学校维度的申报数量排名</view>
|
||
</view>
|
||
</view>
|
||
<view v-if="stats.schoolDistribution.length" class="ranking-list">
|
||
<view v-for="(item, index) in stats.schoolDistribution" :key="item.label" class="ranking-item">
|
||
<view class="ranking-order">{{ index + 1 }}</view>
|
||
<view class="ranking-main">
|
||
<view class="distribution-main">
|
||
<view class="distribution-label">{{ item.label }}</view>
|
||
<view class="distribution-value">{{ item.value }}</view>
|
||
</view>
|
||
<view class="bar-track">
|
||
<view class="bar-fill bar-fill--green" :style="{ width: getBarWidth(item.value, maxSchoolValue) }"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view v-else class="empty-card">暂无学校排行</view>
|
||
</view>
|
||
|
||
<view class="section-card">
|
||
<view class="section-head">
|
||
<view>
|
||
<view class="section-title">分布明细</view>
|
||
<view class="section-subtitle">{{ selectedYear ? `${selectedYear} 年` : '全部年份' }}统计口径下的分类数量</view>
|
||
</view>
|
||
</view>
|
||
<view class="detail-grid">
|
||
<view class="detail-panel">
|
||
<view class="detail-title">项目类型</view>
|
||
<view v-if="stats.typeDistribution.length" class="detail-list">
|
||
<view v-for="item in stats.typeDistribution" :key="`type-${item.label}`" class="detail-row">
|
||
<text class="detail-label">{{ item.label }}</text>
|
||
<text class="detail-value">{{ item.value }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-else class="detail-empty">暂无数据</view>
|
||
</view>
|
||
<view class="detail-panel">
|
||
<view class="detail-title">项目分组</view>
|
||
<view v-if="stats.groupDistribution.length" class="detail-list">
|
||
<view v-for="item in stats.groupDistribution" :key="`group-${item.label}`" class="detail-row">
|
||
<text class="detail-label">{{ item.label }}</text>
|
||
<text class="detail-value">{{ item.value }}</text>
|
||
</view>
|
||
</view>
|
||
<view v-else class="detail-empty">暂无数据</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</block>
|
||
</scroll-view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import CommonHero from '../../components/common-hero/common-hero.vue'
|
||
import { getChallengeCupStatistics } from './stats-service'
|
||
|
||
const createInitialStats = () => ({
|
||
availableYears: [],
|
||
summary: {
|
||
declarationCount: 0,
|
||
participantCount: 0,
|
||
advisorCount: 0,
|
||
awardCount: 0,
|
||
schoolCount: 0
|
||
},
|
||
awardCountDescription: '',
|
||
yearDistribution: [],
|
||
typeDistribution: [],
|
||
groupDistribution: [],
|
||
schoolDistribution: []
|
||
})
|
||
|
||
export default {
|
||
components: {
|
||
CommonHero
|
||
},
|
||
data() {
|
||
return {
|
||
loading: false,
|
||
refreshing: false,
|
||
hasLoaded: false,
|
||
loadError: '',
|
||
selectedYear: '',
|
||
stats: createInitialStats()
|
||
}
|
||
},
|
||
computed: {
|
||
summary() {
|
||
return this.stats.summary || createInitialStats().summary
|
||
},
|
||
yearOptionLabels() {
|
||
const years = Array.isArray(this.stats.availableYears) ? this.stats.availableYears : []
|
||
return ['全部年份', ...years.map((item) => `${item} 年`)]
|
||
},
|
||
selectedYearIndex() {
|
||
if (!this.selectedYear) {
|
||
return 0
|
||
}
|
||
const years = Array.isArray(this.stats.availableYears) ? this.stats.availableYears : []
|
||
const index = years.findIndex((item) => Number(item) === Number(this.selectedYear))
|
||
return index >= 0 ? index + 1 : 0
|
||
},
|
||
maxYearValue() {
|
||
return this.getMaxValue(this.stats.yearDistribution)
|
||
},
|
||
maxTypeValue() {
|
||
return this.getMaxValue(this.stats.typeDistribution)
|
||
},
|
||
maxGroupValue() {
|
||
return this.getMaxValue(this.stats.groupDistribution)
|
||
},
|
||
maxSchoolValue() {
|
||
return this.getMaxValue(this.stats.schoolDistribution)
|
||
}
|
||
},
|
||
onLoad() {
|
||
this.loadData()
|
||
},
|
||
methods: {
|
||
showToast(title) {
|
||
uni.showToast({
|
||
title,
|
||
icon: 'none'
|
||
})
|
||
},
|
||
getMaxValue(list) {
|
||
const values = (Array.isArray(list) ? list : []).map((item) => Number(item.value || 0))
|
||
return values.length ? Math.max(...values, 1) : 1
|
||
},
|
||
getBarWidth(value, maxValue) {
|
||
const current = Number(value || 0)
|
||
const max = Number(maxValue || 1)
|
||
const percent = max > 0 ? (current / max) * 100 : 0
|
||
return `${Math.max(percent, current > 0 ? 8 : 0)}%`
|
||
},
|
||
handleYearChange(event) {
|
||
const index = Number(event.detail.value || 0)
|
||
if (index === 0) {
|
||
this.selectedYear = ''
|
||
} else {
|
||
const year = this.stats.availableYears[index - 1]
|
||
this.selectedYear = year || ''
|
||
}
|
||
this.loadData()
|
||
},
|
||
handleRefresh() {
|
||
this.refreshing = true
|
||
this.loadData(true)
|
||
},
|
||
async loadData(fromRefresh = false) {
|
||
if (this.loading) {
|
||
if (fromRefresh) {
|
||
this.refreshing = false
|
||
}
|
||
return
|
||
}
|
||
this.loading = true
|
||
this.loadError = ''
|
||
try {
|
||
const params = this.selectedYear ? { year: Number(this.selectedYear) } : undefined
|
||
const result = await getChallengeCupStatistics(params)
|
||
this.stats = {
|
||
...createInitialStats(),
|
||
...(result || {}),
|
||
summary: {
|
||
...createInitialStats().summary,
|
||
...((result && result.summary) || {})
|
||
},
|
||
availableYears: Array.isArray(result && result.availableYears) ? result.availableYears : [],
|
||
yearDistribution: Array.isArray(result && result.yearDistribution) ? result.yearDistribution : [],
|
||
typeDistribution: Array.isArray(result && result.typeDistribution) ? result.typeDistribution : [],
|
||
groupDistribution: Array.isArray(result && result.groupDistribution) ? result.groupDistribution : [],
|
||
schoolDistribution: Array.isArray(result && result.schoolDistribution) ? result.schoolDistribution : []
|
||
}
|
||
if (
|
||
this.selectedYear &&
|
||
!this.stats.availableYears.some((item) => Number(item) === Number(this.selectedYear))
|
||
) {
|
||
this.selectedYear = ''
|
||
}
|
||
this.hasLoaded = true
|
||
} catch (error) {
|
||
this.loadError = (error && error.message) || '统计数据加载失败'
|
||
this.showToast(this.loadError)
|
||
} finally {
|
||
this.loading = false
|
||
if (fromRefresh) {
|
||
this.refreshing = false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.page {
|
||
min-height: 100vh;
|
||
background:
|
||
radial-gradient(circle at top left, rgba(37, 99, 235, 0.14), transparent 28%),
|
||
linear-gradient(180deg, #f7fbff 0%, #fffdf8 100%);
|
||
}
|
||
|
||
.page-scroll {
|
||
height: 100vh;
|
||
//padding: 28rpx 24rpx 40rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.hero-card {
|
||
padding: 34rpx 36rpx;
|
||
border-radius: 28rpx;
|
||
background: rgba(255, 255, 255, 0.5);
|
||
box-shadow: 0 4rpx 12rpx 0 rgba(174, 174, 174, 0.4);
|
||
backdrop-filter: blur(24rpx);
|
||
-webkit-backdrop-filter: blur(24rpx);
|
||
border: 1rpx solid rgba(255, 255, 255, 0.5);
|
||
}
|
||
|
||
.hero-badge {
|
||
display: inline-flex;
|
||
padding: 10rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba(20,150,242,.1);
|
||
font-size: 22rpx;
|
||
color: #1496f2;
|
||
letter-spacing: 1rpx;
|
||
}
|
||
|
||
.hero-title {
|
||
margin-top: 8rpx;
|
||
padding-left: 10rpx;
|
||
font-size: 30rpx;
|
||
line-height: 1.35;
|
||
font-weight: 700;
|
||
color: #18a0f7;
|
||
border-left: 5px solid #0f94ef;
|
||
}
|
||
|
||
.hero-desc {
|
||
margin-top: 20rpx;
|
||
font-size: 24rpx;
|
||
line-height: 1.8;
|
||
color: #7f7f7f;
|
||
}
|
||
|
||
.hero-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 16rpx;
|
||
margin-top: 24rpx;
|
||
}
|
||
|
||
.picker-wrap {
|
||
display: block;
|
||
}
|
||
|
||
.action-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 10rpx;
|
||
padding: 16rpx 24rpx;
|
||
border-radius: 999rpx;
|
||
background: rgba(255, 255, 255, 0.18);
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.action-pill--ghost {
|
||
background: rgba(10, 20, 45, 0.16);
|
||
}
|
||
|
||
.action-pill--loading {
|
||
opacity: 0.7;
|
||
}
|
||
|
||
.action-pill-arrow {
|
||
font-size: 18rpx;
|
||
opacity: 0.85;
|
||
}
|
||
|
||
.alert-card {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16rpx;
|
||
margin-top: 22rpx;
|
||
padding: 24rpx;
|
||
border-radius: 28rpx;
|
||
background: #eef6ff;
|
||
border: 1rpx solid rgba(37, 99, 235, 0.12);
|
||
}
|
||
|
||
.alert-icon {
|
||
flex-shrink: 0;
|
||
width: 36rpx;
|
||
height: 36rpx;
|
||
border-radius: 50%;
|
||
background: #2563eb;
|
||
color: #fff;
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
line-height: 36rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.alert-text {
|
||
font-size: 24rpx;
|
||
line-height: 1.7;
|
||
color: #31537c;
|
||
}
|
||
|
||
.state-card {
|
||
margin-top: 24rpx;
|
||
padding: 32rpx 28rpx;
|
||
border-radius: 28rpx;
|
||
background: rgba(255, 255, 255, 0.94);
|
||
border: 1rpx solid rgba(37, 99, 235, 0.08);
|
||
box-shadow: 0 10rpx 28rpx rgba(34, 94, 142, 0.08);
|
||
}
|
||
|
||
.state-card--error {
|
||
background: rgba(247, 250, 255, 0.98);
|
||
border-color: rgba(37, 99, 235, 0.16);
|
||
}
|
||
|
||
.state-title {
|
||
font-size: 30rpx;
|
||
font-weight: 700;
|
||
color: #1f2329;
|
||
}
|
||
|
||
.state-desc {
|
||
margin-top: 12rpx;
|
||
font-size: 24rpx;
|
||
line-height: 1.7;
|
||
color: #6c7480;
|
||
}
|
||
|
||
.state-action {
|
||
margin-top: 18rpx;
|
||
font-size: 24rpx;
|
||
font-weight: 600;
|
||
color: #2563eb;
|
||
}
|
||
|
||
.metrics-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 18rpx;
|
||
margin-top: 24rpx;
|
||
}
|
||
|
||
.metric-card {
|
||
padding: 24rpx;
|
||
border-radius: 28rpx;
|
||
background: linear-gradient(135deg, #eff6ff 0%, #ffffff 100%);
|
||
border: 1rpx solid #dbeafe;
|
||
box-shadow: 0 10rpx 28rpx rgba(15, 23, 42, 0.05);
|
||
}
|
||
|
||
.metric-card--accent {
|
||
background: linear-gradient(135deg, #f0fdf4 0%, #ffffff 100%);
|
||
border-color: #bbf7d0;
|
||
}
|
||
|
||
.metric-card--warm {
|
||
background: linear-gradient(135deg, #fff7ed 0%, #ffffff 100%);
|
||
border-color: #fed7aa;
|
||
}
|
||
|
||
.metric-card--success {
|
||
background: linear-gradient(135deg, #ecfeff 0%, #ffffff 100%);
|
||
border-color: #a5f3fc;
|
||
}
|
||
|
||
.metric-label {
|
||
font-size: 24rpx;
|
||
color: #64748b;
|
||
}
|
||
|
||
.metric-value {
|
||
margin-top: 12rpx;
|
||
font-size: 46rpx;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
color: #0f172a;
|
||
}
|
||
|
||
.metric-foot {
|
||
margin-top: 14rpx;
|
||
font-size: 22rpx;
|
||
line-height: 1.6;
|
||
color: #94a3b8;
|
||
}
|
||
|
||
.mini-metrics {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||
gap: 16rpx;
|
||
margin-top: 18rpx;
|
||
}
|
||
|
||
.mini-metric-card {
|
||
padding: 22rpx 18rpx;
|
||
border-radius: 24rpx;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
box-shadow: 0 8rpx 20rpx rgba(15, 23, 42, 0.05);
|
||
font-size: 22rpx;
|
||
line-height: 1.6;
|
||
color: #64748b;
|
||
}
|
||
|
||
.mini-metric-value {
|
||
display: block;
|
||
margin-top: 8rpx;
|
||
font-size: 34rpx;
|
||
font-weight: 700;
|
||
color: #0f172a;
|
||
}
|
||
|
||
.section-card {
|
||
margin-top: 24rpx;
|
||
padding: 28rpx 24rpx;
|
||
border-radius: 30rpx;
|
||
background: rgba(255, 255, 255, 0.94);
|
||
box-shadow: 0 14rpx 34rpx rgba(15, 23, 42, 0.06);
|
||
}
|
||
|
||
.section-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 18rpx;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: 700;
|
||
color: #162033;
|
||
}
|
||
|
||
.section-subtitle {
|
||
margin-top: 8rpx;
|
||
font-size: 22rpx;
|
||
line-height: 1.6;
|
||
color: #8b94a3;
|
||
}
|
||
|
||
.empty-card {
|
||
margin-top: 20rpx;
|
||
padding: 28rpx 24rpx;
|
||
border-radius: 24rpx;
|
||
background: #f8fbff;
|
||
font-size: 24rpx;
|
||
color: #90a0b7;
|
||
}
|
||
|
||
.trend-list,
|
||
.distribution-list,
|
||
.ranking-list,
|
||
.detail-list {
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.trend-item,
|
||
.distribution-item {
|
||
& + & {
|
||
margin-top: 18rpx;
|
||
}
|
||
}
|
||
|
||
.trend-top,
|
||
.distribution-main,
|
||
.detail-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16rpx;
|
||
}
|
||
|
||
.trend-label,
|
||
.distribution-label,
|
||
.detail-label {
|
||
flex: 1;
|
||
min-width: 0;
|
||
font-size: 24rpx;
|
||
line-height: 1.6;
|
||
color: #1f2937;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.trend-value,
|
||
.distribution-value,
|
||
.detail-value {
|
||
flex-shrink: 0;
|
||
font-size: 24rpx;
|
||
font-weight: 700;
|
||
color: #0f172a;
|
||
}
|
||
|
||
.bar-track {
|
||
height: 16rpx;
|
||
margin-top: 12rpx;
|
||
border-radius: 999rpx;
|
||
background: #edf2f7;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.bar-fill {
|
||
height: 100%;
|
||
border-radius: 999rpx;
|
||
}
|
||
|
||
.bar-fill--blue {
|
||
background: linear-gradient(90deg, #3b82f6 0%, #2563eb 100%);
|
||
}
|
||
|
||
.bar-fill--cyan {
|
||
background: linear-gradient(90deg, #38bdf8 0%, #0ea5e9 100%);
|
||
}
|
||
|
||
.bar-fill--orange {
|
||
background: linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%);
|
||
}
|
||
|
||
.bar-fill--green {
|
||
background: linear-gradient(90deg, #34d399 0%, #10b981 100%);
|
||
}
|
||
|
||
.ranking-item {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16rpx;
|
||
& + & {
|
||
margin-top: 18rpx;
|
||
}
|
||
}
|
||
|
||
.ranking-order {
|
||
flex-shrink: 0;
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border-radius: 50%;
|
||
background: #ecf4ff;
|
||
color: #2563eb;
|
||
font-size: 22rpx;
|
||
font-weight: 700;
|
||
line-height: 40rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.ranking-main {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.detail-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||
gap: 18rpx;
|
||
margin-top: 20rpx;
|
||
}
|
||
|
||
.detail-panel {
|
||
padding: 22rpx;
|
||
border-radius: 24rpx;
|
||
background: #f9fbfe;
|
||
}
|
||
|
||
.detail-title {
|
||
font-size: 26rpx;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.detail-row {
|
||
padding: 16rpx 0;
|
||
border-bottom: 1rpx solid rgba(148, 163, 184, 0.18);
|
||
}
|
||
|
||
.detail-row:last-child {
|
||
border-bottom: none;
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.detail-empty {
|
||
margin-top: 18rpx;
|
||
font-size: 22rpx;
|
||
color: #94a3b8;
|
||
}
|
||
</style>
|