feat(admin): 新增用户端答题流程页面与相关功能模块

- 新增用户端答题流程主页面,包含封面、介绍、答题、答题图解四个状态
- 新增用户端组件:封面、介绍页、答题页、答题图解页、题目渲染器及通用布局组件
- 新增用户端类型定义、模拟数据及业务常量
- 新增用户路由及类型声明,包含组别和队伍选择页面
- 新增管理端首页模块:数据卡片、头部横幅、折线图、饼图、创意横幅和项目新闻
- 新增富文本编辑器组件,支持图片和视频上传至OSS
- 优化题库分类树,改为扁平化列表结构
- 完善比赛创建功能,增加轮次类型、题目分数类型及关联AP字段
- 修复模板删除函数调用参数错误
- 添加 pinyin-pro 依赖以支持拼音处理功能
This commit is contained in:
2026-02-05 18:00:11 +08:00
parent ce5ee2a289
commit ca42c6a876
49 changed files with 3569 additions and 179 deletions

View File

@ -0,0 +1,178 @@
import img from '@/assets/imgs/q5-img.png'
/**
* 模拟数据
* 真实后端数据可能是:
* 活动详情接口传活动id 获取 流程nodes
* 每次可能只返回一个n1前端先展示n1用户点击“抽题”后再请求n2。其中questions 可能不是在n1就返回而是点击抽提时候 传nodeId 去随机获取后前端保存
* 1. 包含 3 个节点N1, N2, N3
* 2. 每个节点包含 2 个批次(汉字听一听, 汉字加一加)
* 3. 每个批次包含 2 个问题Q1, Q2, Q3, Q4, Q5
*/
export const mockData = {
activityInfo: {
name: '星·辞海遨游',
},
nodes: [
{
nodeId: 'N1',
roundTitle: '第一轮',
moduleName: '汉字听一听',
batchName: '根据提示写汉字',
description: '请根据汉语拼音提示书写正确的汉字时间为15秒。',
uiTemplate: 'TEMPLATE_DICTATION_HINT',
config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 1 },
questions: [
{
id: 'Q1',
content: { pinyin: 'jiū,表示小鸟的叫声', hint: '表示小鸟的叫声' },
answer: ['啾'],
},
{
id: 'Q2',
content: { pinyin: 'yāo', hint: '形容草木茂盛美丽' },
answer: ['夭'],
},
],
},
{
nodeId: 'N2',
roundTitle: '第二轮',
moduleName: '汉字听一听',
batchName: '同音字',
description: '诗词理解',
uiTemplate: 'TEMPLATE_DICTATION_HOMOPHONE',
config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 1 },
questions: [
{
id: 'Q3',
content: { mainText: 'táng' },
answer: ['唐', '塘', '糖', '搪', '溏', '瑭', '鄌', '螗', '糖', '堂', '膛', '螳', '鄳', '樘', '镗', '棠', '饧'],
},
{
id: 'Q4',
content: { mainText: 'jù' },
answer: ['剧', '据', '巨', '拒', '聚', '炬', '距', '惧', '具', '俱', '沮', '咀', '矩', '锯'],
},
],
},
{
nodeId: 'N3',
roundTitle: '第二轮',
moduleName: '诗词理解',
batchName: '选择题',
description: '请选择正确的选项时间为30秒。',
uiTemplate: 'TEMPLATE_POETRY_MULTIPLE_CHOICE',
config: { timeLimit: 30, questionCount: 2, scorePerQuestion: 2 },
questions: [
{
id: 'Q3',
content: {
questionText: '《闻王昌龄左迁龙标遥有此寄》中,“左迁”的意思是()',
options: [
{ label: 'A', text: '贬官,降职', image: img },
{ label: 'B', text: '搬家', image: img },
{ label: 'C', text: '一路向西游玩', image: img },
],
},
answer: ['A'],
},
{
id: 'Q4',
content: {
questionText: '《静夜思》中,“举头望明月”的下一句是()',
options: [
{ label: 'A', text: '低头思故乡', image: img },
{ label: 'B', text: '疑是地上霜', image: img },
],
},
answer: ['A'],
},
],
},
{
nodeId: 'N4',
roundTitle: '第三轮',
moduleName: '汉字加一加',
batchName: '部件组合',
description: '请写出含有指定部件的汉字时间为60秒。',
uiTemplate: 'TEMPLATE_COMPONENT_ADD',
config: { timeLimit: 5, questionCount: 1, scorePerQuestion: 1 },
questions: [
{
id: 'Q5',
content: { component: '车' },
answer: ['辆', '轿', '转', '软', '轮', '辐', '输', '轨', '轩', '轻', '轶', '辄', '轴', '轧', '轼', '辊', '辕', '辙', '辍', '辗', '轸', '轭', '辚', '辖', '较', '辘', '轫', '轵', '辋', '轲', '轺', '臻', '软', '辌', '轷', '轻', '辁', '辒', '蚺', '韫', '轱', '辂', '轻', '轹', '辅', '舻', '辐', '斩', '连', '阵', '军', '轰', '库', '辈', '载', '辈', '晕', '辔'],
},
],
},
{
nodeId: 'N5',
roundTitle: '第四轮',
moduleName: '词语听写',
batchName: '拼音写词',
description: '请根据汉语拼音提示书写正确的词语时间为30秒。',
uiTemplate: 'TEMPLATE_WORD_DICTATION',
config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 2 },
questions: [
{
id: 'Q6',
content: { pinyin: 'zhì rè', hint: '形容温度极高' },
answer: ['炙热'],
},
{
id: 'Q7',
content: { pinyin: 'hào hàn', hint: '形容广大繁多' },
answer: ['浩瀚'],
},
],
},
{
nodeId: 'N6',
roundTitle: '第五轮',
moduleName: '成语写一写',
batchName: '文字要求',
description: '请根据要求书写正确的成语时间为60秒。',
uiTemplate: 'TEMPLATE_IDIOM_TEXT_REQ',
config: { timeLimit: 5, questionCount: 1, scorePerQuestion: 1 },
questions: [
{
id: 'Q8',
content: { requirement: '请写出含有反义字的四字成语。' },
answer: ['七上八下', '前因后果', '大同小异', '东张西望', '左顾右盼', '南辕北辙', '深入浅出', '出生入死', '死去活来', '天翻地覆', '黑白分明'],
},
],
},
{
nodeId: 'N7',
roundTitle: '第六轮',
moduleName: '成语写一写',
batchName: '看图写成语',
description: '请根据图片书写正确的成语时间为30秒。',
uiTemplate: 'TEMPLATE_IDIOM_IMAGE',
config: { timeLimit: 30, questionCount: 2, scorePerQuestion: 2 },
questions: [
{
id: 'Q9',
content: { hint: '比喻事情已定', image: img },
answer: ['板上钉钉'],
},
{
id: 'Q10',
content: { hint: '比喻事情已定', image: img },
answer: ['板上钉钉'],
},
],
},
],
}
export const uiTemplatesMapping = {
TEMPLATE_DICTATION_HINT: '汉字听写-提示',
TEMPLATE_DICTATION_HOMOPHONE: '汉字听写-同音',
TEMPLATE_COMPONENT_ADD: '汉字加一加',
TEMPLATE_WORD_DICTATION: '词语听写',
TEMPLATE_IDIOM_TEXT_REQ: '成语-文字要求',
TEMPLATE_IDIOM_IMAGE: '成语-看图',
TEMPLATE_POETRY_MULTIPLE_CHOICE: '诗词理解-选择题',
}

View File

@ -15,6 +15,40 @@ export const mockData: CompetitionData = {
name: '星·辞海遨游',
},
nodes: [
{
nodeId: 'N3',
roundTitle: '第二轮',
moduleName: '诗词理解',
batchName: '选择题',
description: '请选择正确的选项时间为30秒。',
uiTemplate: 'TEMPLATE_POETRY_MULTIPLE_CHOICE',
config: { timeLimit: 300, questionCount: 2, scorePerQuestion: 2 },
questions: [
{
id: 'Q3',
content: {
title: '《闻王昌龄左迁龙标遥有此寄》中,“左迁”的意思是()',
options: [
{ label: 'A', text: '贬官,降职', image: img },
{ label: 'B', text: '搬家', image: img },
{ label: 'C', text: '一路向西游玩', image: img },
],
},
answer: ['A'],
},
{
id: 'Q4',
content: {
title: '《静夜思》中,“举头望明月”的下一句是()',
options: [
{ label: 'A', text: '低头思故乡', image: img },
{ label: 'B', text: '疑是地上霜', image: img },
],
},
answer: ['A'],
},
],
},
{
nodeId: 'N1',
roundTitle: '第一轮',
@ -26,12 +60,18 @@ export const mockData: CompetitionData = {
questions: [
{
id: 'Q1',
content: { pinyin: 'jiū,表示小鸟的叫声', hint: '表示小鸟的叫声' },
content: {
title: 'jiū,表示小鸟的叫声',
meta: { hint: '表示小鸟的叫声' },
},
answer: ['啾'],
},
{
id: 'Q2',
content: { pinyin: 'yāo', hint: '形容草木茂盛美丽' },
content: {
title: 'yāo',
meta: { hint: '形容草木茂盛美丽' },
},
answer: ['夭'],
},
],
@ -41,24 +81,25 @@ export const mockData: CompetitionData = {
roundTitle: '第二轮',
moduleName: '汉字听一听',
batchName: '同音字',
description: '请根据拼音书写同音字时间为30秒。',
description: '诗词理解',
uiTemplate: 'TEMPLATE_DICTATION_HOMOPHONE',
config: { timeLimit: 5, questionCount: 2, scorePerQuestion: 1 },
questions: [
{
id: 'Q3',
content: { mainText: 'táng' },
content: { title: 'táng' },
answer: ['唐', '塘', '糖', '搪', '溏', '瑭', '鄌', '螗', '糖', '堂', '膛', '螳', '鄳', '樘', '镗', '棠', '饧'],
},
{
id: 'Q4',
content: { mainText: 'jù' },
content: { title: 'jù' },
answer: ['剧', '据', '巨', '拒', '聚', '炬', '距', '惧', '具', '俱', '沮', '咀', '矩', '锯'],
},
],
},
{
nodeId: 'N3',
nodeId: 'N4',
roundTitle: '第三轮',
moduleName: '汉字加一加',
batchName: '部件组合',
@ -68,13 +109,13 @@ export const mockData: CompetitionData = {
questions: [
{
id: 'Q5',
content: { component: '车' },
content: { title: '车' },
answer: ['辆', '轿', '转', '软', '轮', '辐', '输', '轨', '轩', '轻', '轶', '辄', '轴', '轧', '轼', '辊', '辕', '辙', '辍', '辗', '轸', '轭', '辚', '辖', '较', '辘', '轫', '轵', '辋', '轲', '轺', '臻', '软', '辌', '轷', '轻', '辁', '辒', '蚺', '韫', '轱', '辂', '轻', '轹', '辅', '舻', '辐', '斩', '连', '阵', '军', '轰', '库', '辈', '载', '辈', '晕', '辔'],
},
],
},
{
nodeId: 'N4',
nodeId: 'N5',
roundTitle: '第四轮',
moduleName: '词语听写',
batchName: '拼音写词',
@ -84,18 +125,24 @@ export const mockData: CompetitionData = {
questions: [
{
id: 'Q6',
content: { pinyin: 'zhì rè', hint: '形容温度极高' },
content: {
title: 'zhì rè',
meta: { hint: '形容温度极高' },
},
answer: ['炙热'],
},
{
id: 'Q7',
content: { pinyin: 'hào hàn', hint: '形容广大繁多' },
content: {
title: 'hào hàn',
meta: { hint: '形容广大繁多' },
},
answer: ['浩瀚'],
},
],
},
{
nodeId: 'N5',
nodeId: 'N6',
roundTitle: '第五轮',
moduleName: '成语写一写',
batchName: '文字要求',
@ -105,13 +152,13 @@ export const mockData: CompetitionData = {
questions: [
{
id: 'Q8',
content: { requirement: '请写出含有反义字的四字成语。' },
content: { title: '请写出含有反义字的四字成语。' },
answer: ['七上八下', '前因后果', '大同小异', '东张西望', '左顾右盼', '南辕北辙', '深入浅出', '出生入死', '死去活来', '天翻地覆', '黑白分明'],
},
],
},
{
nodeId: 'N6',
nodeId: 'N7',
roundTitle: '第六轮',
moduleName: '成语写一写',
batchName: '看图写成语',
@ -121,16 +168,47 @@ export const mockData: CompetitionData = {
questions: [
{
id: 'Q9',
content: { hint: '比喻事情已定', image: img },
content: {
title: '',
titleImage: img,
meta: { hint: '比喻事情已定' },
},
answer: ['板上钉钉'],
},
{
id: 'Q10',
content: { hint: '比喻事情已定', image: img },
content: {
title: '',
titleImage: img,
meta: { hint: '比喻事情已定' },
},
answer: ['板上钉钉'],
},
],
},
{
nodeId: 'N8',
roundTitle: '第七轮',
moduleName: '汉字辨析',
batchName: '选字填空',
description: '请选择正确的汉字填入括号中。',
uiTemplate: 'TEMPLATE_POETRY_MULTIPLE_CHOICE',
config: { timeLimit: 20, questionCount: 1, scorePerQuestion: 2 },
questions: [
{
id: 'Q11',
content: {
title: '下列哪个字是“没”的繁体字?',
options: [
{ label: 'A', text: '没', renderType: 'tianzige' },
{ label: 'B', text: '沒', renderType: 'tianzige' },
{ label: 'C', text: '殁', renderType: 'tianzige' },
],
},
answer: ['B'],
},
],
},
],
}
@ -141,4 +219,5 @@ export const uiTemplatesMapping = {
TEMPLATE_WORD_DICTATION: '词语听写',
TEMPLATE_IDIOM_TEXT_REQ: '成语-文字要求',
TEMPLATE_IDIOM_IMAGE: '成语-看图',
TEMPLATE_POETRY_MULTIPLE_CHOICE: '诗词理解-选择题',
}

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, onUnmounted, ref } from 'vue'
import CompetitionLayout from '@/components/custom/user/CompetitionLayout.vue'
import { mockData } from './data/mock' // 引入模拟数据
import AnswerAnalysisComp from './modules/AnswerAnalysisComp.vue'
import CoverComp from './modules/CoverComp.vue' // 封面组件
@ -104,65 +105,48 @@ onUnmounted(() => {
</script>
<template>
<div class="user-app-container relative h-screen w-full overflow-hidden font-sans">
<!-- 背景图占位 (实际项目中应为 img background-image) -->
<div class="absolute inset-0 z-0 bg-blue-100/50">
<!-- 模拟云纹/山水背景 -->
<div class="absolute bottom-0 h-1/3 w-full from-teal-200/50 to-transparent bg-gradient-to-t" />
<div class="absolute top-0 h-1/4 w-full from-blue-200/50 to-transparent bg-gradient-to-b" />
<CompetitionLayout>
<div class="h-screen w-full overflow-hidden font-sans">
<!-- 主内容区域 -->
<div class="relative z-10 h-full w-full">
<!-- 状态 1: 封面 -->
<CoverComp
v-if="status === 'COVER'"
:title="data.activityInfo.name"
:nodes="data.nodes"
@start="handleStart"
/>
<!-- 状态 2: 介绍页 -->
<IntroComp
v-else-if="status === 'INTRO'"
:node="currentNode"
@back="status = 'COVER'"
@draw="handleDraw"
/>
<!-- 状态 3: 答题页 -->
<GameComp
v-else-if="status === 'GAME'"
:node="currentNode"
:question="currentQuestion"
:sub-step="subStep"
:time-left="timeLeft"
@back="status = 'INTRO'"
@next="handleToAnalysis"
/>
<!-- 状态 4: 答题图解页 -->
<AnswerAnalysisComp
v-else-if="status === 'ANSWER_ANALYSIS'"
:question-id="currentQuestion.id"
@back="status = 'GAME'"
@next="handleNext"
/>
</div>
</div>
<!-- 顶部左右信息 (固定) -->
<div class="absolute left-6 top-4 z-20 text-lg text-gray-700 font-medium tracking-wide">
教育学会
</div>
<div class="absolute right-6 top-4 z-20 text-lg text-gray-700 font-medium tracking-wide">
树人研究院
</div>
<!-- 主内容区域 -->
<div class="relative z-10 h-full w-full">
<!-- 状态 1: 封面 -->
<CoverComp
v-if="status === 'COVER'"
:title="data.activityInfo.name"
:nodes="data.nodes"
@start="handleStart"
/>
<!-- 状态 2: 介绍页 -->
<IntroComp
v-else-if="status === 'INTRO'"
:node="currentNode"
@back="status = 'COVER'"
@draw="handleDraw"
/>
<!-- 状态 3: 答题页 -->
<GameComp
v-else-if="status === 'GAME'"
:node="currentNode"
:question="currentQuestion"
:sub-step="subStep"
:time-left="timeLeft"
@back="status = 'INTRO'"
@next="handleToAnalysis"
/>
<!-- 状态 4: 答题图解页 -->
<AnswerAnalysisComp
v-else-if="status === 'ANSWER_ANALYSIS'"
:question-id="currentQuestion.id"
@back="status = 'GAME'"
@next="handleNext"
/>
</div>
</div>
</CompetitionLayout>
</template>
<style scoped>
.user-app-container {
/* 基础背景色,防止图片加载失败时太突兀 */
background-color: #eef7ff;
}
</style>

View File

@ -8,8 +8,8 @@ const props = defineProps<{
}>()
const pinyinChars = computed(() => {
if (props.template === 'TEMPLATE_WORD_DICTATION' && props.content.pinyin) {
return props.content.pinyin.split(' ')
if (props.template === 'TEMPLATE_WORD_DICTATION' && props.content.title) {
return props.content.title.split(' ')
}
return []
})
@ -20,24 +20,24 @@ const pinyinChars = computed(() => {
<!-- 模板A: 汉字听写-提示 (拼音+提示) -->
<div v-if="template === 'TEMPLATE_DICTATION_HINT'" class="text-center">
<div class="mb-6 inline-block text-[120px] text-red-500 font-bold leading-none" style="text-shadow: 2px 2px 4px rgba(0,0,0,0.1);">
{{ content.pinyin }}
{{ content.title }}
</div>
<!-- <div class="text-4xl text-gray-800 font-bold">
{{ content.hint }}
{{ content.meta?.hint }}
</div> -->
</div>
<!-- 模板B: 汉字听写-同音字 (大字) -->
<div v-else-if="template === 'TEMPLATE_DICTATION_HOMOPHONE'" class="text-center">
<div class="text-[100px] text-red-600 font-bold">
{{ content.mainText }}
{{ content.title }}
</div>
</div>
<!-- 模板C: 汉字加一加 (提示+部件) -->
<div v-else-if="template === 'TEMPLATE_COMPONENT_ADD'" class="text-center">
<div class="text-[100px] text-red-600 font-bold">
{{ content.component }}
{{ content.title }}
</div>
</div>
@ -71,10 +71,10 @@ const pinyinChars = computed(() => {
<!-- 模板E: 成语-文字要求 -->
<div v-else-if="template === 'TEMPLATE_IDIOM_TEXT_REQ'" class="text-center">
<div class="mb-8 text-4xl text-gray-800 font-bold">
{{ content.requirement }}
{{ content.title }}
</div>
<!-- <div class="rounded bg-gray-100 px-4 py-2 text-xl text-gray-500">
示例{{ content.example }}
示例{{ content.meta?.example }}
</div> -->
</div>
@ -82,15 +82,220 @@ const pinyinChars = computed(() => {
<div v-else-if="template === 'TEMPLATE_IDIOM_IMAGE'" class="flex flex-col items-center text-center">
<div class="mb-6 h-60 w-80 flex items-center justify-center">
<!-- <span class="text-gray-400">图片展示区域<br></span> -->
<img :src="content.image" alt="idiom image" class="h-full w-full object-contain">
<img :src="content.titleImage" alt="idiom image" class="h-full w-full object-contain">
</div>
<!-- <div class="text-xl text-gray-600 font-bold">
提示{{ content.hint }}
提示{{ content.meta?.hint }}
</div> -->
</div>
<!-- 模板G: 诗词理解-选择题 (通用选择题模板) -->
<div v-else-if="template === 'TEMPLATE_POETRY_MULTIPLE_CHOICE'" class="w-full flex flex-col items-center">
<div class="mb-8 text-3xl text-gray-800 font-bold leading-relaxed">
{{ content.title }}
</div>
<div class="options-container">
<div
v-for="(option, index) in content.options"
:key="index"
class="option-card group"
:class="{
'is-mixed': !option.renderType || option.renderType === 'mixed' || option.renderType === 'image',
'is-text': option.renderType === 'text',
}"
>
<!-- 1. 图片渲染模式 -->
<div v-if="option.image" class="option-image-wrapper">
<img :src="option.image" :alt="option.label" class="option-image">
</div>
<!-- 2. 田字格渲染模式 -->
<div v-if="option.renderType === 'tianzige'" class="tianzige-wrapper">
<!-- 米字格背景 -->
<div class="tianzige-bg">
<div class="line-horizontal" />
<div class="line-vertical" />
<svg width="100%" height="100%" class="line-diagonal">
<line x1="0" y1="0" x2="100%" y2="100%" stroke="#f87171" stroke-width="1" stroke-dasharray="4 2" />
<line x1="100%" y1="0" x2="0" y2="100%" stroke="#f87171" stroke-width="1" stroke-dasharray="4 2" />
</svg>
</div>
<span class="tianzige-text">{{ option.text }}</span>
</div>
<!-- 选项标签和文字 -->
<div class="option-content">
<div class="option-label">
{{ option.label }}
</div>
<!-- 如果不是田字格模式显示普通文本 -->
<span v-if="option.renderType !== 'tianzige'" class="option-text">
{{ option.text }}
</span>
</div>
</div>
</div>
</div>
<div v-else class="text-red-500">
未知的题目模板: {{ template }}
</div>
</div>
</template>
<style lang="scss" scoped>
.options-container {
width: 100%;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2rem;
margin-bottom: 3rem;
.option-card {
width: 320px;
height: 260px;
}
}
.option-card {
display: flex;
cursor: pointer;
align-items: center;
justify-content: center;
border: 2px solid transparent;
border-radius: 0.75rem; /* rounded-xl */
background-color: #f9fafb; /* bg-gray-50 */
padding: 1rem; /* p-4 */
transition: all 0.3s;
&:hover {
border-color: #f87171; /* border-red-400 */
background-color: #fef2f2; /* bg-red-50 */
box-shadow:
0 10px 15px -3px rgba(0, 0, 0, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05); /* shadow-lg */
}
&.is-mixed {
flex-direction: column;
}
&.is-text {
flex-direction: row;
gap: 1rem; /* gap-4 */
}
}
.option-image-wrapper {
margin-bottom: 1rem;
height: 9rem;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border-radius: 0.5rem; /* rounded-lg */
padding: 0.5rem; /* p-2 */
}
.option-image {
height: 100%;
width: 100%;
object-fit: contain;
transition: transform 0.3s;
.group:hover & {
transform: scale(1.05);
}
}
.tianzige-wrapper {
position: relative;
margin-bottom: 0.75rem; /* mb-3 */
height: 8rem; /* h-32 */
width: 8rem; /* w-32 */
display: flex;
user-select: none;
align-items: center;
justify-content: center;
border: 2px solid #ef4444; /* border-red-500 */
background-color: white;
}
.tianzige-bg {
pointer-events: none;
position: absolute;
inset: 0;
z-index: 0;
height: 100%;
width: 100%;
.line-horizontal {
position: absolute;
left: 0;
top: 50%;
height: 1px;
width: 100%;
border-top: 1px dashed #f87171; /* border-red-400 */
opacity: 0.6;
}
.line-vertical {
position: absolute;
left: 50%;
top: 0;
height: 100%;
width: 1px;
border-left: 1px dashed #f87171; /* border-red-400 */
opacity: 0.6;
}
.line-diagonal {
position: absolute;
inset: 0;
opacity: 0.6;
}
}
.tianzige-text {
font-family: 'KaiTi', 'STKaiti', serif; /* font-kaaiti */
z-index: 10;
font-size: 3.75rem; /* text-6xl */
font-weight: 700;
color: #1f2937; /* text-gray-800 */
}
.option-content {
display: flex;
align-items: center;
gap: 0.75rem; /* gap-3 */
}
.option-label {
height: 2rem; /* h-8 */
width: 2rem; /* w-8 */
display: flex;
align-items: center;
justify-content: center;
border-radius: 9999px; /* rounded-full */
background-color: #fee2e2; /* bg-red-100 */
color: #dc2626; /* text-red-600 */
font-weight: 700;
.group:hover & {
background-color: #ef4444; /* bg-red-500 */
color: white;
}
}
.option-text {
font-size: 1.25rem; /* text-xl */
color: #374151; /* text-gray-700 */
font-weight: 500;
.group:hover & {
color: #b91c1c; /* text-red-700 */
}
}
</style>

View File

@ -5,15 +5,32 @@ export type UiTemplateType =
| 'TEMPLATE_WORD_DICTATION' // 词语听写 (zhi re)
| 'TEMPLATE_IDIOM_TEXT_REQ' // 成语-文字要求 (反义字)
| 'TEMPLATE_IDIOM_IMAGE' // 成语-看图
| 'TEMPLATE_POETRY_MULTIPLE_CHOICE' // 诗词理解-选择题
export interface QuestionOption {
label: string // A, B, C
text: string // 选项文本/值
image?: string // 选项图片
renderType?: 'text' | 'image' | 'tianzige' | 'mixed' // 关键字段:控制选项渲染样式
}
export interface QuestionContent {
pinyin?: string
hint?: string
mainText?: string // 大字展示
component?: string // 部件
requirement?: string // 题目要求
example?: string // 例子
image?: string // 图片路径
// 核心标准字段 (对应数据库列)
title: string // 题干/主要内容 (原 word, questionText, mainText, pinyin, requirement, component)
titleImage?: string // 题干图片 (原 image)
titleAudio?: string // 题干音频 (原 soundUrl)
options?: QuestionOption[] // 选项列表
// 扩展元数据 (对应数据库 JSON 字段)
meta?: {
hint?: string // 提示
example?: string // 示例
pinyin?: string // 如果 title 是汉字,这里存拼音;反之亦然
definition?: string // 释义
strokeCount?: number // 笔画数
radicals?: string // 部首
[key: string]: any
}
}
export interface Question {