|
|
@@ -1,51 +1,31 @@
|
|
|
<template>
|
|
|
<div class="pinyin-area" :style="{ 'text-align': pinyinOverallPosition, padding: pinyinPadding }">
|
|
|
+ <AudioPlay v-if="isEnable(isEnableVoice)" :file-id="audioFileId" :theme-color="unifiedAttrib && unifiedAttrib.topic_color ? unifiedAttrib.topic_color : ''
|
|
|
+ " />
|
|
|
+
|
|
|
<!-- 新规则:使用 richTextList -->
|
|
|
<div v-if="richTextList && richTextList.length > 0" class="rich-text-container">
|
|
|
<template v-for="(block, index) in parsedBlocks">
|
|
|
<!-- 文字块:包含 word_list -->
|
|
|
- <span
|
|
|
- v-if="block.type === 'text'"
|
|
|
- :key="'text-' + index"
|
|
|
- class="pinyin-sentence"
|
|
|
- :style="[{ 'align-items': pinyinPosition === 'top' ? 'flex-end' : 'flex-start' }, block.styleObj]"
|
|
|
- >
|
|
|
+ <span v-if="block.type === 'text'" :key="'text-' + index" class="pinyin-sentence"
|
|
|
+ :style="[{ 'align-items': pinyinPosition === 'top' ? 'flex-end' : 'flex-start' }, block.styleObj]">
|
|
|
<span v-for="(word, wIndex) in block.word_list" :key="wIndex" class="pinyin-text">
|
|
|
- <span
|
|
|
- :class="{ active: visible && word_index == wIndex && sentence_index === block.index }"
|
|
|
- :title="isPreview ? '' : '点击校对'"
|
|
|
- :style="{
|
|
|
+ <span :class="{ active: visible && word_index == wIndex && sentence_index === block.index }"
|
|
|
+ :title="isPreview ? '' : '点击校对'" :style="{
|
|
|
cursor: isPreview ? '' : 'pointer',
|
|
|
'align-items': wIndex == 0 ? 'flex-start' : 'center',
|
|
|
- }"
|
|
|
- @click="correctPinyin(word, block.paragraphIndex, block.oldIndex, wIndex)"
|
|
|
- >
|
|
|
- <span
|
|
|
- v-if="pinyinPosition === 'top' && hasPinyinInParagraphNeVersion(block.paragraphIndex)"
|
|
|
- class="pinyin"
|
|
|
- >
|
|
|
- {{ getPinyinText(word) }}</span
|
|
|
- >
|
|
|
+ }" @click="correctPinyin(word, block.paragraphIndex, block.oldIndex, wIndex)">
|
|
|
+ <span v-if="pinyinPosition === 'top' && hasPinyinInParagraphNeVersion(block.paragraphIndex)"
|
|
|
+ class="pinyin">
|
|
|
+ {{ getPinyinText(word) }}</span>
|
|
|
<span class="py-char" :style="textStyle(word)">{{ convertText(word.text) }}</span>
|
|
|
- <span
|
|
|
- v-if="pinyinPosition !== 'top' && hasPinyinInParagraphNeVersion(block.paragraphIndex)"
|
|
|
- class="pinyin"
|
|
|
- >
|
|
|
- {{ getPinyinText(word) }}</span
|
|
|
- >
|
|
|
+ <span v-if="pinyinPosition !== 'top' && hasPinyinInParagraphNeVersion(block.paragraphIndex)"
|
|
|
+ class="pinyin">
|
|
|
+ {{ getPinyinText(word) }}</span>
|
|
|
</span>
|
|
|
</span>
|
|
|
</span>
|
|
|
- <!-- 图片块 -->
|
|
|
- <img
|
|
|
- v-else-if="block.type === 'image'"
|
|
|
- :key="'image-' + index"
|
|
|
- :src="block.src"
|
|
|
- :alt="block.alt"
|
|
|
- :width="block.width"
|
|
|
- :height="block.height"
|
|
|
- class="inline-image"
|
|
|
- />
|
|
|
+
|
|
|
<!-- 换行符 -->
|
|
|
<br v-else-if="block.type === 'newline'" :key="'newline-' + index" />
|
|
|
</template>
|
|
|
@@ -54,63 +34,33 @@
|
|
|
<!-- 老规则:使用 paragraphList -->
|
|
|
<template v-else-if="paragraphList && paragraphList.length > 0">
|
|
|
<div v-for="(paragraph, pIndex) in paragraphList" :key="pIndex" class="pinyin-paragraph">
|
|
|
- <div
|
|
|
- v-for="(sentence, sIndex) in paragraph"
|
|
|
- :key="sIndex"
|
|
|
- class="pinyin-sentence"
|
|
|
- :style="{ 'align-items': pinyinPosition === 'top' ? 'flex-end' : 'flex-start' }"
|
|
|
- >
|
|
|
+ <div v-for="(sentence, sIndex) in paragraph" :key="sIndex" class="pinyin-sentence"
|
|
|
+ :style="{ 'align-items': pinyinPosition === 'top' ? 'flex-end' : 'flex-start' }">
|
|
|
<span v-for="(word, wIndex) in sentence" :key="wIndex" class="pinyin-text">
|
|
|
- <span
|
|
|
- :class="{
|
|
|
- active: visible && word_index == wIndex && sentence_index === sIndex && paragraph_index === pIndex,
|
|
|
- }"
|
|
|
- :title="isPreview ? '' : '点击校对'"
|
|
|
- :style="{
|
|
|
- cursor: isPreview ? '' : 'pointer',
|
|
|
- 'align-items': wIndex == 0 ? 'flex-start' : 'center',
|
|
|
- }"
|
|
|
- @click="correctPinyin(word, pIndex, sIndex, wIndex)"
|
|
|
- >
|
|
|
- <span
|
|
|
- v-if="pinyinPosition === 'top' && hasPinyinInParagraph(sIndex)"
|
|
|
- class="pinyin"
|
|
|
- :style="{ 'font-size': pinyinSize }"
|
|
|
- >
|
|
|
- {{ getPinyinText(word) }}</span
|
|
|
- >
|
|
|
+ <span :class="{
|
|
|
+ active: visible && word_index == wIndex && sentence_index === sIndex && paragraph_index === pIndex,
|
|
|
+ }" :title="isPreview ? '' : '点击校对'" :style="{
|
|
|
+ cursor: isPreview ? '' : 'pointer',
|
|
|
+ 'align-items': wIndex == 0 ? 'flex-start' : 'center',
|
|
|
+ }" @click="correctPinyin(word, pIndex, sIndex, wIndex)">
|
|
|
+ <span v-if="pinyinPosition === 'top' && hasPinyinInParagraph(sIndex)" class="pinyin"
|
|
|
+ :style="{ 'font-size': pinyinSize }">
|
|
|
+ {{ getPinyinText(word) }}</span>
|
|
|
<span class="py-char" :style="textStyle(word)">{{ convertText(word.text) }}</span>
|
|
|
- <span
|
|
|
- v-if="pinyinPosition !== 'top' && hasPinyinInParagraph(sIndex)"
|
|
|
- class="pinyin"
|
|
|
- :style="{ 'font-size': pinyinSize }"
|
|
|
- >
|
|
|
- {{ getPinyinText(word) }}</span
|
|
|
- >
|
|
|
+ <span v-if="pinyinPosition !== 'top' && hasPinyinInParagraph(sIndex)" class="pinyin"
|
|
|
+ :style="{ 'font-size': pinyinSize }">
|
|
|
+ {{ getPinyinText(word) }}</span>
|
|
|
</span>
|
|
|
</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
- <CorrectPinyin
|
|
|
- :visible.sync="visible"
|
|
|
- :new-version="newVersion"
|
|
|
- :select-content="selectContent"
|
|
|
- :component-type="componentType"
|
|
|
- @fillTonePinyin="fillTonePinyin"
|
|
|
- />
|
|
|
-
|
|
|
- <el-dialog
|
|
|
- ref="optimizedDialog"
|
|
|
- title=""
|
|
|
- :visible.sync="noteDialogVisible"
|
|
|
- width="680px"
|
|
|
- :style="dialogStyle"
|
|
|
- :close-on-click-modal="false"
|
|
|
- destroy-on-close
|
|
|
- @close="noteDialogVisible = false"
|
|
|
- >
|
|
|
+ <CorrectPinyin :visible.sync="visible" :new-version="newVersion" :select-content="selectContent"
|
|
|
+ :component-type="componentType" @fillTonePinyin="fillTonePinyin" />
|
|
|
+
|
|
|
+ <el-dialog ref="optimizedDialog" title="" :visible.sync="noteDialogVisible" width="680px" :style="dialogStyle"
|
|
|
+ :close-on-click-modal="false" destroy-on-close @close="noteDialogVisible = false">
|
|
|
<span v-html="sanitizeHTML(note)"></span>
|
|
|
</el-dialog>
|
|
|
</div>
|
|
|
@@ -120,14 +70,28 @@
|
|
|
import CorrectPinyin from '@/views/book/courseware/create/components/base/common/CorrectPinyin.vue';
|
|
|
import { sanitizeHTML } from '@/utils/common';
|
|
|
import { isEnable } from '@/views/book/courseware/data/common';
|
|
|
+import AudioPlay from '@/views/book/courseware/preview/components/character_base/components/AudioPlay.vue';
|
|
|
|
|
|
export default {
|
|
|
name: 'PinyinText',
|
|
|
components: {
|
|
|
CorrectPinyin,
|
|
|
+ AudioPlay
|
|
|
},
|
|
|
inject: ['convertText'],
|
|
|
props: {
|
|
|
+ isEnableVoice:{
|
|
|
+ type: String,
|
|
|
+ default: 'false'
|
|
|
+ },
|
|
|
+ audioFileId: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ unifiedAttrib: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({}),
|
|
|
+ },
|
|
|
paragraphList: {
|
|
|
type: Array,
|
|
|
default: () => [],
|
|
|
@@ -211,32 +175,19 @@ export default {
|
|
|
for (const item of this.richTextList) {
|
|
|
oldIndex += 1;
|
|
|
|
|
|
- if (item.text && typeof item.text === 'string' && item.text.includes('<img')) {
|
|
|
- // 处理图片
|
|
|
- const imgMatch = item.text.match(/<img\s+([^>]*)\/?\s*>/i);
|
|
|
- if (imgMatch) {
|
|
|
- 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);
|
|
|
-
|
|
|
- blocks.push({
|
|
|
- type: 'image',
|
|
|
- src: srcMatch ? srcMatch[1] : '',
|
|
|
- alt: altMatch ? altMatch[1] : '',
|
|
|
- width: widthMatch ? widthMatch[1] : null,
|
|
|
- height: heightMatch ? heightMatch[1] : null,
|
|
|
- });
|
|
|
- }
|
|
|
- } else if (item.is_style === 'true' || item.is_style === true) {
|
|
|
+ if (item.is_style === 'true' || item.is_style === true) {
|
|
|
const tagText = item.text || '';
|
|
|
// 正则匹配闭标签:匹配 </tagname> 格式
|
|
|
const closeTagMatch = tagText.match(/^<\/(\w+)>$/);
|
|
|
if (closeTagMatch) {
|
|
|
// 闭标签:从栈顶开始查找最近的同名标签并移除(LIFO 原则)
|
|
|
const tagName = closeTagMatch[1];
|
|
|
- this.removeTagFromStack(tagStack, tagName);
|
|
|
+ for (let i = tagStack.length - 1; i >= 0; i--) {
|
|
|
+ if (tagStack[i].tag === tagName) {
|
|
|
+ tagStack.splice(i, 1);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
} else {
|
|
|
// 开标签:解析标签名和样式并压栈
|
|
|
const openTagMatch = tagText.match(/^<(\w+)([^>]*)>$/);
|
|
|
@@ -325,14 +276,6 @@ export default {
|
|
|
},
|
|
|
|
|
|
methods: {
|
|
|
- removeTagFromStack(tagStack, tagName) {
|
|
|
- for (let i = tagStack.length - 1; i >= 0; i--) {
|
|
|
- if (tagStack[i].tag === tagName) {
|
|
|
- tagStack.splice(i, 1);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
textStyle(item) {
|
|
|
const styles = { ...item.activeTextStyle };
|
|
|
styles['font-size'] = styles.fontSize;
|
|
|
@@ -477,7 +420,7 @@ export default {
|
|
|
text-wrap: pretty;
|
|
|
hanging-punctuation: allow-end;
|
|
|
|
|
|
- > span {
|
|
|
+ >span {
|
|
|
display: inline-flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
@@ -508,7 +451,7 @@ export default {
|
|
|
</style>
|
|
|
|
|
|
<style lang="scss">
|
|
|
-.pinyin-area + .pinyin-area {
|
|
|
+.pinyin-area+.pinyin-area {
|
|
|
margin-top: 4px;
|
|
|
}
|
|
|
</style>
|