Remove custom CSS variable overrides, use Element Plus built-in dark mode with VueUse useDark
306 lines
8.7 KiB
Vue
306 lines
8.7 KiB
Vue
<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>
|