feat(user): 重构用户端竞赛流程并添加新页面
- 将原单页竞赛流程拆分为独立路由页面(首页、封面、规则、抽题、答题、图解、组别、队伍) - 新增 Pinia store 管理竞赛状态、计时器和音频控制 - 添加 SCSS 变量文件定义主题色 - 更新路由配置和类型定义以支持新页面结构 - 重构 QuestionRenderer 组件优化样式 - 添加图标依赖并更新国际化配置 - 移动图片资源到用户目录并清理旧文件
This commit is contained in:
152
apps/admin/src/views/user/game/index.vue
Normal file
152
apps/admin/src/views/user/game/index.vue
Normal file
@ -0,0 +1,152 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
|
||||
import { useRouterPush } from '@/hooks/common/router'
|
||||
import { useCompetitionStore } from '@/store/modules/competition'
|
||||
// import { uiTemplatesMapping } from '@/views/user/data/mock'
|
||||
import QuestionRenderer from '../modules/QuestionRenderer.vue'
|
||||
|
||||
const { routerPushByKey } = useRouterPush()
|
||||
|
||||
const store = useCompetitionStore()
|
||||
|
||||
// 本地倒计时控制(为了在页面上显示“开始答题”还是倒计时)
|
||||
const isStarted = ref(false)
|
||||
|
||||
const currentNode = computed(() => store.currentNode)
|
||||
const currentQuestion = computed(() => store.currentQuestion)
|
||||
const timeLeft = computed(() => store.timeLeft)
|
||||
|
||||
const formattedTime = computed(() => {
|
||||
const m = Math.floor(timeLeft.value / 60)
|
||||
const s = timeLeft.value % 60
|
||||
const mm = m < 10 ? `0${m}` : m
|
||||
const ss = s < 10 ? `0${s}` : s
|
||||
return `倒计时 ${mm}:${ss}`
|
||||
})
|
||||
|
||||
// Mock chart data
|
||||
const chartData = [
|
||||
{ group: '第一组', total: 42, correct: 36 },
|
||||
{ group: '第二组', total: 20, correct: 16 },
|
||||
{ group: '第三组', total: 19, correct: 19 },
|
||||
{ group: '第四组', total: 17, correct: 14 },
|
||||
]
|
||||
|
||||
function handleBack() {
|
||||
store.stopTimer()
|
||||
routerPushByKey('user_draw')
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
if (!isStarted.value) {
|
||||
// 点击开始答题
|
||||
isStarted.value = true
|
||||
store.startTimer()
|
||||
}
|
||||
else {
|
||||
// 答题中,点击直接进入下一页(或者也可以设计为暂停等,这里按原逻辑是直接跳过)
|
||||
store.stopTimer()
|
||||
routerPushByKey('user_analysis')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CompetitionLayout
|
||||
action-position="top"
|
||||
:show-back="true"
|
||||
:show-next="true"
|
||||
:title="currentNode?.moduleName || ''"
|
||||
back-btn-text="返回"
|
||||
:next-btn-text="isStarted ? formattedTime : '开始答题'"
|
||||
:next-disabled="isStarted"
|
||||
btn-theme="light"
|
||||
@back="handleBack"
|
||||
@next="handleNext"
|
||||
>
|
||||
<div class="relative h-full w-full flex flex-col items-center px-12 font-sans">
|
||||
<!-- 题目说明 -->
|
||||
<!-- <div v-if="currentNode" class="m-2 text-2xl text-gray-800 font-medium">
|
||||
{{ currentNode.description }}
|
||||
</div> -->
|
||||
|
||||
<!-- 题目内容区域 -->
|
||||
<div v-if="currentNode && currentQuestion" class="flex-2 mb-4 mt-12 w-full flex items-center justify-center">
|
||||
<QuestionRenderer
|
||||
:template="currentNode.uiTemplate" :content="currentQuestion.content"
|
||||
class="origin-center scale-150 transform"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 隐藏的 Next 按钮 (方便演示:右上角小区域) -->
|
||||
<div class="absolute right-0 top-0 z-50 h-20 w-20 cursor-pointer" title="Next Step (Debug)" @click="handleNext">
|
||||
<div class="h-full w-full flex items-center justify-center">
|
||||
<div class="h-6 w-6 rotate-45 bg-blue-500">
|
||||
<icon-ic:baseline-arrow-right-alt class="text-icon text-white" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部图表区域 -->
|
||||
<div
|
||||
class="relative z-20 h-64 max-w-5xl w-full flex flex-col border-2 border-blue-300 rounded-xl bg-white/90 p-4 shadow-lg backdrop-blur"
|
||||
>
|
||||
<!-- 图表标题栏 -->
|
||||
<div class="mb-4 flex items-center justify-between px-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="h-3 w-3 rotate-45 bg-orange-400" />
|
||||
<span class="text-lg text-blue-800 font-bold">答题进度</span>
|
||||
</div>
|
||||
<div class="flex gap-6 text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="h-3 w-3 rounded-full bg-blue-400" />
|
||||
<span class="text-gray-600">答题数量</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<div class="h-3 w-3 rounded-full bg-orange-300" />
|
||||
<span class="text-gray-600">正确数量</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 柱状图 -->
|
||||
<div class="flex flex-1 items-end justify-around px-8 pb-2">
|
||||
<div v-for="item in chartData" :key="item.group" class="w-16 flex flex-col items-center gap-2">
|
||||
<div class="h-32 flex items-end gap-2">
|
||||
<!-- Blue Bar -->
|
||||
<div
|
||||
class="relative w-6 rounded-t-md from-blue-500 to-blue-300 bg-gradient-to-t transition-all duration-1000"
|
||||
:style="{ height: `${item.total * 2}px` }"
|
||||
>
|
||||
<span class="absolute left-1/2 text-blue-800 font-bold -top-6 -translate-x-1/2">{{ item.total
|
||||
}}</span>
|
||||
</div>
|
||||
<!-- Orange Bar -->
|
||||
<div
|
||||
class="relative w-6 rounded-t-md from-orange-400 to-orange-200 bg-gradient-to-t transition-all duration-1000"
|
||||
:style="{ height: `${item.correct * 2}px` }"
|
||||
>
|
||||
<span class="absolute left-1/2 text-orange-600 font-bold -top-6 -translate-x-1/2">{{ item.correct
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-blue-800 font-bold">
|
||||
{{ item.group }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CompetitionLayout>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Question Renderer Override if needed */
|
||||
:deep(.question-content) {
|
||||
font-size: 4rem;
|
||||
color: #ef4444;
|
||||
/* red-500 */
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user