- 新增大屏开发调试指南文档,包含设计基准、调试方法和字体配置说明 - 添加字体大小预览工具,支持多分辨率视口切换(2048x1080/1920x1080/3840x2160) - 新增字体资源 README 文档和团队标题背景图片 - 优化用户界面背景图片(title-bg3.png、title-bg4.png) - 更新 ActionButton 和 CompetitionLayout 组件样式 - 完善用户模块各页面布局(分析、封面、抽签、游戏、规则、团队) - 优化题目渲染器组件和成绩详情页面 - 更新 UnoCSS 配置以支持大屏字体缩放方案
551 lines
15 KiB
Vue
551 lines
15 KiB
Vue
<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 secondPartStyle = computed(() => {
|
||
if (dictationParts.value.length < 2)
|
||
return { fontSize: 'text-5xl', letterSpacing: 'tracking-widest' }
|
||
|
||
const secondPart = dictationParts.value[1]
|
||
const length = secondPart.length
|
||
|
||
// 根据文字长度返回不同的样式配置
|
||
if (length <= 16) {
|
||
return {
|
||
fontSize: 'text-5xl', // 51.2px - 保持大字体
|
||
letterSpacing: 'tracking-widest', // 0.4em - 正常字间距
|
||
}
|
||
}
|
||
else if (length <= 20) {
|
||
return {
|
||
fontSize: 'text-4xl', // 40px - 稍微缩小
|
||
letterSpacing: 'tracking-wide', // 0.2em - 缩小字间距
|
||
}
|
||
}
|
||
else {
|
||
return {
|
||
fontSize: 'text-3xl', // 33.6px - 更小一点
|
||
letterSpacing: 'tracking-normal', // 0.05em - 最小字间距
|
||
}
|
||
}
|
||
})
|
||
|
||
// 联想对对碰:上方词组
|
||
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-start gap-4">
|
||
<!-- 第一个框:拼音 -->
|
||
<div class="short-content-bg pinyin-text text-6xl">
|
||
{{ dictationParts[0] }}
|
||
</div>
|
||
|
||
<!-- 第二个框:意思提示 -->
|
||
<div
|
||
v-if="dictationParts.length > 1" class="content-with-bg pinyin-text !max-w-85%"
|
||
:class="[secondPartStyle.fontSize, secondPartStyle.letterSpacing]"
|
||
>
|
||
{{ dictationParts[1] }}
|
||
</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-2 text-center text-2xl">
|
||
{{ title }}
|
||
</div>
|
||
<div class="mb-1 text-gray-800 font-bold leading-relaxed">
|
||
<div class="rich-content content-with-bg pinyin-text ml-3 mt-4 flex text-7xl !max-w-100% !px-16 !py-8 !tracking-[0.08em]">
|
||
{{ 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-6xl tracking-[0.06em]">
|
||
请写出含有“ <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-5xl">
|
||
{{ content }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 模板E: 成语-文字要求 -->
|
||
<div v-else-if="template === QuestionCategoryEnum.IdiomWriting2" class="text-center">
|
||
<div class="mb-2">
|
||
<img :src="ImageUrl" preview-disabled class="max-h-380px max-w-700px" object-fit="cover">
|
||
</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-6xl">
|
||
请回答带有“{{ 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: 2.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: 30px 50px;
|
||
/* 优化长文本显示 */
|
||
max-width: 80%;
|
||
/* 限制最大宽度 */
|
||
min-height: 120px;
|
||
/* 最小高度,与 short-content-bg 保持一致 */
|
||
word-break: keep-all;
|
||
/* 避免中文单词被截断 */
|
||
white-space: normal;
|
||
/* 允许换行 */
|
||
line-height: 1.5;
|
||
/* 行高,让换行更美观 */
|
||
display: inline-flex;
|
||
/* 使用 flex 布局 */
|
||
align-items: center;
|
||
/* 垂直居中 */
|
||
justify-content: center;
|
||
/* 水平居中 */
|
||
text-align: center;
|
||
/* 文字居中 */
|
||
}
|
||
|
||
.short-content-bg {
|
||
background: url('@/assets/imgs/user/short-content-bg.png') no-repeat center;
|
||
background-size: 100% 100%;
|
||
border-radius: 12px;
|
||
padding: 20px 80px;
|
||
min-height: 120px;
|
||
/* 最小高度,与 content-with-bg 保持一致 */
|
||
display: inline-flex;
|
||
/* 使用 flex 布局 */
|
||
align-items: center;
|
||
/* 垂直居中 */
|
||
justify-content: center;
|
||
/* 水平居中 */
|
||
}
|
||
|
||
.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;
|
||
}
|
||
|
||
/* 拼音字体样式 - 支持声调符号 */
|
||
.pinyin-text {
|
||
/* 字体回退顺序:
|
||
1. GB Pinyinok-B - 专业拼音字体
|
||
2. Microsoft YaHei - 微软雅黑(Windows 系统自带,支持拼音)
|
||
3. PingFang SC - 苹方(macOS 系统自带,支持拼音)
|
||
4. STHeiti - 华文黑体(支持拼音)
|
||
5. sans-serif - 系统默认无衬线字体
|
||
*/
|
||
font-family: 'GB Pinyinok-B', 'Microsoft YaHei', 'PingFang SC', 'STHeiti', 'SimHei', sans-serif;
|
||
font-weight: bold;
|
||
letter-spacing: 0.08em;
|
||
/* 确保拼音声调符号不被截断 */
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* 如果需要加载自定义拼音字体 */
|
||
@font-face {
|
||
font-family: 'GB Pinyinok-B';
|
||
/* 优先使用本地已安装的字体 */
|
||
src: local('GB Pinyinok-B'), local('GBPinyinok-B'), local('GB Pinyinok B');
|
||
/* 如果本地没有,则加载项目中的字体文件(需要先添加字体文件)*/
|
||
/* src: url('@/assets/fonts/GBPinyinok-B.woff2') format('woff2'),
|
||
url('@/assets/fonts/GBPinyinok-B.woff') format('woff'),
|
||
url('@/assets/fonts/GBPinyinok-B.ttf') format('truetype'); */
|
||
font-weight: normal;
|
||
font-style: normal;
|
||
font-display: swap;
|
||
/* 字体加载时先显示系统字体,加载完成后切换 */
|
||
}
|
||
</style>
|