feat(user): 重构用户端竞赛流程,集成后端API并优化交互体验

- 新增 `activityinfo` store 管理活动信息,支持持久化
- 重构 `competition` store,分离数据层与流程控制,新增音效管理
- 新增游戏API模块 (`game.ts`),实现抽题、题目详情、提交结果等接口
- 用户端页面全面对接后端数据:首页轮播展示活动列表,组别/队伍页动态加载,封面页显示题目卡片
- 抽题页改为竹签翻转动画,游戏页集成题目详情与倒计时
- 优化布局组件,支持点击返回首页,统一路由参数传递
- 更新类型定义,支持富文本题目渲染
- 添加 `activityinfo` 拼写检查词条
This commit is contained in:
2026-02-10 08:45:26 +08:00
parent 0fd8ef527a
commit 61a7412fc1
23 changed files with 962 additions and 643 deletions

View File

@ -1,9 +1,12 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
import { useRouterPush } from '@/hooks/common/router'
import { fetchGetTeamListByGroupId } from '@/service/api/competition'
const { routerPushByKey } = useRouterPush()
const { routerPushByKey, routerBack } = useRouterPush()
const route = useRoute()
interface TeamItem {
id: number
@ -11,17 +14,40 @@ interface TeamItem {
icon: string
}
const teams = ref<TeamItem[]>([
{ id: 1, name: '1号代表队', icon: 'star' },
{ id: 2, name: '2号代表队', icon: 'star' },
{ id: 3, name: '3号代表队', icon: 'star' },
{ id: 4, name: '4号代表队', icon: 'star' },
])
const teams = ref<TeamItem[]>([])
const loading = ref(false)
const activityId = route.query.activityId
const groupsId = route.query.groupsId
async function getTeams() {
if (!groupsId)
return
loading.value = true
try {
const { data, error } = await fetchGetTeamListByGroupId(Number(groupsId))
if (error) {
window?.$message?.error(error.message)
return
}
const list = data?.data || []
if (list && Array.isArray(list)) {
teams.value = list.map((item: any) => ({
id: item.Id,
name: item.Name,
icon: 'star', // Default icon
}))
}
}
finally {
loading.value = false
}
}
const showContent = ref(false)
function handleBack() {
routerPushByKey('user_groups')
routerBack()
}
function handleNext() {
@ -31,12 +57,13 @@ function handleNext() {
function selectTeam(_id: number) {
// 选择队伍逻辑
routerPushByKey('user_cover', { query: { id: _id.toString() } })
routerPushByKey('user_cover', { query: { activityId, groupsId, teamId: _id } })
}
onMounted(() => {
setTimeout(() => {
showContent.value = true
getTeams()
}, 100)
})
</script>
@ -61,8 +88,7 @@ onMounted(() => {
<TransitionGroup name="list-anim" tag="div" class="teams-grid">
<div
v-for="(item, index) in teams" v-show="showContent" :key="item.id" class="team-item"
:style="{ '--delay': `${index * 0.1}s` }"
@click="selectTeam(item.id)"
:style="{ '--delay': `${index * 0.1}s` }" @click="selectTeam(item.id)"
>
<div class="icon-wrapper">
<img src="@/assets/imgs/user/star-icon.svg" alt="team-icon" class="h-full w-full object-cover">