feat: 新增题目导入服务并优化用户端答题流程
- 新增 question-importer 服务,支持从 JSON 文件批量导入题目到题库 - 重构用户端答题流程,整合抽题、答题、结果分析页面状态管理 - 将题目分类从 UI 模板类型切换为业务分类(question_category_name) - 在题库管理页面支持题目批量选择和删除功能 - 优化用户端组别选择,增加“已结束”状态提示和禁用逻辑 - 修复题目新增/更新 API 请求参数格式问题 - 为富文本编辑器配置排除不必要的工具栏按键
This commit is contained in:
@ -13,9 +13,13 @@ const { routerPushByKey } = useRouterPush()
|
||||
|
||||
const store = useCompetitionStore()
|
||||
const activityInfoStore = useActivityInfoStore()
|
||||
const { activityId, groupId, activityInfo } = storeToRefs(activityInfoStore)
|
||||
|
||||
// 直接从 Store 解构所需状态,保持单一数据源
|
||||
const { activityId, groupId, activityInfo, teamId, teamIds } = storeToRefs(activityInfoStore)
|
||||
const currentQuestionInfo = computed(() => store.currentQuestionInfo)
|
||||
const currentQuestionDetail = computed(() => store.currentQuestionDetail)
|
||||
const currentQuestionMainInfo = computed(() => store.currentQuestionMainInfo)
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
// 本地倒计时控制(为了在页面上显示“开始答题”还是倒计时)
|
||||
@ -24,6 +28,11 @@ const isStarted = ref(false)
|
||||
console.log(activityInfo.value, 'activityInfo.value')
|
||||
console.log(currentQuestionInfo.value, 'currentQuestionInfo.value')
|
||||
console.log(currentQuestionDetail.value, 'currentQuestionDetail.value')
|
||||
console.log(currentQuestionMainInfo.value, 'currentQuestionMainInfo.value')
|
||||
|
||||
const mainTitle = computed(() => currentQuestionMainInfo.value?.QuestionList?.Name || '')
|
||||
|
||||
// const subTitle = computed(() => currentQuestionMainInfo.value?.QuestionList?.QuestionContent || '')
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchGetQuestionDetailData()
|
||||
@ -33,7 +42,14 @@ onMounted(async () => {
|
||||
watch(() => store.timeLeft, (newVal) => {
|
||||
if (newVal === 0 && isStarted.value) {
|
||||
store.stopTimer()
|
||||
routerPushByKey('user_analysis')
|
||||
routerPushByKey('user_analysis', {
|
||||
query: {
|
||||
activityId: String(activityId.value),
|
||||
groupsId: String(groupId.value),
|
||||
teamId: String(teamId.value),
|
||||
teamIds: teamIds.value.join(','),
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@ -69,7 +85,14 @@ const chartData = [
|
||||
|
||||
function handleBack() {
|
||||
store.stopTimer()
|
||||
routerPushByKey('user_draw')
|
||||
routerPushByKey('user_draw', {
|
||||
query: {
|
||||
activityId: String(activityId.value),
|
||||
groupsId: String(groupId.value),
|
||||
teamId: String(teamId.value),
|
||||
teamIds: teamIds.value.join(','),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,12 +103,15 @@ async function fetchGetQuestionDetailData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const currentActivityId = String(activityId.value || '')
|
||||
const questionId = store.currentQuestionInfo.QuestionID || (store.currentQuestionInfo as any).Id
|
||||
const questionId = store.currentQuestionInfo?.QuestionID || (store.currentQuestionInfo as any).Id
|
||||
const { data: detailData } = await fetchGetQuestionDetail(currentActivityId, questionId)
|
||||
console.log(detailData, 'detailData')
|
||||
if (detailData && detailData.data) {
|
||||
store.setCurrentQuestionDetail(detailData.data)
|
||||
}
|
||||
else {
|
||||
window?.$message?.error('题目详情不存在,请联系管理员')
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to fetch question detail', error)
|
||||
@ -126,7 +152,14 @@ async function handleNext() {
|
||||
else {
|
||||
// 备用逻辑:如果已经在答题中(理论上不会触发,因为上面已经跳转),直接跳转
|
||||
store.stopTimer()
|
||||
routerPushByKey('user_analysis')
|
||||
routerPushByKey('user_analysis', {
|
||||
query: {
|
||||
activityId: String(activityId.value),
|
||||
groupsId: String(groupId.value),
|
||||
teamId: String(teamId.value),
|
||||
teamIds: teamIds.value.join(','),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,18 +180,19 @@ async function fetchGetGameStatisticsData(_params: Api.Competition.QuestionAddPa
|
||||
|
||||
<template>
|
||||
<CompetitionLayout
|
||||
action-position="top" :show-back="false" :show-next="true"
|
||||
:title="store.currentQuestionInfo?.ActitvityQuestionName || ''" back-btn-text="返回"
|
||||
:next-btn-text="isStarted ? formattedTime : '开始答题'" :next-disabled="false" btn-theme="light" @back="handleBack"
|
||||
@next="handleNext"
|
||||
action-position="top" :show-back="false" :show-next="true" :title="mainTitle || ''"
|
||||
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 overflow-hidden px-12 font-sans">
|
||||
<!-- 题目说明 -->
|
||||
<!-- <p>{{ store.currentQuestionInfo.ActitvityQuestionName }}</p> -->
|
||||
<!-- <p class="mt-8px text-3xl">
|
||||
{{ subTitle }}
|
||||
</p> -->
|
||||
<!-- 题目内容区域 -->
|
||||
<div v-if="template && content" class="w-full flex flex-1 items-center justify-center overflow-hidden py-4">
|
||||
<div class="max-h-full max-w-full flex items-center justify-center">
|
||||
<QuestionRenderer :template="template" :content="content" class="origin-center scale-150 transform" />
|
||||
<div class="max-h-full max-w-full">
|
||||
<QuestionRenderer class="origin-center scale-150 transform" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user