|
|
@@ -1,31 +1,57 @@
|
|
|
<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 : ''
|
|
|
- " />
|
|
|
+ <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>
|
|
|
@@ -34,33 +60,63 @@
|
|
|
<!-- 老规则:使用 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>
|
|
|
@@ -76,13 +132,13 @@ export default {
|
|
|
name: 'PinyinText',
|
|
|
components: {
|
|
|
CorrectPinyin,
|
|
|
- AudioPlay
|
|
|
+ AudioPlay,
|
|
|
},
|
|
|
inject: ['convertText'],
|
|
|
props: {
|
|
|
- isEnableVoice:{
|
|
|
+ isEnableVoice: {
|
|
|
type: String,
|
|
|
- default: 'false'
|
|
|
+ default: 'false',
|
|
|
},
|
|
|
audioFileId: {
|
|
|
type: String,
|
|
|
@@ -175,19 +231,32 @@ export default {
|
|
|
for (const item of this.richTextList) {
|
|
|
oldIndex += 1;
|
|
|
|
|
|
- if (item.is_style === 'true' || item.is_style === true) {
|
|
|
+ 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) {
|
|
|
const tagText = item.text || '';
|
|
|
// 正则匹配闭标签:匹配 </tagname> 格式
|
|
|
const closeTagMatch = tagText.match(/^<\/(\w+)>$/);
|
|
|
if (closeTagMatch) {
|
|
|
// 闭标签:从栈顶开始查找最近的同名标签并移除(LIFO 原则)
|
|
|
const tagName = closeTagMatch[1];
|
|
|
- for (let i = tagStack.length - 1; i >= 0; i--) {
|
|
|
- if (tagStack[i].tag === tagName) {
|
|
|
- tagStack.splice(i, 1);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
+ this.removeTagFromStack(tagStack, tagName);
|
|
|
} else {
|
|
|
// 开标签:解析标签名和样式并压栈
|
|
|
const openTagMatch = tagText.match(/^<(\w+)([^>]*)>$/);
|
|
|
@@ -276,6 +345,14 @@ 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;
|
|
|
@@ -420,7 +497,7 @@ export default {
|
|
|
text-wrap: pretty;
|
|
|
hanging-punctuation: allow-end;
|
|
|
|
|
|
- >span {
|
|
|
+ > span {
|
|
|
display: inline-flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
@@ -451,7 +528,7 @@ export default {
|
|
|
</style>
|
|
|
|
|
|
<style lang="scss">
|
|
|
-.pinyin-area+.pinyin-area {
|
|
|
+.pinyin-area + .pinyin-area {
|
|
|
margin-top: 4px;
|
|
|
}
|
|
|
</style>
|