SportMeetingAdminSys/app/pages/scoreboard.vue
laobinghu 4df5c13976 refactor: simplify dark mode with VueUse + Element Plus
Remove custom CSS variable overrides, use Element Plus built-in dark mode with VueUse useDark
2026-03-22 16:22:35 +08:00

290 lines
7.4 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="scoreboard-container">
<el-card>
<template #header>
<div class="card-header">
<span>记分板</span>
<el-button type="primary" @click="loadScoreboard">刷新</el-button>
</div>
</template>
<el-form :inline="true" class="filter-form">
<!-- 年级筛选 -->
<el-form-item label="年级">
<el-select v-model="filters.grade" placeholder="全部" clearable @change="onFilterChange">
<el-option v-for="g in config.grades" :key="g" :label="g" :value="g" />
</el-select>
</el-form-item>
<!-- 班级类型筛选 -->
<el-form-item label="班级类型">
<el-select v-model="filters.classType" placeholder="全部" clearable :disabled="!filters.grade" @change="onFilterChange">
<el-option v-for="c in config.classTypes" :key="c" :label="c" :value="c" />
</el-select>
</el-form-item>
<!-- 性别筛选 -->
<el-form-item label="性别">
<el-select v-model="filters.gender" placeholder="全部" clearable :disabled="!filters.grade" @change="onFilterChange">
<el-option v-for="g in config.genders" :key="g" :label="g" :value="g" />
</el-select>
</el-form-item>
</el-form>
<el-table :data="scoreboard" border stripe class="scoreboard-table">
<el-table-column label="排名" width="80" class-name="col-rank">
<template #default="{ $index }">
<div class="rank-cell">
<el-icon v-if="$index === 0" color="#FFD700" :size="24"><Trophy /></el-icon>
<el-icon v-else-if="$index === 1" color="#C0C0C0" :size="24"><Trophy /></el-icon>
<el-icon v-else-if="$index === 2" color="#CD7F32" :size="24"><Trophy /></el-icon>
<span v-else>{{ $index + 1 }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="name" label="队伍名称" />
<el-table-column prop="team_group" label="组别" width="150" class-name="col-group" />
<el-table-column prop="total_score" label="总分" width="100" sortable>
<template #default="{ row }">
<el-tag type="danger" size="large">{{ row.total_score }}</el-tag>
</template>
</el-table-column>
<el-table-column label="奖牌统计" width="200" class-name="col-medal">
<template #default="{ row }">
<div class="medals">
<el-tag type="warning">🥇 {{ row.gold_count }}</el-tag>
<el-tag type="success">🥈 {{ row.silver_count }}</el-tag>
<el-tag type="info">🥉 {{ row.bronze_count }}</el-tag>
</div>
</template>
</el-table-column>
</el-table>
</el-card>
<el-row :gutter="20" class="stats-section">
<el-col :xs="24" :sm="24" :md="8" :lg="8">
<el-card>
<template #header>
<span>积分规则</span>
</template>
<ul class="rules-list">
<li>第1名7分 + 金牌</li>
<li>第2名5分 + 银牌</li>
<li>第3名3分 + 铜牌</li>
<li>其他名次:不计分</li>
</ul>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8">
<el-card>
<template #header>
<span>金牌榜前三</span>
</template>
<div v-for="(item, index) in topGold" :key="item.id" class="top-item">
<span>{{ index + 1 }}. {{ item.name }}</span>
<el-tag type="warning">{{ item.gold_count }} 枚</el-tag>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="24" :md="8" :lg="8">
<el-card>
<template #header>
<span>总分榜前三</span>
</template>
<div v-for="(item, index) in topScore" :key="item.id" class="top-item">
<span>{{ index + 1 }}. {{ item.name }}</span>
<el-tag type="danger">{{ item.total_score }} </el-tag>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { Trophy } from '@element-plus/icons-vue'
const scoreboard = ref([])
const config = ref({
grades: [] as string[],
classTypes: [] as string[],
genders: [] as string[]
})
const filters = ref({
grade: '',
classType: '',
gender: ''
})
const topGold = computed(() => {
return [...scoreboard.value]
.sort((a, b) => b.gold_count - a.gold_count)
.slice(0, 3)
})
const topScore = computed(() => {
return scoreboard.value.slice(0, 3)
})
const loadConfig = async () => {
try {
const res = await $fetch('/api/config')
config.value = res.data.groups
} catch (error) {
ElMessage.error('加载配置失败')
}
}
const loadScoreboard = async () => {
try {
const params = new URLSearchParams()
if (filters.value.grade) params.append('grade', filters.value.grade)
if (filters.value.classType) params.append('classType', filters.value.classType)
if (filters.value.gender) params.append('gender', filters.value.gender)
const res = await $fetch(`/api/scoreboard?${params}`)
scoreboard.value = res.data
} catch (error) {
ElMessage.error('加载记分板失败')
}
}
const onFilterChange = () => {
loadScoreboard()
}
onMounted(() => {
loadConfig()
loadScoreboard()
})
</script>
<style scoped>
.scoreboard-container {
max-width: 1200px;
margin: 0 auto;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.card-header span {
color: var(--el-text-color-primary);
font-size: 18px;
font-weight: 600;
}
.filter-form {
margin-bottom: 20px;
}
/* 确保表格和表单在暗色模式下文字清晰 */
:deep(.el-card) {
background-color: var(--header-bg);
color: var(--el-text-color-primary);
}
:deep(.el-card .el-card__header) {
border-bottom-color: var(--el-border-color);
}
:deep(.el-form-item__label) {
color: var(--el-text-color-primary) !important;
}
:deep(.el-table) {
color: var(--el-text-color-primary);
}
:deep(.el-table th) {
background-color: var(--header-bg);
color: var(--el-text-color-primary);
}
:deep(.el-table tr) {
background-color: var(--header-bg);
}
:deep(.el-table td) {
color: var(--el-text-color-primary);
}
.rank-cell {
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
font-weight: bold;
color: var(--el-text-color-primary);
}
.medals {
display: flex;
gap: 8px;
justify-content: center;
}
.stats-section {
margin-top: 20px;
}
.rules-list {
list-style: none;
padding: 0;
margin: 0;
}
.rules-list li {
padding: 8px 0;
border-bottom: 1px solid var(--el-border-color);
color: var(--el-text-color-primary);
}
.rules-list li:last-child {
border-bottom: none;
}
.top-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid var(--el-border-color);
color: var(--el-text-color-primary);
}
.top-item:last-child {
border-bottom: none;
}
@media (max-width: 900px) {
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.filter-form :deep(.el-form-item) {
margin-right: 0;
width: 100%;
}
.filter-form :deep(.el-select) {
width: 100%;
}
.scoreboard-table :deep(.col-group),
.scoreboard-table :deep(.col-medal) {
display: none;
}
.stats-section :deep(.el-col) {
margin-bottom: 12px;
}
}
</style>