|
|
@@ -53,71 +53,124 @@
|
|
|
<div class="cell-wrap">
|
|
|
<div class="cell-wrap-content" :style="[tdStyle, computedRichStyle(col.content)]">
|
|
|
<template v-if="isEnable(data.property.view_pinyin)">
|
|
|
- <div
|
|
|
- v-for="(item, index) in col.model_pinyin"
|
|
|
- :key="index"
|
|
|
- class="pinyin-text"
|
|
|
- :class="[index === 0 ? 'pinyin-text-left' : '']"
|
|
|
- >
|
|
|
- <template v-if="item.type === 'input'">
|
|
|
- <template v-if="data.property.fill_type === fillTypeList[0].value">
|
|
|
- <el-input
|
|
|
- :key="index"
|
|
|
- v-model="item.value"
|
|
|
- :disabled="disabled"
|
|
|
- :class="[...computedAnswerClass(item, i, j)]"
|
|
|
- :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
|
|
|
- />
|
|
|
- </template>
|
|
|
- <template v-else-if="data.property.fill_type === fillTypeList[1].value">
|
|
|
- <el-popover :key="index" placement="top" trigger="click">
|
|
|
- <div class="word-list">
|
|
|
+ <template v-if="col.rich_text_list">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in col.rich_text_list"
|
|
|
+ :key="index"
|
|
|
+ class="pinyin-text"
|
|
|
+ :class="[index === 0 ? 'pinyin-text-left' : '']"
|
|
|
+ >
|
|
|
+ <!-- 文字块:包含 word_list -->
|
|
|
+ <span
|
|
|
+ v-if="item.type === 'text'"
|
|
|
+ :key="'text-' + index"
|
|
|
+ class="pinyin-sentence"
|
|
|
+ :style="[
|
|
|
+ { 'align-items': data.property.pinyin_position === 'top' ? 'flex-end' : 'flex-start' },
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ v-for="(word, wIndex) in item.word_list"
|
|
|
+ :key="wIndex"
|
|
|
+ class="pinyin-text"
|
|
|
+ :style="item.styleObj"
|
|
|
+ >
|
|
|
+ <span v-if="word.type === 'text'">
|
|
|
<span
|
|
|
- v-for="{ content, mark } in data.word_list"
|
|
|
- :key="mark"
|
|
|
- class="word-item"
|
|
|
- @click="handleSelectWord(content, mark, item)"
|
|
|
+ :style="{
|
|
|
+ flexDirection:
|
|
|
+ data.property.pinyin_position === 'top' ? 'column' : 'column-reverse',
|
|
|
+ 'align-items': getWordAlignItems(word, item),
|
|
|
+ }"
|
|
|
>
|
|
|
- {{ convertText(content) }}
|
|
|
+ <span class="pinyin" :style="getPinyinStyle(word)"> {{ word.pinyin }}</span>
|
|
|
+ <span class="py-char" :style="getCharStyle(word, item)">{{
|
|
|
+ convertText(word.text)
|
|
|
+ }}</span>
|
|
|
</span>
|
|
|
- </div>
|
|
|
+ </span>
|
|
|
+ <template v-else-if="word.type === 'input'">
|
|
|
+ <span v-if="data.property.pinyin_position === 'top'" class="pinyin"> </span>
|
|
|
+ <template v-if="data.property.fill_type === fillTypeList[0].value">
|
|
|
+ <el-input
|
|
|
+ :key="index"
|
|
|
+ v-model="word.value"
|
|
|
+ :disabled="disabled"
|
|
|
+ :class="[...computedAnswerClass(word, i, j)]"
|
|
|
+ :style="[{ width: Math.max(40, word.value.length * 21.3) + 'px' }]"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <template v-else-if="data.property.fill_type === fillTypeList[1].value">
|
|
|
+ <el-popover :key="index" placement="top" trigger="click">
|
|
|
+ <div class="word-list">
|
|
|
+ <span
|
|
|
+ v-for="{ content, mark } in data.word_list"
|
|
|
+ :key="mark"
|
|
|
+ class="word-item"
|
|
|
+ @click="handleSelectWord(content, mark, word)"
|
|
|
+ >
|
|
|
+ {{ convertText(content) }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
|
|
|
- <el-input
|
|
|
- slot="reference"
|
|
|
- v-model="item.value"
|
|
|
- :readonly="true"
|
|
|
- :class="[...computedAnswerClass(item, i, j)]"
|
|
|
- :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
|
|
|
- />
|
|
|
- </el-popover>
|
|
|
- </template>
|
|
|
+ <el-input
|
|
|
+ slot="reference"
|
|
|
+ v-model="word.value"
|
|
|
+ :readonly="true"
|
|
|
+ :class="[...computedAnswerClass(word, i, j)]"
|
|
|
+ :style="[{ width: Math.max(40, word.value.length * 21.3) + 'px' }]"
|
|
|
+ />
|
|
|
+ </el-popover>
|
|
|
+ </template>
|
|
|
|
|
|
- <template v-else-if="data.property.fill_type === fillTypeList[2].value">
|
|
|
- <span :key="j" class="write-click" @click="handleWriteClick(item)">
|
|
|
- <img
|
|
|
- v-show="item.write_base64"
|
|
|
- style="background-color: #f4f4f4"
|
|
|
- :src="item.write_base64"
|
|
|
- alt="write-show"
|
|
|
- />
|
|
|
- </span>
|
|
|
- </template>
|
|
|
+ <template v-else-if="data.property.fill_type === fillTypeList[2].value">
|
|
|
+ <span :key="j" class="write-click" @click="handleWriteClick(word)">
|
|
|
+ <img
|
|
|
+ v-show="word.write_base64"
|
|
|
+ style="background-color: #f4f4f4"
|
|
|
+ :src="word.write_base64"
|
|
|
+ alt="write-show"
|
|
|
+ />
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
|
|
|
- <template v-else-if="data.property.fill_type === fillTypeList[3].value">
|
|
|
- <SoundRecordBox
|
|
|
- ref="record"
|
|
|
- :key="j"
|
|
|
- type="mini"
|
|
|
- :many-times="false"
|
|
|
- class="record-box"
|
|
|
- :answer-record-list="data.audio_answer_list"
|
|
|
- :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
|
|
|
- :attrib="data.unified_attrib"
|
|
|
- @handleWav="handleMiniWav($event, item)"
|
|
|
+ <template v-else-if="data.property.fill_type === fillTypeList[3].value">
|
|
|
+ <SoundRecordBox
|
|
|
+ ref="record"
|
|
|
+ :key="j"
|
|
|
+ type="mini"
|
|
|
+ :many-times="false"
|
|
|
+ class="record-box"
|
|
|
+ :answer-record-list="data.audio_answer_list"
|
|
|
+ :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
|
|
|
+ :attrib="data.unified_attrib"
|
|
|
+ @handleWav="handleMiniWav($event, word)"
|
|
|
+ />
|
|
|
+ </template>
|
|
|
+ <span v-if="data.property.pinyin_position === 'bottom'" class="pinyin"> </span>
|
|
|
+ </template>
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ <!-- 图片块 -->
|
|
|
+ <div
|
|
|
+ v-else-if="item.type === 'image'"
|
|
|
+ :key="'image-' + index"
|
|
|
+ :style="item.containerStyle"
|
|
|
+ class="image-container"
|
|
|
+ >
|
|
|
+ <img
|
|
|
+ :src="item.src"
|
|
|
+ :alt="item.alt"
|
|
|
+ :width="item.width"
|
|
|
+ :height="item.height"
|
|
|
+ :style="item.imageStyle"
|
|
|
+ class="inline-image"
|
|
|
/>
|
|
|
- </template>
|
|
|
- <span v-if="data.property.pinyin_position === 'bottom'" class="pinyin"> </span>
|
|
|
- </template>
|
|
|
+ </div>
|
|
|
+ <!-- 换行符 -->
|
|
|
+ <br v-else-if="block.type === 'newline'" :key="'newline-' + index" />
|
|
|
+
|
|
|
+ <!--
|
|
|
<template v-else>
|
|
|
<span v-if="data.property.pinyin_position === 'top'" class="pinyin">
|
|
|
{{ item.pinyin.replace(/\s+/g, '') }}
|
|
|
@@ -128,8 +181,9 @@
|
|
|
<span v-if="data.property.pinyin_position === 'bottom'" class="pinyin">{{
|
|
|
item.pinyin.replace(/\s+/g, '')
|
|
|
}}</span>
|
|
|
- </template>
|
|
|
- </div>
|
|
|
+ </template> -->
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
</template>
|
|
|
<template v-else>
|
|
|
<div v-for="(item, index) in col.model_essay" :key="index" :style="[tdStyle]">
|
|
|
@@ -247,8 +301,6 @@
|
|
|
class="table-box"
|
|
|
:style="{
|
|
|
width: isMobile ? '100%' : data.property.width + 'px',
|
|
|
-
|
|
|
- height: data.property.height + 'px',
|
|
|
}"
|
|
|
>
|
|
|
<table style="width: 100%; table-layout: fixed">
|
|
|
@@ -298,90 +350,75 @@
|
|
|
<div class="cell-wrap">
|
|
|
<div class="cell-wrap-content" :style="[tdStyle, computedRichStyle(col.content)]">
|
|
|
<template v-if="isEnable(data.property.view_pinyin)">
|
|
|
- <div
|
|
|
- v-for="(item, index) in col.model_pinyin"
|
|
|
- :key="index"
|
|
|
- class="pinyin-text"
|
|
|
- :class="[index === 0 ? 'pinyin-text-left' : '']"
|
|
|
- >
|
|
|
- <template v-if="item.type === 'input'">
|
|
|
- <!-- <template v-if="data.property.fill_type === fillTypeList[0].value">
|
|
|
- <el-input
|
|
|
- :key="index"
|
|
|
- v-model="item.value"
|
|
|
- :disabled="disabled"
|
|
|
- :class="[...computedAnswerClass(item, i, j)]"
|
|
|
- :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
|
|
|
- />
|
|
|
- </template>
|
|
|
- <template v-else-if="data.property.fill_type === fillTypeList[1].value">
|
|
|
- <el-popover :key="index" placement="top" trigger="click">
|
|
|
- <div class="word-list">
|
|
|
- <span
|
|
|
- v-for="{ content, mark } in data.word_list"
|
|
|
- :key="mark"
|
|
|
- class="word-item"
|
|
|
- @click="handleSelectWord(content, mark, item)"
|
|
|
- >
|
|
|
- {{ convertText(content) }}
|
|
|
+ <template v-if="col.rich_text_list">
|
|
|
+ <div
|
|
|
+ v-for="(item, index) in col.rich_text_list"
|
|
|
+ :key="index"
|
|
|
+ class="pinyin-text"
|
|
|
+ :class="[index === 0 ? 'pinyin-text-left' : '']"
|
|
|
+ >
|
|
|
+ <!-- 文字块:包含 word_list -->
|
|
|
+ <span
|
|
|
+ v-if="item.type === 'text'"
|
|
|
+ :key="'text-' + index"
|
|
|
+ class="pinyin-sentence"
|
|
|
+ :style="[
|
|
|
+ { 'align-items': data.property.pinyin_position === 'top' ? 'flex-end' : 'flex-start' },
|
|
|
+ ]"
|
|
|
+ >
|
|
|
+ <span
|
|
|
+ v-for="(word, wIndex) in item.word_list"
|
|
|
+ :key="wIndex"
|
|
|
+ class="pinyin-text"
|
|
|
+ :style="item.styleObj"
|
|
|
+ >
|
|
|
+ <span v-if="word.type === 'text'">
|
|
|
+ <span
|
|
|
+ :style="{
|
|
|
+ flexDirection:
|
|
|
+ data.property.pinyin_position === 'top' ? 'column' : 'column-reverse',
|
|
|
+ 'align-items': getWordAlignItems(word, item),
|
|
|
+ }"
|
|
|
+ >
|
|
|
+ <span class="pinyin" :style="getPinyinStyle(word)"> {{ word.pinyin }}</span>
|
|
|
+ <span class="py-char" :style="getCharStyle(word, item)">{{
|
|
|
+ convertText(word.text)
|
|
|
+ }}</span>
|
|
|
+ </span>
|
|
|
</span>
|
|
|
- </div>
|
|
|
-
|
|
|
- <el-input
|
|
|
- slot="reference"
|
|
|
- v-model="item.value"
|
|
|
- :readonly="true"
|
|
|
- :class="[...computedAnswerClass(item, i, j)]"
|
|
|
- :style="[{ width: Math.max(40, item.value.length * 21.3) + 'px' }]"
|
|
|
- />
|
|
|
- </el-popover>
|
|
|
- </template>
|
|
|
-
|
|
|
- <template v-else-if="data.property.fill_type === fillTypeList[2].value">
|
|
|
- <span :key="j" class="write-click" @click="handleWriteClick(item)">
|
|
|
+ <template v-else-if="word.type === 'input'">
|
|
|
+ <span v-if="data.property.pinyin_position === 'top'" class="pinyin"> </span>
|
|
|
+ <span
|
|
|
+ :key="`answer-${j}`"
|
|
|
+ class="right-answer"
|
|
|
+ v-show="computedAnswerText(word, i, j).length > 0"
|
|
|
+ >
|
|
|
+ {{ convertText(computedAnswerText(word, i, j)) }}
|
|
|
+ </span>
|
|
|
+ <span v-if="data.property.pinyin_position === 'bottom'" class="pinyin"> </span>
|
|
|
+ </template>
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ <!-- 图片块 -->
|
|
|
+ <div
|
|
|
+ v-else-if="item.type === 'image'"
|
|
|
+ :key="'image-' + index"
|
|
|
+ :style="item.containerStyle"
|
|
|
+ class="image-container"
|
|
|
+ >
|
|
|
<img
|
|
|
- v-show="item.write_base64"
|
|
|
- style="background-color: #f4f4f4"
|
|
|
- :src="item.write_base64"
|
|
|
- alt="write-show"
|
|
|
+ :src="item.src"
|
|
|
+ :alt="item.alt"
|
|
|
+ :width="item.width"
|
|
|
+ :height="item.height"
|
|
|
+ :style="item.imageStyle"
|
|
|
+ class="inline-image"
|
|
|
/>
|
|
|
- </span>
|
|
|
- </template>
|
|
|
-
|
|
|
- <template v-else-if="data.property.fill_type === fillTypeList[3].value">
|
|
|
- <SoundRecordBox
|
|
|
- ref="record"
|
|
|
- :key="j"
|
|
|
- type="mini"
|
|
|
- :many-times="false"
|
|
|
- class="record-box"
|
|
|
- :answer-record-list="data.audio_answer_list"
|
|
|
- :task-model="isJudgingRightWrong ? 'ANSWER' : ''"
|
|
|
- :attrib="data.unified_attrib"
|
|
|
- @handleWav="handleMiniWav($event, item)"
|
|
|
- />
|
|
|
- </template> -->
|
|
|
- <span v-if="data.property.pinyin_position === 'bottom'" class="pinyin"> </span>
|
|
|
- </template>
|
|
|
- <template v-else>
|
|
|
- <span v-if="data.property.pinyin_position === 'top'" class="pinyin">
|
|
|
- {{ item.pinyin.replace(/\s+/g, '') }}
|
|
|
- </span>
|
|
|
- <span :style="{ ...item.activeTextStyle }">
|
|
|
- {{ convertText(item.text) }}
|
|
|
- </span>
|
|
|
- <span v-if="data.property.pinyin_position === 'bottom'" class="pinyin">{{
|
|
|
- item.pinyin.replace(/\s+/g, '')
|
|
|
- }}</span>
|
|
|
- </template>
|
|
|
- <span
|
|
|
- :key="`answer-${j}`"
|
|
|
- class="right-answer"
|
|
|
- v-show="computedAnswerText(item, i, j).length > 0"
|
|
|
- >
|
|
|
- {{ convertText(computedAnswerText(item, i, j)) }}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
+ </div>
|
|
|
+ <!-- 换行符 -->
|
|
|
+ <br v-else-if="block.type === 'newline'" :key="'newline-' + index" />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
</template>
|
|
|
<template v-else>
|
|
|
<div v-for="(item, index) in col.model_essay" :key="index" :style="[tdStyle]">
|
|
|
@@ -575,6 +612,234 @@ export default {
|
|
|
this.computedTableCellShow();
|
|
|
},
|
|
|
methods: {
|
|
|
+ // 解析 richTextList 为可渲染的块
|
|
|
+ parsedBlocks(col) {
|
|
|
+ if (!col.rich_text_list || col.rich_text_list.length === 0) return [];
|
|
|
+ const blocks = [];
|
|
|
+ let textBlockIndex = 0;
|
|
|
+ let oldIndex = -1;
|
|
|
+ let paragraphIndex = 0;
|
|
|
+ const tagStack = [];
|
|
|
+
|
|
|
+ for (const item of col.rich_text_list) {
|
|
|
+ oldIndex += 1;
|
|
|
+
|
|
|
+ if (item.text && typeof item.text === 'string' && item.text.includes('<img')) {
|
|
|
+ blocks.push(this.parseImageBlock(item, tagStack));
|
|
|
+ } else if (item.is_style === 'true' || item.is_style === true) {
|
|
|
+ this.handleStyleTag(item, tagStack);
|
|
|
+ } else if (item.text === '\n') {
|
|
|
+ blocks.push({ type: 'newline' });
|
|
|
+ paragraphIndex += 1;
|
|
|
+ } else if (item.word_list && item.word_list.length > 0) {
|
|
|
+ const hasEmphasisDot = this.checkHasEmphasisDot(tagStack);
|
|
|
+ blocks.push(this.createTextBlock(item, tagStack, textBlockIndex, oldIndex, paragraphIndex, hasEmphasisDot));
|
|
|
+ textBlockIndex += 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return blocks;
|
|
|
+ },
|
|
|
+ // 解析图片块
|
|
|
+ parseImageBlock(item, tagStack) {
|
|
|
+ const imgMatch = item.text.match(/<img\s+([^>]*)\/?\s*>/i);
|
|
|
+ if (!imgMatch) return null;
|
|
|
+
|
|
|
+ const attrs = imgMatch[1];
|
|
|
+ const srcMatch = attrs.match(/src=["']([^"']*)["']/i);
|
|
|
+ const altMatch = attrs.match(/alt=["']([^"']*)["']/i);
|
|
|
+ const widthMatch = attrs.match(/width=["']?(\d+)["']?/i);
|
|
|
+ const heightMatch = attrs.match(/height=["']?(\d+)["']?/i);
|
|
|
+ const styleMatch = attrs.match(/style=["']([^"']*)["']/i);
|
|
|
+
|
|
|
+ const containerStyleObj = {};
|
|
|
+ tagStack.forEach((tagItem) => {
|
|
|
+ if (tagItem.style) {
|
|
|
+ this.mergeStyleString(containerStyleObj, tagItem.style);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const imageStyleObj = {};
|
|
|
+ if (styleMatch) {
|
|
|
+ this.mergeStyleString(imageStyleObj, styleMatch[1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'image',
|
|
|
+ src: srcMatch ? srcMatch[1] : '',
|
|
|
+ alt: altMatch ? altMatch[1] : '',
|
|
|
+ width: widthMatch ? widthMatch[1] : null,
|
|
|
+ height: heightMatch ? heightMatch[1] : null,
|
|
|
+ containerStyle: containerStyleObj,
|
|
|
+ imageStyle: imageStyleObj,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ // 处理样式标签
|
|
|
+ handleStyleTag(item, tagStack) {
|
|
|
+ const tagText = item.text || '';
|
|
|
+ const closeTagMatch = tagText.match(/^<\/(\w+)>$/);
|
|
|
+
|
|
|
+ if (closeTagMatch) {
|
|
|
+ const tagName = closeTagMatch[1];
|
|
|
+ this.removeTagFromStack(tagStack, tagName);
|
|
|
+ } else {
|
|
|
+ const openTagMatch = tagText.match(/^<(\w+)([^>]*)>$/);
|
|
|
+ if (openTagMatch) {
|
|
|
+ const tagName = openTagMatch[1];
|
|
|
+ const attrs = openTagMatch[2];
|
|
|
+ const { style, className } = this.extractTagInfo(attrs, tagName);
|
|
|
+ tagStack.push({ tag: tagName, style, className });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 检查标签栈中是否有着重点样式
|
|
|
+ checkHasEmphasisDot(tagStack) {
|
|
|
+ return tagStack.some((tagItem) => {
|
|
|
+ if (!tagItem.className) return false;
|
|
|
+ return tagItem.className.includes('rich-text-emphasis-dot');
|
|
|
+ });
|
|
|
+ },
|
|
|
+ // 创建文本块
|
|
|
+ createTextBlock(item, tagStack, textBlockIndex, oldIndex, paragraphIndex, hasEmphasisDot) {
|
|
|
+ const combinedStyle = tagStack
|
|
|
+ .filter((tagItem) => tagItem.style)
|
|
|
+ .map((tagItem) => tagItem.style)
|
|
|
+ .join(';');
|
|
|
+
|
|
|
+ const styleObj = combinedStyle ? this.parseStyleToObject(combinedStyle) : null;
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'text',
|
|
|
+ word_list: item.word_list,
|
|
|
+ index: textBlockIndex,
|
|
|
+ oldIndex,
|
|
|
+ paragraphIndex,
|
|
|
+ styleObj,
|
|
|
+ hasEmphasisDot,
|
|
|
+ };
|
|
|
+ },
|
|
|
+ // 拼音固定为拼音字体,跟随汉字的字号、颜色、粗细的样式
|
|
|
+ getPinyinStyle(item) {
|
|
|
+ const styles = {};
|
|
|
+
|
|
|
+ // 固定使用 League 字体
|
|
|
+ styles['font-family'] = 'League';
|
|
|
+
|
|
|
+ // 只继承字号和颜色
|
|
|
+ if (item.activeTextStyle) {
|
|
|
+ if (item.activeTextStyle.fontSize) {
|
|
|
+ styles['font-size'] = item.activeTextStyle.fontSize;
|
|
|
+ }
|
|
|
+ if (item.activeTextStyle.color) {
|
|
|
+ styles['color'] = item.activeTextStyle.color;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果设置了全局字号,优先使用全局字号
|
|
|
+ if (this.isAllSetting && this.fontSize) {
|
|
|
+ styles['font-size'] = this.fontSize;
|
|
|
+ this.isAllSetting = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 明确重置不应该继承的样式
|
|
|
+ styles['text-decoration'] = 'none';
|
|
|
+ styles['font-style'] = 'normal';
|
|
|
+ styles['letter-spacing'] = '0';
|
|
|
+
|
|
|
+ return styles;
|
|
|
+ },
|
|
|
+ // 获取单个字符的样式(包括着重点)
|
|
|
+ getCharStyle(word, block, charIndex) {
|
|
|
+ const baseStyle = { ...word.activeTextStyle };
|
|
|
+ baseStyle['font-size'] = baseStyle.fontSize;
|
|
|
+ baseStyle['font-family'] = baseStyle.fontFamily;
|
|
|
+
|
|
|
+ if (this.isAllSetting) {
|
|
|
+ baseStyle['font-size'] = this.fontSize;
|
|
|
+ baseStyle['font-family'] = this.fontFamily;
|
|
|
+ this.isAllSetting = false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果该文字块有着重点标记,应用到字符上
|
|
|
+ if (block.hasEmphasisDot) {
|
|
|
+ const letterSpacing = block.styleObj?.letterSpacing || '0';
|
|
|
+ const letterSpacingValue = parseFloat(letterSpacing) || 0;
|
|
|
+
|
|
|
+ baseStyle['border-bottom'] = 'none';
|
|
|
+ baseStyle['background-image'] = 'radial-gradient(circle at center, currentColor 0.15em, transparent 0.16em)';
|
|
|
+ baseStyle['background-repeat'] = 'repeat-x';
|
|
|
+ baseStyle['background-position'] = `${letterSpacingValue * -0.5}em 100%`;
|
|
|
+ baseStyle['background-size'] = `${1 + letterSpacingValue}em 0.3em`;
|
|
|
+ baseStyle['padding-bottom'] = '0.3em';
|
|
|
+ baseStyle['display'] = 'inline';
|
|
|
+ }
|
|
|
+
|
|
|
+ return baseStyle;
|
|
|
+ },
|
|
|
+ // 如果汉字有字间距,拼音与汉字左对齐,如果没有则居中对齐
|
|
|
+ getWordAlignItems(word, block) {
|
|
|
+ const letterSpacing = block.styleObj?.letterSpacing;
|
|
|
+ if (letterSpacing && letterSpacing !== '0' && letterSpacing !== '0px') {
|
|
|
+ return 'flex-start';
|
|
|
+ }
|
|
|
+ return 'center';
|
|
|
+ },
|
|
|
+ // 提取标签信息(样式和类名)
|
|
|
+ extractTagInfo(attrs, tagName) {
|
|
|
+ const styleMatch = attrs.match(/style=["']([^"']*)["']/);
|
|
|
+ let style = styleMatch ? styleMatch[1] : null;
|
|
|
+
|
|
|
+ const classMatch = attrs.match(/class=["']([^"']*)["']/);
|
|
|
+ const className = classMatch ? classMatch[1] : '';
|
|
|
+
|
|
|
+ if (!style) {
|
|
|
+ style = this.getDefaultTagStyle(tagName);
|
|
|
+ }
|
|
|
+
|
|
|
+ return { style, className };
|
|
|
+ },
|
|
|
+ // 获取标签默认样式
|
|
|
+ getDefaultTagStyle(tagName) {
|
|
|
+ const tagStyleMap = {
|
|
|
+ strong: 'font-weight: bold',
|
|
|
+ b: 'font-weight: bold',
|
|
|
+ em: 'font-style: italic',
|
|
|
+ i: 'font-style: italic',
|
|
|
+ u: 'text-decoration: underline',
|
|
|
+ s: 'text-decoration: line-through',
|
|
|
+ del: 'text-decoration: line-through',
|
|
|
+ sub: 'vertical-align: sub',
|
|
|
+ sup: 'vertical-align: super',
|
|
|
+ };
|
|
|
+ return tagStyleMap[tagName] || null;
|
|
|
+ },
|
|
|
+ // 解析样式字符串为对象
|
|
|
+ parseStyleToObject(styleStr) {
|
|
|
+ const styleObj = {};
|
|
|
+ if (!styleStr) return styleObj;
|
|
|
+
|
|
|
+ this.mergeStyleString(styleObj, styleStr);
|
|
|
+ return styleObj;
|
|
|
+ },
|
|
|
+ // 合并样式字符串到对象
|
|
|
+ mergeStyleString(styleObj, styleStr) {
|
|
|
+ styleStr.split(';').forEach((rule) => {
|
|
|
+ if (rule.trim()) {
|
|
|
+ const [prop, value] = rule.split(':').map((s) => s.trim());
|
|
|
+ if (prop && value) {
|
|
|
+ const camelProp = prop.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
|
+ styleObj[camelProp] = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ },
|
|
|
+ removeTagFromStack(tagStack, tagName) {
|
|
|
+ for (let i = tagStack.length - 1; i >= 0; i--) {
|
|
|
+ if (tagStack[i].tag === tagName) {
|
|
|
+ tagStack.splice(i, 1);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
toggle(status) {
|
|
|
const index = this.statusList.findIndex((item) => status.crossAnswer === item);
|
|
|
status.crossAnswer = index === this.statusList.length - 1 ? this.statusList[0] : this.statusList[index + 1];
|
|
|
@@ -602,13 +867,16 @@ export default {
|
|
|
if (!this.isJudgingRightWrong) {
|
|
|
this.answer.answer_list = this.data.answer_lists;
|
|
|
}
|
|
|
- this.data.option_list.forEach((item) => {
|
|
|
- item.forEach((items) => {
|
|
|
+ this.data.option_list.forEach((item, index) => {
|
|
|
+ item.forEach((items, indexs) => {
|
|
|
items.model_essay.forEach((li) => {
|
|
|
if (li.type === 'input') {
|
|
|
this.isHasInput = true;
|
|
|
}
|
|
|
});
|
|
|
+ if (items.rich_text_list) {
|
|
|
+ this.data.option_list[index][indexs].rich_text_list = this.parsedBlocks(items);
|
|
|
+ }
|
|
|
});
|
|
|
});
|
|
|
},
|
|
|
@@ -942,7 +1210,7 @@ $border-color: #e6e6e6;
|
|
|
position: relative;
|
|
|
left: 0;
|
|
|
display: inline-block;
|
|
|
- height: 24px;
|
|
|
+ min-height: 24px;
|
|
|
line-height: 24px;
|
|
|
vertical-align: bottom;
|
|
|
border-bottom: 1px solid $font-color;
|
|
|
@@ -975,6 +1243,43 @@ $border-color: #e6e6e6;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ .cell-wrap-content {
|
|
|
+ p {
|
|
|
+ display: inline-flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ margin: 0.5em 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ span {
|
|
|
+ display: inline-flex;
|
|
|
+ }
|
|
|
+
|
|
|
+ .pinyin-sentence {
|
|
|
+ display: inline;
|
|
|
+ white-space: normal;
|
|
|
+ }
|
|
|
+
|
|
|
+ .image-container {
|
|
|
+ display: block;
|
|
|
+
|
|
|
+ .inline-image {
|
|
|
+ display: inline-block;
|
|
|
+ max-width: 100%;
|
|
|
+ height: auto;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep .el-input--small .el-input__inner {
|
|
|
+ height: 100%;
|
|
|
+ line-height: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep audio,
|
|
|
+ :deep video {
|
|
|
+ max-width: 100%;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|