feat(competition): 新增比赛管理功能并优化UI交互

- 添加比赛创建向导,支持分步配置基础信息、分组管理、硬件绑定和题目配置
- 实现比赛卡片组件,支持复制和删除操作
- 优化比赛详情页的表单交互和样式
- 新增全局组件类型定义和图标
- 添加比赛海报上传功能
- 完善比赛状态管理,新增"已结束"状态
This commit is contained in:
2026-01-21 08:41:36 +08:00
parent e6346a771e
commit 3270e24c25
12 changed files with 923 additions and 315 deletions

View File

@ -1,6 +1,15 @@
<script setup lang="ts">
import { NGrid, NGridItem, NSelect } from 'naive-ui'
import { computed, ref } from 'vue'
import { computed } from 'vue'
const props = defineProps<{
modelValue: {
questions: {
regular: Array<{ value: string, score: number, time: number }>
pk: Array<{ value: string, score: number, time: number }>
}
}
}>()
const questionOptions = [
{ label: '汉字听写 - 根据提示书写汉字', value: 'hanzi' },
@ -36,50 +45,54 @@ const timeOptions = [
{ label: '90s', value: 90 },
]
const regularQuestions = ref([
{ value: 'hanzi', score: 1, time: 15 },
{ value: 'tongyin', score: 1, time: 15 },
{ value: 'pianpang', score: 1, time: 30 },
{ value: 'ciyu', score: 1, time: 30 },
{ value: 'chengyu', score: 1, time: 30 },
{ value: 'fanyi', score: 1, time: 60 },
{ value: 'jinyi', score: 1, time: 30 },
{ value: 'kantu', score: 1, time: 30 },
{ value: 'gushi', score: 1, time: 60 },
{ value: 'mingju', score: 1, time: 60 },
])
const regularTotalScore = computed(() => {
return props.modelValue?.questions?.regular?.reduce((acc, cur) => acc + cur.score, 0) || 0
})
const pkQuestions = ref([
{ value: 'speed10', score: 1, time: 60 },
{ value: 'speed10', score: 1, time: 60 },
{ value: 'speed10', score: 1, time: 60 },
{ value: 'speed10', score: 1, time: 60 },
{ value: 'speed10', score: 3, time: 60 },
])
const pkTotalScore = computed(() => {
return props.modelValue?.questions?.pk?.reduce((acc, cur) => acc + cur.score, 0) || 0
})
const regularTotalScore = computed(() => regularQuestions.value.reduce((acc, cur) => acc + cur.score, 0))
const pkTotalScore = computed(() => pkQuestions.value.reduce((acc, cur) => acc + cur.score, 0))
function validate() {
// 简单校验:主赛环节是否有题目
if (!props.modelValue?.questions?.regular?.length) {
window.$message?.error('请至少配置一道主赛题目')
return false
}
// 检查是否所有题目都选了题型
const invalidRegular = props.modelValue.questions.regular.some(q => !q.value)
if (invalidRegular) {
window.$message?.error('请完善主赛环节的题型配置')
return false
}
return true
}
defineExpose({ validate })
</script>
<template>
<div class="py-4">
<div class="mb-8 text-xl text-gray-800 font-bold">
比赛赛题包配置
</div>
<NGrid :x-gap="48" :y-gap="24" cols="1 l:2" responsive="screen">
<!-- 左侧常规赛题包 -->
<!-- 左侧主赛环节 -->
<NGridItem>
<div class="h-full rounded-2xl bg-[#F5F9FF] p-6">
<div class="mb-6 flex items-center justify-between border-b border-blue-100 pb-4">
<div class="flex items-center gap-2 text-gray-800 font-bold">
<icon-ic-outline-archive class="text-xl text-blue-500" />
<span class="text-lg">常规赛题包</span>
<span class="text-lg">主赛环节</span>
</div>
<div class="text-sm text-[#8DA5C3] font-medium">
{{ regularQuestions.length }} / 总计 {{ regularTotalScore }}
{{ props.modelValue?.questions?.regular?.length || 0 }} / 总计 {{ regularTotalScore }}
</div>
</div>
<div class="flex flex-col gap-5">
<div
v-for="(item, idx) in regularQuestions"
v-for="(item, idx) in props.modelValue?.questions?.regular"
:key="idx"
class="flex items-center gap-3"
>
@ -141,22 +154,22 @@ const pkTotalScore = computed(() => pkQuestions.value.reduce((acc, cur) => acc +
</div>
</NGridItem>
<!-- 右侧加时 (PK环节) -->
<!-- 右侧加时PK环节 -->
<NGridItem>
<div class="h-full border border-[#FFF0E0] rounded-2xl bg-[#FFF9F5] p-6">
<div class="mb-6 flex items-center justify-between border-b border-orange-100 pb-4">
<div class="flex items-center gap-2 text-[#5E3218] font-bold">
<icon-ic-outline-timer class="text-xl text-[#D96B23]" />
<span class="text-lg">加时赛题包</span>
<span class="text-lg">加时PK环节</span>
</div>
<div class="text-sm text-[#D96B23]/60 font-medium">
{{ pkQuestions.length }} / 总计 {{ pkTotalScore }}
{{ props.modelValue?.questions?.pk?.length || 0 }} / 总计 {{ pkTotalScore }}
</div>
</div>
<div class="flex flex-col gap-5">
<div
v-for="(item, idx) in pkQuestions"
v-for="(item, idx) in props.modelValue?.questions?.pk"
:key="idx"
class="flex items-center gap-3"
>