Files
reader-star/apps/admin/src/store/modules/competition.ts
rjl b71a758474 feat(user): 重构用户端竞赛流程并添加新页面
- 将原单页竞赛流程拆分为独立路由页面(首页、封面、规则、抽题、答题、图解、组别、队伍)
- 新增 Pinia store 管理竞赛状态、计时器和音频控制
- 添加 SCSS 变量文件定义主题色
- 更新路由配置和类型定义以支持新页面结构
- 重构 QuestionRenderer 组件优化样式
- 添加图标依赖并更新国际化配置
- 移动图片资源到用户目录并清理旧文件
2026-02-06 17:45:44 +08:00

130 lines
3.1 KiB
TypeScript

import type { CompetitionData } from '@/views/user/types'
import { defineStore } from 'pinia'
import { computed, ref } from 'vue'
import { useRouterPush } from '@/hooks/common/router'
import { AudioController } from '@/utils/audio'
import { mockData } from '@/views/user/data/mock'
export const useCompetitionStore = defineStore('competition', () => {
const { routerPushByKey } = useRouterPush()
// --- State ---
const data = ref<CompetitionData>(mockData)
const currentStep = ref(0) // Node 索引
const subStep = ref(0) // Question 索引
const timeLeft = ref(0)
const timerInterval = ref<number | null>(null)
// 音效控制器 (支持自定义音频文件,例如:{ start: '/audio/start.mp3' })
const audioController = new AudioController()
// --- Getters ---
const currentNode = computed(() => data.value.nodes[currentStep.value])
const currentQuestion = computed(() => {
const questions = currentNode.value?.questions || []
return questions[subStep.value] || questions[0]
})
const isLastQuestionInNode = computed(() => {
if (!currentNode.value)
return true
return subStep.value >= currentNode.value.questions.length - 1
})
const isLastNode = computed(() => {
return currentStep.value >= data.value.nodes.length - 1
})
// --- Actions ---
function initData() {
// 这里可以重置或者重新拉取数据
// data.value = ...
resetProgress()
}
function resetProgress() {
currentStep.value = 0
subStep.value = 0
stopTimer()
}
// --- Audio Helpers ---
// 音频相关逻辑已抽离到 @/utils/audio.ts
/**
* 启动倒计时定时器
*/
function startTimer() {
stopTimer()
if (!currentNode.value)
return
timeLeft.value = currentNode.value.config.timeLimit
audioController.play('start') // 播放开始音效
timerInterval.value = window.setInterval(() => {
if (timeLeft.value > 0) {
timeLeft.value--
// 剩余5秒播放倒计时音效
if (timeLeft.value <= 5) {
audioController.play('tick')
}
}
else {
stopTimer()
// 时间到,自动进入解析页
routerPushByKey('user_analysis')
}
}, 1000)
}
function stopTimer() {
if (timerInterval.value) {
clearInterval(timerInterval.value)
timerInterval.value = null
}
}
/**
* 进入下一题或下一环节
* @returns 返回下一步的路由名称,方便组件跳转
*/
function nextStep() {
stopTimer()
// A. 如果当前环节还有题
if (!isLastQuestionInNode.value) {
subStep.value++
// 回到抽题页
return 'user_draw'
}
// B. 当前环节结束,去下一个环节
else if (!isLastNode.value) {
currentStep.value++
subStep.value = 0
// 回到抽题页(新环节)
return 'user_draw'
}
// C. 全部结束
else {
resetProgress()
return 'user_cover'
}
}
return {
data,
currentStep,
subStep,
timeLeft,
currentNode,
currentQuestion,
initData,
startTimer,
stopTimer,
nextStep,
}
})