feat(competition): 实现比赛创建功能并集成后端API

- 新增比赛创建页面,包含基本信息、组别管理和题目配置三个步骤
- 添加 OSS 图片上传组件,支持海报上传到阿里云OSS
- 集成后端API:获取房间列表、创建活动、创建队伍和创建题目
- 优化表单数据绑定和验证逻辑,使用防抖减少频繁更新
- 修复组别管理Excel导入的数据填充问题
- 更新类型定义,添加CommonResponse和比赛相关接口类型
This commit is contained in:
2026-01-29 18:03:13 +08:00
parent 82b1dd7ebe
commit beaaee72b4
13 changed files with 493 additions and 147 deletions

View File

@ -1,8 +1,10 @@
<script setup lang="ts">
import { NButton, NForm, NFormItem, NInput, NInputNumber, NModal, NSelect, type SelectOption } from 'naive-ui'
import { ref, watch } from 'vue'
import { onMounted, ref, watch } from 'vue'
import { VueDraggable } from 'vue-draggable-plus'
import { roundTypeOptions, scoreTypeOptions, timeOptions, uiTypeOptions } from '@/constants/business'
import { fetchGetQuestionListAll } from '@/service/api/question'
import { fetchTemplateList } from '@/service/api/template'
import { useQuestionConfig } from './useQuestionConfig'
const props = defineProps<{
@ -13,13 +15,13 @@ const props = defineProps<{
roundType: string
questions: Array<{
id: string
value: string | null
questionId: string | null
time: number
title: string
scoreType: Api.Competition.QuestionScoreType
score: number
uiType?: string
templateId?: string
templateId?: number
}>
}>
}
@ -28,7 +30,6 @@ const {
// getRoundScore,
getRoundTime,
totalStats,
questionOptions,
addRound,
removeRound,
addQuestion,
@ -37,24 +38,9 @@ const {
reset,
} = useQuestionConfig(props)
const templateIdOptions: SelectOption[] = [
{
label: '模板1',
value: 't1',
},
{
label: '模版2',
value: 't2',
},
{
label: '模版3',
value: 't3',
},
{
label: '模版4',
value: 't4',
},
]
const questionOptions = ref<SelectOption[]>([])
const templateIdOptions = ref<SelectOption[]>([])
// 模态框控制
const showAddModal = ref(false)
@ -97,10 +83,44 @@ watch(() => props.modelValue, (val) => {
})
})
}
// eslint-disable-next-line no-console
console.log(val, '拖动后的重新顺序')
}, { immediate: true, deep: true })
/** 获取所有题目 */
async function getAllQuestions() {
const { data: res, error } = await fetchGetQuestionListAll()
if (error) {
window.$message?.error(error.message)
return
}
const { data } = res || []
questionOptions.value = data?.map((item: any) => ({
label: `${item.Name}-${item.QuestionContent}`,
value: item.Id,
})) || []
}
/** 获取所有模板 */
async function getAllTemplates() {
const { data: res, error } = await fetchTemplateList()
if (error) {
window.$message?.error(error.message)
return
}
const { data } = res || []
templateIdOptions.value = Array.isArray(data)
? data.map((item: any) => ({
label: item.Name,
value: item.ID,
})) || []
: []
}
onMounted(() => {
getAllQuestions()
getAllTemplates()
})
defineExpose({ validate, reset })
</script>
@ -271,7 +291,7 @@ defineExpose({ validate, reset })
<!-- 题型 -->
<div class="col-span-4">
<NSelect
v-model:value="item.value" :options="questionOptions" placeholder="请选择题目类型" size="small"
v-model:value="item.questionId" :options="questionOptions" placeholder="请选择题目类型" size="small"
class="font-medium"
/>
</div>
@ -279,16 +299,16 @@ defineExpose({ validate, reset })
<!-- UItype -->
<div class="col-span-4">
<NSelect
v-model:value="item.uiType" :options="uiTypeOptions" placeholder="请选择UI类型"
size="small" class="font-medium"
v-model:value="item.uiType" :options="uiTypeOptions" placeholder="请选择UI类型" size="small"
class="font-medium"
/>
</div>
<!-- templateId -->
<div class="col-span-4">
<NSelect
v-model:value="item.templateId" :options="templateIdOptions" placeholder="请选择模板"
size="small" class="font-medium"
v-model:value="item.templateId" :options="templateIdOptions" placeholder="请选择模板" size="small"
class="font-medium"
/>
</div>