import { computed, nextTick } from 'vue' import { useDict } from '@/hooks/business/useDict' const { options: roundTypeOptions } = useDict('round_type') const { options: scoreTypeOptions } = useDict('score_type') export interface QuestionItem extends Partial { id: string } export interface RoundModel { id: string name: string questions: QuestionItem[] } function generateId() { return `q_${Date.now()}_${Math.random().toString(36).slice(2, 9)}` } export function useQuestionConfig(props: any) { // 计算每个轮次的分数 const getRoundScore = (roundIndex: number) => { const round = props.modelValue?.rounds?.[roundIndex] if (!round) return 0 return round.questions.reduce((sum: number, item: any) => sum + (Number(item.Point) || 0), 0) } // 计算每个轮次的时长 const getRoundTime = (roundIndex: number) => { const round = props.modelValue?.rounds?.[roundIndex] if (!round) return 0 return round.questions.reduce((sum: number, item: any) => sum + (Number(item.QuestionTime) || 0), 0) } // 总计统计 const totalStats = computed(() => { const rounds = props.modelValue?.rounds || [] return rounds.reduce((acc: any, round: any) => { acc.count += round.questions.length acc.score += round.questions.reduce((sum: number, item: any) => sum + (Number(item.Point) || 0), 0) acc.time += round.questions.reduce((sum: number, item: any) => sum + (Number(item.QuestionTime) || 0), 0) return acc }, { count: 0, score: 0, time: 0 }) }) // 拖拽结束处理:根据拖动意图重新分组 function handleDragEnd(roundIndex: number, event: any) { nextTick(() => { const round = props.modelValue.rounds[roundIndex] if (!round || !round.questions) return const { newIndex } = event const questions = round.questions const draggedItem = questions[newIndex] if (!draggedItem) return // 统一转字符串进行比较,兼容 number/string const targetQid = String(draggedItem.QuestionID) // 1. 分离目标组和其他元素 const targetItems = questions.filter((q: any) => String(q.QuestionID) === targetQid) const nonTargetItems = questions.filter((q: any) => String(q.QuestionID) !== targetQid) // 2. 计算插入位置 // 我们需要找到拖拽元素在非目标元素中的“相对位置” // 如果它被拖到了同组元素中间,我们应该将其归并到该组的后面(保持组的完整性) let prevNonTargetIndex = -1 // 从 newIndex 向前寻找第一个非目标元素 for (let i = newIndex - 1; i >= 0; i--) { if (String(questions[i].QuestionID) !== targetQid) { // 找到这个非目标元素在 nonTargetItems 中的索引 const prevItem = questions[i] prevNonTargetIndex = nonTargetItems.indexOf(prevItem) break } } let insertionIndex = 0 if (prevNonTargetIndex === -1) { // 如果前面没有非目标元素,说明插到了最前面 insertionIndex = 0 } else { // 如果前面有非目标元素,检查它所属的组 const prevItem = nonTargetItems[prevNonTargetIndex] const prevGroupId = String(prevItem.QuestionID) // 找到该组在 nonTargetItems 中的最后一个元素的位置 // 这样做是为了确保如果插入到了某组中间,会跳过该组剩余元素,插在该组之后 let lastIndexInGroup = prevNonTargetIndex for (let i = prevNonTargetIndex + 1; i < nonTargetItems.length; i++) { if (String(nonTargetItems[i].QuestionID) === prevGroupId) { lastIndexInGroup = i } else { // 遇到不同组,停止 break } } insertionIndex = lastIndexInGroup + 1 } // 3. 重组数组 round.questions = [ ...nonTargetItems.slice(0, insertionIndex), ...targetItems, ...nonTargetItems.slice(insertionIndex), ] }) } // 题目类型变更处理:按首次出现顺序分组 function handleTypeChange(roundIndex: number) { const round = props.modelValue.rounds[roundIndex] if (!round || !round.questions) return const questions = round.questions const groups = new Map() const groupOrder: string[] = [] // 记录分组和顺序 for (const q of questions) { const qid = String(q.QuestionID) if (!groups.has(qid)) { groups.set(qid, []) groupOrder.push(qid) } groups.get(qid)?.push(q) } // 重组 const newQuestions: any[] = [] for (const qid of groupOrder) { const group = groups.get(qid) if (group) { newQuestions.push(...group) } } round.questions = newQuestions } // 新增轮次(现在逻辑为:根据 roundType 添加题目到对应分组,如果分组不存在则创建) function addRound(customName?: string, customCount?: number, roundType?: Api.Competition.CompetitionRoundType) { if (!props.modelValue.rounds) { props.modelValue.rounds = [] } const type = roundType || roundTypeOptions.value?.[0]?.value || 0 const count = typeof customCount === 'number' ? customCount : 10 // 查找是否已存在该类型的轮次 const existingRound = props.modelValue.rounds.find((r: any) => r.roundType === type) const newQuestions = Array.from({ length: count }).map(() => ({ id: generateId(), QuestionID: null, Point: 5, QuestionTime: 30, ActitvityQuestionName: '', QuestionRule: scoreTypeOptions.value?.[0]?.value || 0, })) if (existingRound) { existingRound.questions.push(...newQuestions) handleTypeChange(props.modelValue.rounds.indexOf(existingRound)) } else { // 获取类型名称作为默认名称 const typeLabel = roundTypeOptions.value?.find(opt => opt.value === type)?.label || '未知类型' props.modelValue.rounds.push({ id: `round_${Date.now()}`, name: typeLabel, roundType: type, questions: newQuestions, // 新创建时全是空的,不需要排序,或者排一下也无妨 }) } } // 删除轮次 function removeRound(index: number) { props.modelValue.rounds.splice(index, 1) } // 新增题目 function addQuestion(roundIndex: number) { const round = props.modelValue.rounds[roundIndex] if (round) { round.questions.push({ id: generateId(), QuestionID: 0, Point: 5, QuestionTime: 30, ActitvityQuestionName: '', QuestionRule: 1, }) handleTypeChange(roundIndex) } } // 删除题目 function removeQuestion(roundIndex: number, questionIndex: number) { const round = props.modelValue.rounds[roundIndex] if (round) { round.questions.splice(questionIndex, 1) } } function validate() { const rounds = props.modelValue?.rounds || [] if (rounds.length === 0) { window.$message?.error('请至少配置一个比赛环节') return false } for (let i = 0; i < rounds.length; i++) { const round = rounds[i] if (round.questions.length === 0) { window.$message?.error(`${round.name} 至少需要配置一道题目`) return false } for (let j = 0; j < round.questions.length; j++) { const question = round.questions[j] const questionPrefix = `${round.name} 第 ${j + 1} 题` if (question.QuestionID === undefined || question.QuestionID === null || question.QuestionID === '') { window.$message?.error(`${questionPrefix}未选择题型`) return false } if (question.UIType === undefined || question.UIType === null || question.UIType === '') { window.$message?.error(`${questionPrefix}未选择UI类型`) return false } if (question.TemplateID === undefined || question.TemplateID === null || question.TemplateID === '') { window.$message?.error(`${questionPrefix}未选择模板`) return false } if (question.QuestionTime === undefined || question.QuestionTime === null || question.QuestionTime === '') { window.$message?.error(`${questionPrefix}未设置答题时间`) return false } if (question.QuestionRule === undefined || question.QuestionRule === null || question.QuestionRule === '') { window.$message?.error(`${questionPrefix}未设置分数规则`) return false } if (question.ActitvityQuestionName === undefined || question.ActitvityQuestionName === null || question.ActitvityQuestionName === '') { window.$message?.error(`${questionPrefix}未填写标题`) return false } if (question.Point === null || question.Point === undefined) { window.$message?.error(`${questionPrefix}未设置分数`) return false } } } return true } function reset() { // 默认重置为1个轮次,10个题目 props.modelValue.rounds = [] addRound() } return { getRoundScore, getRoundTime, totalStats, addRound, removeRound, addQuestion, handleDragEnd, handleTypeChange, removeQuestion, validate, reset, } }