新增题库模块,包含题目管理页面和Excel导入导出功能 优化比赛管理功能,包括分组管理、硬件绑定和题目配置 重构比赛添加流程,增加重置功能和数据校验 添加Excel工具函数,支持读取和导出Excel文件 更新依赖,添加xlsx库支持
239 lines
8.9 KiB
Vue
239 lines
8.9 KiB
Vue
<script setup lang="ts">
|
||
import { NButton, NDatePicker, NForm, NInput, NInputNumber, NUpload } from 'naive-ui'
|
||
import { useTemplateRef } from 'vue'
|
||
import { useBasicInfo } from './useBasicInfo'
|
||
|
||
const props = defineProps<{
|
||
modelValue: {
|
||
name: string
|
||
startTime: number | null
|
||
endTime: number | null
|
||
groupCount: number
|
||
teamCount: number
|
||
poster: string
|
||
}
|
||
}>()
|
||
|
||
const emit = defineEmits(['update:modelValue'])
|
||
|
||
const formRef = useTemplateRef('formRef')
|
||
|
||
const { formData, updateCount, handleUpload, validate, reset } = useBasicInfo(props, emit)
|
||
|
||
defineExpose({ validate, reset })
|
||
</script>
|
||
|
||
<template>
|
||
<div class="h-full flex gap-12">
|
||
<!-- 左侧表单区域 -->
|
||
<div class="flex-1">
|
||
<div class="mb-12">
|
||
<h2 class="mb-4 text-4xl text-gray-800 font-bold">
|
||
第一步: 基础定义
|
||
</h2>
|
||
<p class="text-lg text-gray-400 font-bold">
|
||
请确认比赛名称与参与规模。这将作为系统自动生成分组与终端连接数的依据。
|
||
</p>
|
||
</div>
|
||
|
||
<NForm ref="formRef" label-placement="top" :show-feedback="false">
|
||
<div class="mb-10">
|
||
<div class="mb-3 text-sm text-[#8DA5C3] font-bold">
|
||
比赛项目名称
|
||
</div>
|
||
<NInput
|
||
v-model:value="formData.name" placeholder="例如:2026年度阅读之星-辞海遨游总决赛"
|
||
class="h-16 rounded-2xl border-none text-xl line-height-16 shadow-[0_2px_10px_rgba(0,0,0,0.02)] !bg-[#FCFCFC]"
|
||
/>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 mb-10 gap-8">
|
||
<div>
|
||
<div class="mb-3 text-sm text-[#8DA5C3] font-bold">
|
||
开始时间
|
||
</div>
|
||
<NDatePicker
|
||
v-model:value="formData.startTime" type="datetime" clearable class="w-full"
|
||
placeholder="选择开始时间" :theme-overrides="{
|
||
peers: {
|
||
Input: {
|
||
borderRadius: '1rem',
|
||
color: '#FCFCFC',
|
||
heightLarge: '4rem',
|
||
fontSizeLarge: '1.25rem',
|
||
textColor: '#1f2937',
|
||
borderHover: 'none',
|
||
borderFocus: 'none',
|
||
},
|
||
},
|
||
}" size="large"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<div class="mb-3 text-sm text-[#8DA5C3] font-bold">
|
||
结束时间
|
||
</div>
|
||
<NDatePicker
|
||
v-model:value="formData.endTime" type="datetime" clearable class="w-full" placeholder="选择结束时间"
|
||
:theme-overrides="{
|
||
peers: {
|
||
Input: {
|
||
borderRadius: '1rem',
|
||
color: '#FCFCFC',
|
||
heightLarge: '4rem',
|
||
fontSizeLarge: '1.25rem',
|
||
textColor: '#1f2937',
|
||
borderHover: 'none',
|
||
borderFocus: 'none',
|
||
},
|
||
},
|
||
}" size="large"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 gap-8">
|
||
<!-- 参赛小组数量卡片 -->
|
||
<div class="rounded-3xl bg-[#F5F8FF] p-8">
|
||
<div class="mb-6 text-sm text-[#3B82F6] font-bold tracking-wide uppercase">
|
||
参赛小组总数 (GROUPS)
|
||
</div>
|
||
<div class="mb-6 flex items-center justify-between">
|
||
<button
|
||
class="h-14 w-14 flex items-center justify-center rounded-full bg-white text-3xl text-[#3B82F6] shadow-md transition-transform active:scale-95 hover:bg-gray-50"
|
||
@click="updateCount('group', -1)"
|
||
>
|
||
<div class="mb-1 font-bold">
|
||
-
|
||
</div>
|
||
</button>
|
||
<div class="w-32">
|
||
<NInputNumber
|
||
v-model:value="formData.groupCount" :min="1" :max="999" :show-button="false"
|
||
class="text-center" :theme-overrides="{
|
||
peers: {
|
||
Input: {
|
||
textColor: '#1f2937',
|
||
fontSizeMedium: '3rem',
|
||
heightMedium: '4rem',
|
||
color: 'transparent',
|
||
caretColor: '#3B82F6',
|
||
},
|
||
},
|
||
}"
|
||
/>
|
||
</div>
|
||
<button
|
||
class="h-14 w-14 flex items-center justify-center rounded-full bg-white text-3xl text-[#3B82F6] shadow-md transition-transform active:scale-95 hover:bg-gray-50"
|
||
@click="updateCount('group', 1)"
|
||
>
|
||
<div class="mb-1 font-bold">
|
||
+
|
||
</div>
|
||
</button>
|
||
</div>
|
||
<div class="text-center text-sm text-[#3B82F6]/60 font-medium italic">
|
||
系统将预设 {{ formData.groupCount }} 个队伍
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 每组队伍数量卡片 -->
|
||
<div class="rounded-3xl bg-[#F5F8FF] p-8">
|
||
<div class="mb-6 text-sm text-[#3B82F6] font-bold tracking-wide uppercase">
|
||
单组包含队伍 (TEAMS)
|
||
</div>
|
||
<div class="mb-6 flex items-center justify-between">
|
||
<button
|
||
class="h-14 w-14 flex items-center justify-center rounded-full bg-white text-3xl text-[#3B82F6] shadow-md transition-transform active:scale-95 hover:bg-gray-50"
|
||
@click="updateCount('team', -1)"
|
||
>
|
||
<div class="mb-1 font-bold">
|
||
-
|
||
</div>
|
||
</button>
|
||
<div class="w-32">
|
||
<NInputNumber
|
||
v-model:value="formData.teamCount" :min="1" :max="999" :show-button="false"
|
||
class="text-center" :theme-overrides="{
|
||
peers: {
|
||
Input: {
|
||
textColor: '#1f2937',
|
||
fontSizeMedium: '3rem',
|
||
heightMedium: '4rem',
|
||
color: 'transparent',
|
||
caretColor: '#3B82F6',
|
||
},
|
||
},
|
||
}"
|
||
/>
|
||
</div>
|
||
<button
|
||
class="h-14 w-14 flex items-center justify-center rounded-full bg-white text-3xl text-[#3B82F6] shadow-md transition-transform active:scale-95 hover:bg-gray-50"
|
||
@click="updateCount('team', 1)"
|
||
>
|
||
<div class="mb-1 font-bold">
|
||
+
|
||
</div>
|
||
</button>
|
||
</div>
|
||
<div class="text-center text-sm text-[#3B82F6]/60 font-medium italic">
|
||
每组将有 {{ formData.teamCount }} 支队伍同时进行比赛
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</NForm>
|
||
</div>
|
||
|
||
<!-- 右侧海报上传区域 -->
|
||
<div class="w-99 flex flex-col pt-32">
|
||
<div class="mb-3 w-full text-sm text-[#8DA5C3] font-bold">
|
||
活动海报
|
||
<NAlert type="info" class="mt-2 text-xs text-[#616263] font-normal">
|
||
<span class="text-xs font-normal">
|
||
建议上传 16:9 或 4:3 比例的图片,以获得最佳的全屏显示效果。
|
||
</span>
|
||
</NAlert>
|
||
</div>
|
||
<NUpload accept="image/*" :show-file-list="false" :custom-request="handleUpload" class="block w-full">
|
||
<div
|
||
class="relative h-[400px] w-full flex flex-col cursor-pointer items-center justify-center overflow-hidden rounded-3xl bg-[#F5F8FF] transition-all hover:bg-gray-100"
|
||
:class="{ 'border-2 border-dashed border-gray-300': !formData.poster }"
|
||
>
|
||
<div
|
||
v-if="formData.poster"
|
||
class="group absolute inset-0 flex items-center justify-center bg-black/50 opacity-0 transition-opacity hover:opacity-100"
|
||
>
|
||
<div class="flex gap-4">
|
||
<!-- NUpload 的 trigger 会自动处理点击事件,这里不需要额外的上传逻辑,只需要一个按钮作为视觉触发 -->
|
||
<NButton ghost color="#fff" size="small">
|
||
更换
|
||
</NButton>
|
||
<NButton ghost color="#ff4d4f" size="small" @click.stop="formData.poster = ''">
|
||
删除
|
||
</NButton>
|
||
</div>
|
||
</div>
|
||
<img v-if="formData.poster" :src="formData.poster" class="h-full w-full object-cover" alt="Poster">
|
||
<div v-else class="flex flex-col items-center text-gray-400">
|
||
<div class="i-carbon-add-filled mb-4 text-4xl text-[#3B82F6]" />
|
||
<span class="text-sm font-bold">点击上传海报</span>
|
||
<span class="mt-2 text-xs opacity-60">支持 JPG/PNG 格式</span>
|
||
</div>
|
||
</div>
|
||
</NUpload>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style scoped>
|
||
:deep(.n-input-number .n-input__input-el) {
|
||
text-align: center;
|
||
font-family: inherit;
|
||
font-weight: 700;
|
||
}
|
||
|
||
:deep(.n-upload-trigger) {
|
||
width: 100%;
|
||
}
|
||
</style>
|