2026-01-23 18:16:03 +08:00
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
|
import type { QuestionContent, UiTemplateType } from '../types'
|
|
|
|
|
|
import { computed } from 'vue'
|
|
|
|
|
|
|
|
|
|
|
|
const props = defineProps<{
|
|
|
|
|
|
template: UiTemplateType
|
|
|
|
|
|
content: QuestionContent
|
|
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
|
|
const pinyinChars = computed(() => {
|
2026-02-05 18:00:11 +08:00
|
|
|
|
if (props.template === 'TEMPLATE_WORD_DICTATION' && props.content.title) {
|
|
|
|
|
|
return props.content.title.split(' ')
|
2026-01-23 18:16:03 +08:00
|
|
|
|
}
|
|
|
|
|
|
return []
|
|
|
|
|
|
})
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<template>
|
|
|
|
|
|
<div class="min-h-[400px] w-full flex items-center justify-center p-10">
|
|
|
|
|
|
<!-- 模板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);">
|
2026-02-05 18:00:11 +08:00
|
|
|
|
“{{ content.title }}”
|
2026-01-23 18:16:03 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<!-- <div class="text-4xl text-gray-800 font-bold">
|
2026-02-05 18:00:11 +08:00
|
|
|
|
{{ content.meta?.hint }}
|
2026-01-23 18:16:03 +08:00
|
|
|
|
</div> -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模板B: 汉字听写-同音字 (大字) -->
|
|
|
|
|
|
<div v-else-if="template === 'TEMPLATE_DICTATION_HOMOPHONE'" class="text-center">
|
|
|
|
|
|
<div class="text-[100px] text-red-600 font-bold">
|
2026-02-05 18:00:11 +08:00
|
|
|
|
“{{ content.title }}”
|
2026-01-23 18:16:03 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模板C: 汉字加一加 (提示+部件) -->
|
|
|
|
|
|
<div v-else-if="template === 'TEMPLATE_COMPONENT_ADD'" class="text-center">
|
|
|
|
|
|
<div class="text-[100px] text-red-600 font-bold">
|
2026-02-05 18:00:11 +08:00
|
|
|
|
{{ content.title }}
|
2026-01-23 18:16:03 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模板D: 词语听写 (拼音+提示) -->
|
|
|
|
|
|
<div v-else-if="template === 'TEMPLATE_WORD_DICTATION'" class="flex flex-col items-center">
|
|
|
|
|
|
<div class="mb-8 flex flex-wrap justify-center gap-6">
|
|
|
|
|
|
<div
|
|
|
|
|
|
v-for="(char, index) in pinyinChars"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
class="relative h-32 w-32 flex select-none items-center justify-center border-2 border-red-500 bg-white"
|
|
|
|
|
|
>
|
|
|
|
|
|
<!-- 米字格背景 -->
|
|
|
|
|
|
<div class="pointer-events-none absolute inset-0 z-0 h-full w-full">
|
|
|
|
|
|
<!-- 横虚线 -->
|
|
|
|
|
|
<div class="absolute left-0 top-1/2 h-[1px] w-full border-t border-red-400 border-dashed opacity-60" />
|
|
|
|
|
|
<!-- 竖虚线 -->
|
|
|
|
|
|
<div class="absolute left-1/2 top-0 h-full w-[1px] border-l border-red-400 border-dashed opacity-60" />
|
|
|
|
|
|
<!-- 斜虚线 (SVG) -->
|
|
|
|
|
|
<svg width="100%" height="100%" class="absolute inset-0 opacity-60">
|
|
|
|
|
|
<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="z-10 text-5xl text-gray-800 font-bold font-sans">{{ char }}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模板E: 成语-文字要求 -->
|
|
|
|
|
|
<div v-else-if="template === 'TEMPLATE_IDIOM_TEXT_REQ'" class="text-center">
|
|
|
|
|
|
<div class="mb-8 text-4xl text-gray-800 font-bold">
|
2026-02-05 18:00:11 +08:00
|
|
|
|
{{ content.title }}
|
2026-01-23 18:16:03 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<!-- <div class="rounded bg-gray-100 px-4 py-2 text-xl text-gray-500">
|
2026-02-05 18:00:11 +08:00
|
|
|
|
示例:{{ content.meta?.example }}
|
2026-01-23 18:16:03 +08:00
|
|
|
|
</div> -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 模板F: 成语-看图 (使用Div占位) -->
|
|
|
|
|
|
<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> -->
|
2026-02-05 18:00:11 +08:00
|
|
|
|
<img :src="content.titleImage" alt="idiom image" class="h-full w-full object-contain">
|
2026-01-23 18:16:03 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<!-- <div class="text-xl text-gray-600 font-bold">
|
2026-02-05 18:00:11 +08:00
|
|
|
|
提示:{{ content.meta?.hint }}
|
2026-01-23 18:16:03 +08:00
|
|
|
|
</div> -->
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-02-05 18:00:11 +08:00
|
|
|
|
<!-- 模板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>
|
|
|
|
|
|
|
2026-01-23 18:16:03 +08:00
|
|
|
|
<div v-else class="text-red-500">
|
|
|
|
|
|
未知的题目模板: {{ template }}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</template>
|
2026-02-05 18:00:11 +08:00
|
|
|
|
|
|
|
|
|
|
<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>
|