feat: 新增题目导入服务并优化用户端答题流程
- 新增 question-importer 服务,支持从 JSON 文件批量导入题目到题库 - 重构用户端答题流程,整合抽题、答题、结果分析页面状态管理 - 将题目分类从 UI 模板类型切换为业务分类(question_category_name) - 在题库管理页面支持题目批量选择和删除功能 - 优化用户端组别选择,增加“已结束”状态提示和禁用逻辑 - 修复题目新增/更新 API 请求参数格式问题 - 为富文本编辑器配置排除不必要的工具栏按键
This commit is contained in:
@ -1,10 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
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 { useCompetitionStore } from '@/store/modules/competition'
|
||||
|
||||
const store = useCompetitionStore()
|
||||
|
||||
const { routerPushByKey } = useRouterPush()
|
||||
const route = useRoute()
|
||||
const activityId = computed(() => route.query?.activityId as string)
|
||||
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 TeamGroup_QuestionID = computed(() => currentQuestionMainInfo.value?.TeamGroup_QuestionID || 0)
|
||||
|
||||
const zoomedImage = ref<string | null>(null)
|
||||
const isCompleted = ref(false)
|
||||
const showNextBtn = ref(false)
|
||||
|
||||
// Mock team data
|
||||
const teams = ref([
|
||||
@ -51,25 +67,105 @@ function handleZoom(img: string) {
|
||||
}
|
||||
|
||||
function handleBack() {
|
||||
routerPushByKey('user_game')
|
||||
routerPushByKey('user_game', {
|
||||
query: {
|
||||
activityId: activityId.value,
|
||||
groupsId: groupsId.value,
|
||||
teamId: teamId.value,
|
||||
teamIds: teamIds.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function handleNext() {
|
||||
routerPushByKey('user_draw')
|
||||
if (isCompleted.value) {
|
||||
routerPushByKey('user_groups', {
|
||||
query: {
|
||||
activityId: activityId.value,
|
||||
},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
routerPushByKey('user_draw', {
|
||||
query: {
|
||||
activityId: activityId.value,
|
||||
groupsId: groupsId.value,
|
||||
teamId: teamId.value,
|
||||
teamIds: teamIds.value,
|
||||
},
|
||||
})
|
||||
// 清空当前题目信息
|
||||
store.initData()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取题目详情
|
||||
* 检测是否还有题可以抽取,如果data为0,则提示没有题可抽取
|
||||
*/
|
||||
async function initQuestions() {
|
||||
if (!activityId.value)
|
||||
return
|
||||
|
||||
try {
|
||||
const { data: outlineData, error } = await fetchGetCurrentQuestion({
|
||||
ActivityID: Number(activityId.value),
|
||||
GroupID: Number(groupsId.value),
|
||||
RoundType: 0,
|
||||
})
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(outlineData, 'outlineData')
|
||||
if (error) {
|
||||
window?.$message?.error(error.message)
|
||||
}
|
||||
isCompleted.value = outlineData?.data?.IsCompleted || false
|
||||
}
|
||||
catch (error) {
|
||||
window.$message?.error('获取题目失败,请重试')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
const timer = ref<ReturnType<typeof setInterval> | undefined>(undefined)
|
||||
|
||||
/** 定时刷新 检查评委是否已经完成打分,是否可以下一步 fetchCheckNextStep */
|
||||
async function checkNextStep() {
|
||||
if (!activityId.value || !groupsId.value)
|
||||
return
|
||||
|
||||
try {
|
||||
const { data: nextStepData, error } = await fetchCheckNextStep(TeamGroup_QuestionID.value || 0)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(nextStepData, 'nextStepData')
|
||||
if (error) {
|
||||
window?.$message?.error(error.message)
|
||||
}
|
||||
const isUse = nextStepData?.data?.IsUse || false
|
||||
showNextBtn.value = isUse
|
||||
if (isUse) {
|
||||
await initQuestions()
|
||||
clearInterval(timer.value)
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
window.$message?.error('检查是否可以下一步失败,请重试')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
// 定时刷新 检查是否可以下一步
|
||||
timer.value = setInterval(checkNextStep, 2000)
|
||||
|
||||
/** 销毁 定时刷新 检查是否可以下一步 */
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(timer.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<CompetitionLayout
|
||||
:show-title="true"
|
||||
:show-back="false"
|
||||
:show-next="true"
|
||||
title="答题图解"
|
||||
action-position="top"
|
||||
back-btn-text="返回"
|
||||
next-btn-text="继续抽题"
|
||||
btn-theme="light"
|
||||
@back="handleBack"
|
||||
:show-title="true" :show-back="false" :show-next="showNextBtn" title="答题图解" action-position="top"
|
||||
back-btn-text="返回" :next-btn-text="isCompleted ? '该组已完成,回到选组' : '下一题'" btn-theme="light" @back="handleBack"
|
||||
@next="handleNext"
|
||||
>
|
||||
<div class="relative h-full w-full flex flex-col items-center px-12 pt-10 font-sans">
|
||||
@ -115,8 +211,7 @@ function handleNext() {
|
||||
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="team.manualScore"
|
||||
type="number"
|
||||
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="留个输入框,修改正确积分"
|
||||
>
|
||||
@ -128,8 +223,7 @@ function handleNext() {
|
||||
|
||||
<!-- Zoom Modal -->
|
||||
<div
|
||||
v-if="zoomedImage"
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm"
|
||||
v-if="zoomedImage" class="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm"
|
||||
@click="zoomedImage = null"
|
||||
>
|
||||
<div class="relative max-h-[90vh] max-w-[90vw] p-4">
|
||||
|
||||
Reference in New Issue
Block a user