diff --git a/apps/admin/src/components/common/oss-image-upload/index.vue b/apps/admin/src/components/common/oss-image-upload/index.vue new file mode 100644 index 0000000..f951486 --- /dev/null +++ b/apps/admin/src/components/common/oss-image-upload/index.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/apps/admin/src/service/api/competition.ts b/apps/admin/src/service/api/competition.ts new file mode 100644 index 0000000..33f6ec0 --- /dev/null +++ b/apps/admin/src/service/api/competition.ts @@ -0,0 +1,42 @@ +import { request } from '../request' + +/** 房间相关接口 */ +export function fetchGetRoomList() { + return request>({ + url: '/Base/ActivityMain/GetActivity_RoomList', + method: 'get', + }) +} + +// 现在服务端把活动新建弄成了三个接口: +// 1. 创建活动 +// 2. 创建活动房间 +// 3. 创建活动队伍 +// 但是业务要求最后一步创建 + +/** 创建活动基础信息 */ +export function fetchCreateActivity(data: Api.Competition.CreateRoomRequest) { + return request>({ + url: '/Base/ActivityMain/AddActivity', + method: 'post', + data, + }) +} + +/** 创建活动队伍 */ +export function fetchCreateTeamList(data: Api.Competition.CreateTeamListRequest[]) { + return request>({ + url: '/Base/ActivityMain/AddActivity_TeamList', + method: 'post', + data, + }) +} + +/** 创建活动题目 */ +export function fetchCreateQuestion(data: Api.Competition.CreateQuestionRequest[]) { + return request>({ + url: '/Base/ActivityMain/AddOrUpdateActivity_Question', + method: 'post', + data, + }) +} diff --git a/apps/admin/src/service/api/template.ts b/apps/admin/src/service/api/template.ts index 541bf79..2577be7 100644 --- a/apps/admin/src/service/api/template.ts +++ b/apps/admin/src/service/api/template.ts @@ -4,7 +4,7 @@ import { request } from '../request' * 获取模板列表 */ export function fetchTemplateList(data?: Api.Template.TemplateSearchParams) { - return request>>({ + return request>({ url: '/Base/ActivityMain/GetTemplete_Pag', method: 'get', params: data || {}, diff --git a/apps/admin/src/typings/api/Competition.d.ts b/apps/admin/src/typings/api/Competition.d.ts index 680facd..1fa2697 100644 --- a/apps/admin/src/typings/api/Competition.d.ts +++ b/apps/admin/src/typings/api/Competition.d.ts @@ -52,6 +52,83 @@ declare namespace Api { /** competition round type */ type CompetitionRoundType = '1' | '2' /** 题包环节 | 加时环节 */ + /** room */ + interface Room { + /** room id */ + ID: number + /** room name */ + Name: string + } + + /** create room request */ + interface CreateRoomRequest { + /** 活动 id */ + id: number + /** 活动名称 */ + name: string + /** 开始时间 */ + startTime: string + /** 结束时间 */ + endTime: string + /** 队伍数量 */ + teams: number + /** 创建时间 */ + createdTime: string + /** 房间 id */ + roomID: number + /** 队伍分组数量 */ + teamGroupNumber: number + /** 背景图片 */ + backgroundImg: string + } + + /** create team list request */ + interface CreateTeamListRequest { + /** 队伍 id */ + id: number + /** 主 id */ + mainId: number + /** 队伍编号 */ + number: string + /** 队伍名称 */ + name: string + /** 笔序列号 */ + penSerial: string + /** 队员名单 */ + nameList: string + /** 学校名称 */ + schoolName: string + /** 队伍分组 id */ + teamGroupId: number + } + + interface CreateQuestionRequest { + /** 题目 id */ + id: number + /** 活动 id */ + activityID: number + /** 问题 id */ + questionID: number + /** 题目序号 */ + questionIndex: number + /** 活动题目名称 */ + actitvityQuestionName: string + /** 答题时间(秒) */ + questionTime: number + /** 题目规则 */ + questionRule: number + /** UI 类型 */ + uiType: string + /** 分值 */ + point: number + /** 题目副标题 */ + questionSubTitle: string + /** 题目模板 id */ + templateID: QuestionTemplateId + /** 赛题包类型 */ + roundType: number + } + /** common record */ type CommonRecord = { /** record id */ diff --git a/apps/admin/src/typings/api/common.d.ts b/apps/admin/src/typings/api/common.d.ts index 1316152..73c3e24 100644 --- a/apps/admin/src/typings/api/common.d.ts +++ b/apps/admin/src/typings/api/common.d.ts @@ -23,6 +23,14 @@ declare namespace Api { /** common search params of table */ type CommonSearchParams = Pick + /** common response */ + interface CommonResponse { + success: boolean + code: number + msg: string + data: any + } + /** * enable status * diff --git a/apps/admin/src/typings/components.d.ts b/apps/admin/src/typings/components.d.ts index daaf174..4868069 100644 --- a/apps/admin/src/typings/components.d.ts +++ b/apps/admin/src/typings/components.d.ts @@ -147,6 +147,7 @@ declare module 'vue' { NTreeSelect: typeof import('naive-ui')['NTreeSelect'] NUpload: typeof import('naive-ui')['NUpload'] NWatermark: typeof import('naive-ui')['NWatermark'] + OssImageUpload: typeof import('./../components/common/oss-image-upload/index.vue')['default'] PageHeader: typeof import('./../components/common/page-header.vue')['default'] PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] @@ -307,6 +308,7 @@ declare global { const NTreeSelect: typeof import('naive-ui')['NTreeSelect'] const NUpload: typeof import('naive-ui')['NUpload'] const NWatermark: typeof import('naive-ui')['NWatermark'] + const OssImageUpload: typeof import('./../components/common/oss-image-upload/index.vue')['default'] const PageHeader: typeof import('./../components/common/page-header.vue')['default'] const PinToggler: typeof import('./../components/common/pin-toggler.vue')['default'] const ReloadButton: typeof import('./../components/common/reload-button.vue')['default'] diff --git a/apps/admin/src/views/competition/competition-add/index.vue b/apps/admin/src/views/competition/competition-add/index.vue index 8a52796..05901b4 100644 --- a/apps/admin/src/views/competition/competition-add/index.vue +++ b/apps/admin/src/views/competition/competition-add/index.vue @@ -1,8 +1,11 @@ diff --git a/apps/admin/src/views/competition/competition-add/modules/GroupManagement.vue b/apps/admin/src/views/competition/competition-add/modules/GroupManagement.vue index c202bcc..88178f2 100644 --- a/apps/admin/src/views/competition/competition-add/modules/GroupManagement.vue +++ b/apps/admin/src/views/competition/competition-add/modules/GroupManagement.vue @@ -1,18 +1,36 @@ diff --git a/apps/admin/src/views/competition/competition-add/modules/QuestionConfig.vue b/apps/admin/src/views/competition/competition-add/modules/QuestionConfig.vue index 734a9ac..311d9ab 100644 --- a/apps/admin/src/views/competition/competition-add/modules/QuestionConfig.vue +++ b/apps/admin/src/views/competition/competition-add/modules/QuestionConfig.vue @@ -1,8 +1,10 @@ @@ -271,7 +291,7 @@ defineExpose({ validate, reset })
@@ -279,16 +299,16 @@ defineExpose({ validate, reset })
diff --git a/apps/admin/src/views/competition/competition-add/modules/useBasicInfo.ts b/apps/admin/src/views/competition/competition-add/modules/useBasicInfo.ts index aa67237..d319204 100644 --- a/apps/admin/src/views/competition/competition-add/modules/useBasicInfo.ts +++ b/apps/admin/src/views/competition/competition-add/modules/useBasicInfo.ts @@ -1,30 +1,38 @@ -import type { UploadCustomRequestOptions } from 'naive-ui' import { debounce } from '@sa/utils' import { ref, watch } from 'vue' export interface BasicInfoModel { name: string - startTime: number | null - endTime: number | null + startTime: string | null + endTime: string | null groupCount: number teamCount: number poster: string - ap: string + roomId: number | null } export function useBasicInfo(props: any, emit: any) { const formData = ref({ ...props.modelValue }) - // 修复死循环:添加值对比逻辑 watch(() => props.modelValue, (val) => { - if (JSON.stringify(val) !== JSON.stringify(formData.value)) { - formData.value = { ...val } + const newForm: BasicInfoModel = { + name: val.name, + startTime: val.startTime, + endTime: val.endTime, + groupCount: val.groupCount, + teamCount: val.teamCount, + poster: val.poster, + roomId: val.roomId, + } + + if (JSON.stringify(newForm) !== JSON.stringify(formData.value)) { + formData.value = newForm } }, { deep: true }) // 添加防抖:300ms 延迟,避免频繁触发父组件更新 const handleUpdate = debounce((val: typeof formData.value) => { - emit('update:modelValue', { ...val }) + emit('update:modelValue', { ...props.modelValue, ...val }) }, 300) watch(formData, (val) => { @@ -46,17 +54,6 @@ export function useBasicInfo(props: any, emit: any) { } } - // 模拟上传处理 - function handleUpload({ file, onFinish }: UploadCustomRequestOptions) { - const reader = new FileReader() - reader.readAsDataURL(file.file as File) - reader.onload = () => { - // 模拟上传成功,直接使用 base64 作为图片地址 - formData.value.poster = reader.result as string - onFinish() - } - } - // 校验方法 function validate() { if (!formData.value.name) { @@ -95,14 +92,13 @@ export function useBasicInfo(props: any, emit: any) { groupCount: 10, teamCount: 4, poster: '', - ap: '', + roomId: null, } } return { formData, updateCount, - handleUpload, validate, reset, } diff --git a/apps/admin/src/views/competition/competition-add/modules/useGroupManagement.ts b/apps/admin/src/views/competition/competition-add/modules/useGroupManagement.ts index 8bd9235..a316e43 100644 --- a/apps/admin/src/views/competition/competition-add/modules/useGroupManagement.ts +++ b/apps/admin/src/views/competition/competition-add/modules/useGroupManagement.ts @@ -13,7 +13,10 @@ export function useGroupManagement(props: any) { if (!groupCount || groupCount <= 0) return - const oldGroups = groups.value + // 优先使用现有的 groups.value,如果为空则尝试使用 props 中的初始数据 + const oldGroups = groups.value.length > 0 + ? groups.value + : (props.modelValue?.groupManagement || []) groups.value = Array.from({ length: groupCount }).map((_, index) => { const i = index + 1 @@ -39,7 +42,7 @@ export function useGroupManagement(props: any) { }) } - watch(() => props.config, generateGroups, { deep: true, immediate: true }) + watch(() => [props.config?.groupCount, props.config?.teamCount], generateGroups, { immediate: true, deep: true }) // 提交时候校验表单是否符合要求 function validate() { @@ -253,25 +256,34 @@ export function useExcelImport(props: any, groups: any) { } // 填充数据 - finalData.forEach((row: any, index) => { - if (index >= groups.value.length) - return + const newGroups = groups.value.map((group: any, index: number) => { + const row = finalData[index] + if (!row) + return group - const group = groups.value[index] + const newGroup = { ...group } // 分组名称 if (row['分组名称']) { - group.name = String(row['分组名称']) + newGroup.name = String(row['分组名称']) } // 队伍名称 - group.teams.forEach((team: any, tIndex: number) => { + newGroup.teams = group.teams.map((team: any, tIndex: number) => { const key = `队伍${tIndex + 1}名称` if (row[key]) { - team.name = String(row[key]) + return { + ...team, + name: String(row[key]), + } } + return team }) + + return newGroup }) + + groups.value = newGroups } catch (error) { console.error('Excel 解析失败:', error) diff --git a/apps/admin/src/views/competition/competition-add/modules/useQuestionConfig.ts b/apps/admin/src/views/competition/competition-add/modules/useQuestionConfig.ts index ec2f1c6..28f4dfa 100644 --- a/apps/admin/src/views/competition/competition-add/modules/useQuestionConfig.ts +++ b/apps/admin/src/views/competition/competition-add/modules/useQuestionConfig.ts @@ -3,13 +3,13 @@ import { roundTypeOptions, scoreTypeOptions } from '@/constants/business' export interface QuestionItem { id: string - value: string | null + questionId: string | null score: number time: number title: string scoreType: Api.Competition.QuestionScoreType uiType?: string - templateId?: string + templateId?: number } export interface RoundModel { @@ -50,21 +50,6 @@ export function useQuestionConfig(props: any) { }, { count: 0, score: 0, time: 0 }) }) - const questionOptions = [ - { label: '请根据提示书写正确的汉字-jiū 表示小鸟的叫声。', value: 'hanzi' }, - { label: '请根据提示书写正确的汉字-“春色满园关不住,一枝红杏出墙来”。请书写“杏”字。', value: 'tongyin' }, - { label: '请写出含有“车”的汉字。', value: 'pianpang' }, - { label: '请根据拼音书写正确的词语。', value: 'ciyu' }, - { label: '请写出含有反义字的四字成语。 - 例如:“不日而月”', value: 'chengyu' }, - { label: '请根据图片书写正确的成语。', value: 'fanyi' }, - { label: '近义词挑战', value: 'jinyi' }, - { label: '看图猜字', value: 'kantu' }, - { label: '古诗词接龙', value: 'gushi' }, - { label: '名句听写', value: 'mingju' }, - { label: '极速成语接龙 - 每题10秒', value: 'speed10' }, - { label: '极速成语接龙 - 每题20秒', value: 'speed20' }, - ] - // 新增轮次 function addRound(customName?: string, customCount?: number, roundType?: Api.Competition.CompetitionRoundType) { if (!props.modelValue.rounds) { @@ -80,7 +65,7 @@ export function useQuestionConfig(props: any) { roundType: roundType || roundTypeOptions[0].value, questions: Array.from({ length: count }).map(() => ({ id: generateId(), - value: null, + questionId: null, score: 5, time: 30, title: '', @@ -138,7 +123,7 @@ export function useQuestionConfig(props: any) { const question = round.questions[j] const questionPrefix = `${round.name} 第 ${j + 1} 题` - if (!question.value) { + if (!question.questionId) { window.$message?.error(`${questionPrefix}未选择题型`) return false } @@ -181,7 +166,6 @@ export function useQuestionConfig(props: any) { getRoundScore, getRoundTime, totalStats, - questionOptions, addRound, removeRound, addQuestion,