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

306 lines
8.7 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="events-container">
<el-card>
<template #header>
<div class="card-header">
<span>比赛项目管理</span>
<el-button type="primary" @click="showAddDialog = true">添加项目</el-button>
</div>
</template>
<el-form :inline="true" class="filter-form">
<!-- 类别筛选 -->
<el-form-item label="类别">
<el-select v-model="filters.category" placeholder="全部" clearable @change="loadEvents">
<el-option label="田赛" value="田赛" />
<el-option label="径赛" value="径赛" />
<el-option label="团体赛" value="团体赛" />
</el-select>
</el-form-item>
<!-- 年级筛选 -->
<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="events" border stripe class="events-table transition-base">
<el-table-column prop="id" label="ID" width="80" class-name="col-id" />
<el-table-column prop="name" label="项目名称" />
<el-table-column prop="category" label="类别" width="120" class-name="col-category" />
<el-table-column prop="event_group" label="组别" width="150" class-name="col-group" />
<el-table-column prop="unit" label="单位" width="100" class-name="col-unit" />
<el-table-column prop="status" label="状态" width="100" class-name="col-status">
<template #default="{ row }">
<el-tag :type="row.status === 'completed' ? 'success' : 'info'">
{{ row.status === 'completed' ? '已完成' : '进行中' }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="showAddDialog" title="添加比赛项目" width="500px">
<el-form :model="form" label-width="100px">
<el-form-item label="类别">
<el-select v-model="form.category" placeholder="请选择" @change="onCategoryChange">
<el-option label="田赛" value="田赛" />
<el-option label="径赛" value="径赛" />
<el-option label="团体赛" value="团体赛" />
</el-select>
</el-form-item>
<el-form-item label="项目名称">
<el-select v-model="form.name" placeholder="请选择">
<el-option
v-for="event in availableEvents"
:key="event.name"
:label="event.name"
:value="event.name"
/>
</el-select>
</el-form-item>
<el-form-item label="组别">
<el-select v-model="form.event_group" placeholder="请选择">
<el-option
v-for="g in allGroupOptions"
:key="g.value"
:label="g.label"
:value="g.value"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="showAddDialog = false">取消</el-button>
<el-button type="primary" @click="addEvent">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ElMessage } from 'element-plus'
import { computed } from 'vue'
const events = ref([])
const showAddDialog = ref(false)
const config = ref({
grades: [] as string[],
classTypes: [] as string[],
genders: [] as string[],
all: [] as string[]
})
const filters = ref({
category: '',
grade: '',
classType: '',
gender: ''
})
const eventTypes = ref({})
const form = ref({
name: '',
category: '',
event_group: '',
unit: ''
})
const availableEvents = computed(() => {
if (!form.value.category) return []
return eventTypes.value[form.value.category] || []
})
// 为添加项目对话框生成所有可用的组别选项从config API获取
const allGroupOptions = computed(() => {
return (config.value.all || []).map(g => ({ value: g, label: g }))
})
const loadConfig = async () => {
try {
const res = await $fetch('/api/config')
config.value = res.data.groups
eventTypes.value = res.data.eventTypes
} catch (error) {
ElMessage.error('加载配置失败')
}
}
const loadEvents = async () => {
try {
const params = new URLSearchParams()
if (filters.value.category) params.append('category', filters.value.category)
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/events?${params}`)
events.value = res.data
} catch (error) {
ElMessage.error('加载项目失败')
}
}
const onFilterChange = () => {
loadEvents()
}
const onCategoryChange = () => {
form.value.name = ''
form.value.unit = ''
}
const addEvent = async () => {
try {
const selectedEvent = availableEvents.value.find(e => e.name === form.value.name)
if (!selectedEvent) {
ElMessage.error('请选择项目')
return
}
await $fetch('/api/events', {
method: 'POST',
body: {
name: form.value.name,
category: form.value.category,
event_group: form.value.event_group,
unit: selectedEvent.unit
}
})
ElMessage.success('添加成功')
showAddDialog.value = false
form.value = { name: '', category: '', event_group: '', unit: '' }
loadEvents()
} catch (error) {
ElMessage.error('添加失败')
}
}
onMounted(() => {
loadConfig()
loadEvents()
})
</script>
<style scoped>
.events-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);
}
/* Table row staggered animations */
.events-table :deep(.el-table__body tr) {
animation: slide-in-up 0.3s ease-out;
animation-fill-mode: both;
}
.events-table :deep(.el-table__body tr:nth-child(1)) { animation-delay: 0.05s; }
.events-table :deep(.el-table__body tr:nth-child(2)) { animation-delay: 0.1s; }
.events-table :deep(.el-table__body tr:nth-child(3)) { animation-delay: 0.15s; }
.events-table :deep(.el-table__body tr:nth-child(4)) { animation-delay: 0.2s; }
.events-table :deep(.el-table__body tr:nth-child(5)) { animation-delay: 0.25s; }
.events-table :deep(.el-table__body tr:nth-child(6)) { animation-delay: 0.3s; }
.events-table :deep(.el-table__body tr:nth-child(7)) { animation-delay: 0.35s; }
.events-table :deep(.el-table__body tr:nth-child(8)) { animation-delay: 0.4s; }
.events-table :deep(.el-table__body tr:nth-child(9)) { animation-delay: 0.45s; }
.events-table :deep(.el-table__body tr:nth-child(10)) { animation-delay: 0.5s; }
@keyframes slide-in-up {
0% {
opacity: 0;
transform: translateY(15px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
@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%;
}
.events-table :deep(.col-id),
.events-table :deep(.col-category),
.events-table :deep(.col-unit),
.events-table :deep(.col-status) {
display: none;
}
}
</style>