Files
reader-star/apps/admin/src/views/user/modules/QuestionRenderer.vue

457 lines
12 KiB
Vue
Raw Normal View History

<script setup lang="ts">
import { computed, watch } from 'vue'
import { QuestionCategoryEnum } from '@/enum/business'
import { useCompetitionStore } from '@/store/modules/competition'
const store = useCompetitionStore()
const currentQuestionMainInfo = computed(() => store.currentQuestionMainInfo)
const currentQuestionDetail = computed(() => store.currentQuestionDetail)
// 题目
const title = computed(() => currentQuestionMainInfo.value?.QuestionList?.QuestionContent)
// 题目内容
const content = computed(() => currentQuestionDetail.value.Name)
// 图片
const ImageUrl = computed(() => currentQuestionDetail.value.ImageUrl)
// 模版类型
const template = computed(() => currentQuestionMainInfo.value?.Activity_Question?.UIType)
// 汉字听写1按 # 分割内容
const dictationParts = computed(() => {
if (content.value) {
return content.value.split('#').filter(Boolean)
}
return []
})
// 联想对对碰:上方词组
const associationWords = computed(() => {
const name = currentQuestionDetail.value?.Name || ''
return name.split(/[,,、]/).filter(Boolean)
})
// 联想对对碰:答案按逗号分行,只保留汉字
const answerLines = computed(() => {
const answer = currentQuestionDetail.value?.Answer || ''
return answer.split(/[,]/)
.map(line => line.split('').filter(char => /[\u4E00-\u9FA5]/.test(char)))
.filter(line => line.length > 0)
})
// 联想对对碰:关键词索引和文字
const keywordInfo = computed(() => ({
index: currentQuestionDetail.value?.KeyWordsIndex ?? -1,
word: currentQuestionDetail.value?.KeyWord || '',
}))
// 计算全局字符索引(用于匹配 KeyWordsIndex
function getGlobalCharIndex(lineIndex: number, charIndex: number) {
let globalIndex = 0
for (let i = 0; i < lineIndex; i++) {
globalIndex += answerLines.value[i].length
}
return globalIndex + charIndex
}
watch(
() => [currentQuestionMainInfo, currentQuestionDetail],
() => {
// eslint-disable-next-line no-console
console.log('Current currentQuestionMainInfo:', currentQuestionMainInfo.value)
// eslint-disable-next-line no-console
console.log('Current currentQuestionDetail:', currentQuestionDetail.value)
},
{ deep: true, immediate: true },
)
</script>
<template>
<div class="w-full flex p-6">
<!-- <p>template:{{ template }}</p> -->
<!-- 模板A: 汉字听写1-提示 (拼音+提示) -->
<div v-if="template === QuestionCategoryEnum.ChineseCharacterDictation1" class="text-center">
<div class="mb-6 inline-block text-5xl font-bold leading-none">
<div class="flex items-center justify-center gap-4">
<div
v-for="(part, index) in dictationParts" :key="`part-${index}`"
class="text-4xl"
:class="index === 0 ? 'short-content-bg' : 'content-with-bg'"
>
{{ part }}
</div>
</div>
</div>
</div>
<!-- 模板D: 汉字听写 2 词语听写 -->
<div
v-else-if="template === QuestionCategoryEnum.ChineseCharacterDictation2 || template === QuestionCategoryEnum.WordDictation"
class="flex flex-col items-center"
>
<div class="mb-1 text-3xl text-gray-800 font-bold leading-relaxed">
<div class="rich-content content-with-bg ml-3 mt-4 flex text-6xl !px-16 !py-10 !tracking-[0.1em]">
{{ content }}
</div>
</div>
</div>
<!-- 模板C: 汉字加一加 (提示+部件) -->
<div v-else-if="template === QuestionCategoryEnum.CharacterRadicalAddition" class="text-center">
<div class="mb-6 inline-block text-5xl font-bold leading-none">
<div class="rich-content ml-3 mt-4 flex gap-1 text-5xl tracking-[0.1em]">
请写出含有 <span class="text-color-red !text-6xl">{{ content }}</span>的汉字书写时间为60秒
</div>
</div>
</div>
<!-- 模板D: 成语写一写1 (拼音+提示) -->
<div v-else-if="template === QuestionCategoryEnum.IdiomWriting1" class="flex flex-col items-center">
<div class="mb-1 text-gray-800 font-bold leading-relaxed">
<div class="rich-content ml-3 mt-4 flex gap-1 text-4xl">
{{ content }}
</div>
</div>
</div>
<!-- 模板E: 成语-文字要求 -->
<div v-else-if="template === QuestionCategoryEnum.IdiomWriting2" class="text-center">
<div class="mb-8 text-4xl text-gray-800 font-bold">
<NImage :src="ImageUrl" preview-disabled width="300" object-fit="contain" />
</div>
</div>
<!-- 模板G: 诗词理解-选择题 (通用选择题模板) -->
<div v-else-if="template === QuestionCategoryEnum.PoetryComprehension" class="w-full flex flex-col items-center">
<div class="mb-1 text-3xl text-gray-800 font-bold leading-relaxed">
<div class="rich-content" v-html="content" />
</div>
</div>
<!-- 模板ChangeWords: 换字组成语 -->
<div v-else-if="template === QuestionCategoryEnum.ChangeWords" class="w-full flex flex-col items-center">
<div class="content-with-bg mb-1 mt-10 p-6 text-6xl text-gray-800 font-bold !px-16 !py-8">
{{ content }}
</div>
</div>
<!-- 模板: 场景猜诗句 -->
<div v-else-if="template === QuestionCategoryEnum.SceneBasedPoemGuessing" class="w-full flex flex-col items-center">
<div class="mb-1 p-16 text-3xl text-gray-800 font-bold leading-relaxed line-height-20">
<div class="rich-content" v-html="content" />
</div>
</div>
<!-- 模板: 联想对对碰 -->
<div
v-else-if="template === QuestionCategoryEnum.AssociationMatching"
class="w-full flex flex-col items-center gap-8"
>
<!-- 上方词组 -->
<div class="flex flex-wrap justify-center gap-4">
<div v-for="(word, index) in associationWords" :key="`word-${index}`" class="association-word text-bold">
{{ word }}
</div>
</div>
<!-- 下方答案字符按逗号分行 -->
<div class="flex flex-col items-center gap-3">
<div v-for="(line, lineIndex) in answerLines" :key="`line-${lineIndex}`" class="flex gap-4">
<div
v-for="(char, charIndex) in line" :key="`char-${lineIndex}-${charIndex}`"
class="association-char text-5xl"
>
<span v-if="getGlobalCharIndex(lineIndex, charIndex) === keywordInfo.index">
{{ keywordInfo.word }}
</span>
</div>
</div>
</div>
</div>
<!-- 模板: 字词飞花令 -->
<div
v-else-if="template === QuestionCategoryEnum.CharacterWordFlowerDrinkingGame"
class="w-full flex flex-col items-center"
>
<div class="options-container text-5xl">
请回答带有{{ content }}的诗词
</div>
</div>
<!-- 模板: 诗词常识 -->
<div v-else-if="template === QuestionCategoryEnum.PoetryCommonKnowledge" class="w-full flex flex-col items-center">
<div class="mb-1 p-16 text-3xl text-gray-800 font-bold leading-relaxed line-height-20">
<div class="rich-content" v-html="content" />
</div>
</div>
<!-- 模板: 逆向接诗句 -->
<div v-else-if="template === QuestionCategoryEnum.ReversePoemMatching" class="w-full flex flex-col items-center">
<div class="mb-1 mt-20 text-5xl text-gray-800 font-bold leading-relaxed">
<div class="rich-content" v-html="content" />
</div>
</div>
<div v-else class="text-red-500">
<div class="mb-6 inline-block text-5xl font-bold leading-none">
<div class="ptions-container1 mb-2 text-center text-2xl">
{{ title }}
</div>
<div class="rich-content ml-3 mt-4 flex gap-1">
{{ content }}
</div>
</div>
未知的题目模板: {{ 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 */
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: 0.8rem;
height: 6rem;
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 */
}
}
.homophone {
:deep(img) {
height: 230px !important;
width: auto !important;
object-fit: contain;
}
}
.rich-content {
:deep(img) {
max-width: 200px;
height: auto;
object-fit: contain;
margin: 0 auto;
vertical-align: middle;
}
:deep(p),
:deep(span),
:deep(div) {
font-size: 2rem; /* text-5xl */
}
}
.content-with-bg {
background: url('@/assets/imgs/user/content-bg.png') no-repeat center;
background-size: 100% 100%;
border-radius: 12px;
padding: 20px 30px;
letter-spacing: 0.4em;
}
.short-content-bg {
background: url('@/assets/imgs/user/short-content-bg.png') no-repeat center;
background-size: 100% 100%;
border-radius: 12px;
padding: 20px 30px;
letter-spacing: 0.1em;
}
.association-word {
background: url('@/assets/imgs/user/dui-kuang.png') no-repeat center;
background-size: 100% 100%;
min-width: 160px;
height: 70px;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
font-weight: bold;
color: white;
padding: 0 20px;
}
.association-char {
background: url('@/assets/imgs/user/dui-text-bg.png') no-repeat center;
background-size: 100% 100%;
width: 100px;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
color: white;
}
</style>