feat: 新增题目导入服务并优化用户端答题流程

- 新增 question-importer 服务,支持从 JSON 文件批量导入题目到题库
- 重构用户端答题流程,整合抽题、答题、结果分析页面状态管理
- 将题目分类从 UI 模板类型切换为业务分类(question_category_name)
- 在题库管理页面支持题目批量选择和删除功能
- 优化用户端组别选择,增加“已结束”状态提示和禁用逻辑
- 修复题目新增/更新 API 请求参数格式问题
- 为富文本编辑器配置排除不必要的工具栏按键
This commit is contained in:
2026-02-11 17:27:08 +08:00
parent 61a7412fc1
commit f3136cd9ad
34 changed files with 4154 additions and 225 deletions

View File

@ -3,20 +3,15 @@ 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 { fetchGetGroupList } from '@/service/api/competition'
import { fetchGetGroupListByActivityID } from '@/service/api/game'
const { routerPushByKey, routerBack } = useRouterPush()
const route = useRoute()
interface GroupItem {
id: number
name: string
icon: string
}
const groups = ref<GroupItem[]>([])
const groups = ref<Api.Competition.ActivityTeamGroup[]>([])
const loading = ref(false)
const activityId = route.query.activityId as string
const isAllEnd = ref(false)
async function getGroups() {
if (!activityId)
@ -24,7 +19,7 @@ async function getGroups() {
loading.value = true
try {
const { data, error } = await fetchGetGroupList(activityId)
const { data, error } = await fetchGetGroupListByActivityID(activityId, 0)
if (error) {
window?.$message?.error(error.message)
return
@ -33,11 +28,14 @@ async function getGroups() {
const list = data?.data || []
if (list && Array.isArray(list)) {
groups.value = list.map((item: any) => ({
id: item.Id,
name: item.Name,
...item,
// id: item.Id,
// name: item.Name,
icon: 'activity', // Default icon since API might not provide one
}))
}
isAllEnd.value = list.every((item: Api.Competition.ActivityTeamGroup) => item.IsEnd)
// console.log(groups.value, 'groups.value')
}
finally {
loading.value = false
@ -51,12 +49,15 @@ function handleBack() {
}
function handleNext() {
routerPushByKey('user_teams')
routerPushByKey('user')
}
function selectGroup(_id: number) {
function selectGroup(item: Api.Competition.ActivityTeamGroup) {
if (item.IsEnd)
return
// 选择组别逻辑
routerPushByKey('user_teams', { query: { activityId, groupsId: _id } })
routerPushByKey('user_teams', { query: { activityId, groupsId: item.Id } })
}
onMounted(() => {
@ -70,7 +71,8 @@ onMounted(() => {
<template>
<CompetitionLayout
:show-back="true"
:show-next="false"
:show-next="isAllEnd"
next-btn-text="查看队伍结果"
title="展示组别"
@back="handleBack"
@next="handleNext"
@ -81,10 +83,11 @@ onMounted(() => {
<div
v-for="(item, index) in groups"
v-show="showContent"
:key="item.id"
:key="item.Id"
class="group-item"
:class="{ 'is-disabled': item.IsEnd }"
:style="{ '--delay': `${index * 0.05}s` }"
@click="selectGroup(item.id)"
@click="selectGroup(item)"
>
<div class="icon-wrapper">
<div class="flower-bg" />
@ -94,7 +97,7 @@ onMounted(() => {
</div>
<div class="group-name-tag">
<span class="sun-icon"></span>
{{ item.name }}
{{ item.Name }}
</div>
</div>
</TransitionGroup>
@ -133,6 +136,16 @@ onMounted(() => {
margin: 0;
margin-bottom: 30px;
&.is-disabled {
opacity: 0.6;
cursor: not-allowed;
filter: grayscale(100%);
&:hover {
transform: none;
}
}
&:hover {
transform: translateY(-5px);
}