feat(results): 新增实时结果列表和详情页面,支持评委查看和评分

- 添加 results_results-list 和 results_results-detail 路由及页面
- 实现比赛列表展示,支持跳转到实时结果详情
- 在结果详情页面添加分组和题目切换功能
- 集成游戏统计 API,实时获取队伍答题数据
- 添加 TeamResultCard 和 GroupTabs 可复用组件
- 更新国际化配置和类型定义
This commit is contained in:
2026-02-28 08:46:11 +08:00
parent e70409082a
commit 2b50bd2c71
19 changed files with 870 additions and 102 deletions

View File

@ -1,9 +1,10 @@
<script setup lang="ts">
import { NImage } from 'naive-ui'
import { computed, onBeforeUnmount, ref } from 'vue'
import { useRoute } from 'vue-router'
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
import { useRouterPush } from '@/hooks/common/router'
import { fetchCheckNextStep, fetchGetCurrentQuestion } from '@/service/api/game'
import { fetchCheckNextStep, fetchGetCurrentQuestion, fetchGetGameStatistics } from '@/service/api/game'
import { useCompetitionStore } from '@/store/modules/competition'
const store = useCompetitionStore()
@ -15,56 +16,17 @@ const groupsId = computed(() => route.query?.groupsId as string)
const teamId = computed(() => route.query?.teamId as string)
const teamIds = computed(() => route.query?.teamIds as string)
const currentQuestionMainInfo = computed(() => store.currentQuestionMainInfo)
const currentQuestionDetail = computed(() => store.currentQuestionDetail)
const TeamGroup_QuestionID = computed(() => currentQuestionMainInfo.value?.TeamGroup_QuestionID || 0)
const QuestionID = computed(() => currentQuestionMainInfo.value?.Activity_Question.ID || 0)
const QuestionDetaiID = computed(() => currentQuestionDetail.value?.Id || 0)
const zoomedImage = ref<string | null>(null)
const isCompleted = ref(false)
const showNextBtn = ref(false)
// Mock team data
const teams = ref([
{
id: 1,
name: '第一队',
writtenCount: 16,
correctCount: 13,
score: 13,
manualScore: null as number | null,
image: 'https://via.placeholder.com/300x400?text=Team+1+Answer',
},
{
id: 2,
name: '第二队',
writtenCount: 15,
correctCount: 12,
score: 12,
manualScore: null as number | null,
image: 'https://via.placeholder.com/300x400?text=Team+2+Answer',
},
{
id: 3,
name: '第三队',
writtenCount: 18,
correctCount: 15,
score: 15,
manualScore: null as number | null,
image: 'https://via.placeholder.com/300x400?text=Team+3+Answer',
},
{
id: 4,
name: '第四队',
writtenCount: 14,
correctCount: 10,
score: 10,
manualScore: null as number | null,
image: 'https://via.placeholder.com/300x400?text=Team+4+Answer',
},
])
function handleZoom(img: string) {
zoomedImage.value = img
}
const teams = ref<any[]>([])
function handleBack() {
routerPushByKey('user_game', {
@ -127,6 +89,7 @@ async function initQuestions() {
}
const timer = ref<ReturnType<typeof setInterval> | undefined>(undefined)
const statisticsTimer = ref<ReturnType<typeof setInterval> | undefined>(undefined)
/** 定时刷新 检查评委是否已经完成打分,是否可以下一步 fetchCheckNextStep */
async function checkNextStep() {
@ -153,12 +116,34 @@ async function checkNextStep() {
}
}
/** 获取游戏现场统计结果 */
async function fetchGetGameStatisticsData() {
if (store.currentQuestionInfo && store.currentQuestionDetail) {
try {
const _params = {
TeamGroupID: Number(groupsId.value),
QuestionID: Number(QuestionID.value),
QuestionDetaiID: Number(QuestionDetaiID.value),
}
const { data } = await fetchGetGameStatistics(_params)
teams.value = (data?.data as any[]) || []
}
catch (error: any) {
console.error('Failed to fetch game statistics', error)
window.$message?.error(error.message || '获取游戏现场统计结果失败')
}
}
}
// 定时刷新 检查是否可以下一步
timer.value = setInterval(checkNextStep, 2000)
// 定时刷新 游戏统计数据
statisticsTimer.value = setInterval(fetchGetGameStatisticsData, 2000)
/** 销毁 定时刷新 检查是否可以下一步 */
onBeforeUnmount(() => {
clearInterval(timer.value)
clearInterval(statisticsTimer.value)
})
</script>
@ -172,7 +157,7 @@ onBeforeUnmount(() => {
<!-- Content -->
<div class="w-full flex flex-col flex-1 overflow-hidden">
<div class="mb-2 text-xl text-red-600 font-bold">
备注由AI模型评判
备注评委正在评分中请等待
</div>
<div v-if="teams && teams.length > 0" class="grid grid-cols-4 h-full gap-6 pb-8">
@ -180,43 +165,37 @@ onBeforeUnmount(() => {
v-for="team in teams" :key="team.id"
class="flex flex-col border-2 border-gray-200 rounded-xl bg-white/90 p-4 shadow-sm"
>
<div class="mb-2 text-xl text-gray-800 font-bold">
{{ team.name }}
<div
class="mb-3 flex items-center gap-2 rounded-full from-sky-300 to-cyan-200 bg-gradient-to-r px-4 py-2 shadow-sm"
>
<span class="text-lg text-white font-bold drop-shadow">{{ team.TeamName }}</span>
<span class="ml-2 rounded-full bg-white/40 px-2 py-0.5 text-sm">正确 <span class="text-md text-blue">{{
team.correctCount }}</span> </span>
<span class="text-s rounded-full bg-white/40 px-2 py-0.5">积分 <span class="text-md text-blue">{{ team.Points
}}</span></span>
</div>
<!-- Answer Image Area -->
<div
class="relative mb-4 flex-1 cursor-zoom-in overflow-hidden border border-gray-300 rounded bg-gray-50"
@click="handleZoom(team.image)"
class="relative mb-4 flex-1 overflow-hidden border border-gray-300 rounded bg-gray-50"
>
<!-- Grid Background Simulation -->
<div class="pointer-events-none absolute inset-0 grid grid-cols-8 gap-px bg-gray-200 opacity-20">
<div v-for="n in 64" :key="n" class="bg-white" />
</div>
<!-- Simulated Handwriting/Content -->
<div class="absolute inset-0 flex items-center justify-center text-gray-400">
(点击放大查看详情)
<!-- <div v-for="n in 64" :key="n" class="bg-white" /> -->
</div>
<NImage
:src="team.UserAnswerPicture"
class="h-full w-full"
object-fit="contain"
/>
</div>
<!-- Stats & Score -->
<div class="flex flex-col gap-2">
<!-- <div class="flex flex-col gap-2">
<div class="text-sm text-gray-700 font-medium">
写了数量{{ team.writtenCount }}正确{{ team.correctCount }}积分{{ team.score }}
</div>
<div class="flex items-center justify-between">
<span class="text-red-500 font-bold">评委当场评</span>
</div>
<div class="relative">
<input
v-model="team.manualScore" type="number"
class="w-full border border-gray-300 rounded px-3 py-2 text-sm focus:border-blue-500 focus:outline-none"
placeholder="留个输入框,修改正确积分"
>
</div>
</div>
</div> -->
</div>
</div>
</div>