|
|
@@ -1,3 +1,4 @@
|
|
|
+<!-- eslint-disable vue/no-v-html -->
|
|
|
<template>
|
|
|
<div class="pinyin-area" :style="{ 'text-align': pinyinOverallPosition, padding: pinyinPadding }">
|
|
|
<AudioPlay
|
|
|
@@ -104,6 +105,15 @@
|
|
|
您的浏览器不支持音频播放
|
|
|
</audio>
|
|
|
</span>
|
|
|
+
|
|
|
+ <!-- 数学公式块(保留 MathJax 原始结构) -->
|
|
|
+ <MathHtml
|
|
|
+ v-else-if="block.type === 'math'"
|
|
|
+ :key="'math-' + index"
|
|
|
+ :html="block.html"
|
|
|
+ :style-obj="block.containerStyle"
|
|
|
+ :is-display="block.isDisplay"
|
|
|
+ />
|
|
|
<!-- 换行符 -->
|
|
|
<br v-else-if="block.type === 'newline'" :key="'newline-' + index" />
|
|
|
</template>
|
|
|
@@ -176,11 +186,44 @@ 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';
|
|
|
|
|
|
+// MathHtml 组件用于渲染数学公式的 HTML 内容,保留 MathJax 的原始结构
|
|
|
+const MathHtml = {
|
|
|
+ name: 'MathHtml',
|
|
|
+ functional: true,
|
|
|
+ props: {
|
|
|
+ html: {
|
|
|
+ type: String,
|
|
|
+ default: '',
|
|
|
+ },
|
|
|
+ styleObj: {
|
|
|
+ type: Object,
|
|
|
+ default: () => ({}),
|
|
|
+ },
|
|
|
+ isDisplay: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ render(h, context) {
|
|
|
+ return h('span', {
|
|
|
+ class: {
|
|
|
+ 'math-container': true,
|
|
|
+ 'math-display': context.props.isDisplay,
|
|
|
+ },
|
|
|
+ style: context.props.styleObj,
|
|
|
+ domProps: {
|
|
|
+ innerHTML: sanitizeHTML(context.props.html || ''),
|
|
|
+ },
|
|
|
+ });
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
export default {
|
|
|
name: 'PinyinText',
|
|
|
components: {
|
|
|
CorrectPinyin,
|
|
|
AudioPlay,
|
|
|
+ MathHtml,
|
|
|
},
|
|
|
inject: ['convertText'],
|
|
|
props: {
|
|
|
@@ -301,10 +344,10 @@ export default {
|
|
|
|
|
|
// 如果找到了结束标签,合并完成,跳出内部循环
|
|
|
if (nextItem.text && nextItem.text.includes('</video>')) {
|
|
|
- j++; // 跳过结束标签
|
|
|
+ j += 1; // 跳过结束标签
|
|
|
break;
|
|
|
}
|
|
|
- j++;
|
|
|
+ j += 1;
|
|
|
}
|
|
|
|
|
|
// 将合并后的内容作为一个新项加入结果
|
|
|
@@ -319,7 +362,7 @@ export default {
|
|
|
} else {
|
|
|
// 其他项直接加入
|
|
|
result.push(item);
|
|
|
- i++;
|
|
|
+ i += 1;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -335,10 +378,33 @@ export default {
|
|
|
let oldIndex = -1;
|
|
|
let paragraphIndex = 0;
|
|
|
const tagStack = [];
|
|
|
+ let mathContext = null;
|
|
|
|
|
|
for (const item of listToParse) {
|
|
|
oldIndex += 1;
|
|
|
|
|
|
+ if (mathContext) {
|
|
|
+ mathContext.html += this.getRawItemText(item);
|
|
|
+
|
|
|
+ if (item.is_style === 'true' || item.is_style === true) {
|
|
|
+ this.updateHtmlTagStack(mathContext.stack, item.text || '');
|
|
|
+ if (mathContext.stack.length === 0) {
|
|
|
+ blocks.push(this.parseMathBlock(mathContext.html, tagStack));
|
|
|
+ mathContext = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.isMathRootTag(item)) {
|
|
|
+ const rootTag = this.extractTagName(item.text || '');
|
|
|
+ mathContext = {
|
|
|
+ html: this.getRawItemText(item),
|
|
|
+ stack: rootTag ? [rootTag] : ['span'],
|
|
|
+ };
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
if (item.text && typeof item.text === 'string' && item.text.includes('<img')) {
|
|
|
blocks.push(this.parseImageBlock(item, tagStack));
|
|
|
} else if (item.text && typeof item.text === 'string' && item.text.includes('<video')) {
|
|
|
@@ -363,6 +429,10 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (mathContext) {
|
|
|
+ blocks.push(this.parseMathBlock(mathContext.html, tagStack));
|
|
|
+ }
|
|
|
+
|
|
|
return blocks;
|
|
|
},
|
|
|
},
|
|
|
@@ -628,6 +698,59 @@ export default {
|
|
|
return styleObj;
|
|
|
},
|
|
|
|
|
|
+ parseMathBlock(html, tagStack) {
|
|
|
+ const containerStyleObj = {};
|
|
|
+ tagStack.forEach((tagItem) => {
|
|
|
+ if (tagItem.style) {
|
|
|
+ this.mergeStyleString(containerStyleObj, tagItem.style);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const isDisplay =
|
|
|
+ /<mjx-container[^>]*\bdisplay=["']true["']/i.test(html) ||
|
|
|
+ /<mjx-assistive-mml[^>]*\bdisplay=["']block["']/i.test(html);
|
|
|
+
|
|
|
+ return {
|
|
|
+ type: 'math',
|
|
|
+ html,
|
|
|
+ containerStyle: containerStyleObj,
|
|
|
+ isDisplay,
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ isMathRootTag(item) {
|
|
|
+ if (!item || !(item.is_style === 'true' || item.is_style === true) || !item.text) return false;
|
|
|
+ return /class=["'][^"']*\bmathjax-container\b[^"']*\beditor-math\b[^"']*["']/i.test(item.text);
|
|
|
+ },
|
|
|
+
|
|
|
+ getRawItemText(item) {
|
|
|
+ if (!item) return '';
|
|
|
+ return item.text || '';
|
|
|
+ },
|
|
|
+
|
|
|
+ extractTagName(tagText) {
|
|
|
+ const match = (tagText || '').match(/^<\/?\s*([a-zA-Z][\w-]*)/);
|
|
|
+ return match ? match[1].toLowerCase() : '';
|
|
|
+ },
|
|
|
+
|
|
|
+ updateHtmlTagStack(tagStack, tagText) {
|
|
|
+ if (!tagText || !tagStack) return;
|
|
|
+
|
|
|
+ const tagName = this.extractTagName(tagText);
|
|
|
+ if (!tagName) return;
|
|
|
+
|
|
|
+ if (/^<\//.test(tagText)) {
|
|
|
+ for (let i = tagStack.length - 1; i >= 0; i--) {
|
|
|
+ if (tagStack[i] === tagName) {
|
|
|
+ tagStack.splice(i, 1);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (!/\/>\s*$/.test(tagText)) {
|
|
|
+ tagStack.push(tagName);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
getBlockColor(block) {
|
|
|
const colorFromObject = block?.styleObj?.color;
|
|
|
if (colorFromObject) return colorFromObject;
|
|
|
@@ -645,7 +768,7 @@ export default {
|
|
|
|
|
|
const blockColor = this.getBlockColor(block);
|
|
|
if (blockColor) {
|
|
|
- baseStyle.color = `${blockColor} !important`;
|
|
|
+ baseStyle.color = blockColor;
|
|
|
}
|
|
|
|
|
|
if (this.isAllSetting) {
|
|
|
@@ -717,7 +840,7 @@ export default {
|
|
|
|
|
|
const blockColor = this.getBlockColor(block);
|
|
|
if (blockColor) {
|
|
|
- styles['color'] = `${blockColor} !important`;
|
|
|
+ styles['color'] = blockColor;
|
|
|
}
|
|
|
|
|
|
// 如果设置了全局字号,优先使用全局字号
|
|
|
@@ -793,6 +916,22 @@ export default {
|
|
|
margin: 0.5em 0;
|
|
|
}
|
|
|
|
|
|
+ :deep(.mathjax-container.editor-math > svg[aria-hidden='true']) {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ .math-container.math-display {
|
|
|
+ display: block !important;
|
|
|
+ width: 100%;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ .math-container.math-display :deep(mjx-container[display='true']) {
|
|
|
+ display: block !important;
|
|
|
+ margin: 0 auto;
|
|
|
+ text-align: center;
|
|
|
+ }
|
|
|
+
|
|
|
span {
|
|
|
display: inline-flex;
|
|
|
}
|