Forráskód Böngészése

富文本拼音效果标点符号处理

zq 1 hónapja
szülő
commit
3747f631a1

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "eep_page",
-  "version": "2026.04.16",
+  "version": "2026.07.08",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "eep_page",
-      "version": "2026.04.16",
+      "version": "2026.07.08",
       "hasInstallScript": true,
       "dependencies": {
         "@tinymce/tinymce-vue": "^3.2.8",

+ 4 - 4
src/components/PinyinText.vue

@@ -39,7 +39,7 @@
                   'align-items': getWordAlignItems(word, block),
                 }"
               >
-                <span v-if="shouldShowWordPinyin(word)" class="pinyin" :style="getPinyinStyle(word, block)">
+                <span class="pinyin" :style="getPinyinStyle(word, block)">
                   {{ getPinyinText(word) }}
                 </span>
                 <span class="py-char" :style="getCharStyle(word, block)">{{ convertText(word.text) }}</span>
@@ -52,7 +52,7 @@
                       'align-items': getWordAlignItems(word, block),
                     }"
                   >
-                    <span v-if="shouldShowWordPinyin(word)" class="pinyin" :style="getPinyinStyle(word, block)">
+                    <span class="pinyin" :style="getPinyinStyle(word, block)">
                       {{ getCharPinyin(word, cIndex) }}
                     </span>
                     <span class="py-char" :style="getCharStyle(word, block, cIndex)">{{ convertText(char) }}</span>
@@ -321,7 +321,7 @@ export default {
       };
     },
 
-    // 预处理 richTextList合并分散的视频标签
+    // 预处理 richTextList,合并分散的视频标签
     normalizedRichTextList() {
       if (!this.richTextList || this.richTextList.length === 0) return [];
 
@@ -808,7 +808,7 @@ export default {
     },
     // 兼容历史数据
     getPinyinText(item) {
-      return this.checkShowPinyin(item.showPinyin) ? item.pinyin.replace(/\s+/g, '') : '\u200B';
+      return this.checkShowPinyin(item.showPinyin) ? item.pinyin : '\u200B';
     },
     shouldShowWordPinyin(item) {
       return this.checkShowPinyin(item && item.showPinyin);

+ 3 - 1
src/views/book/courseware/create/components/base/common/CorrectPinyin.vue

@@ -96,7 +96,7 @@ import { addTone, handleToneValue } from '@/utils/common';
 import RichText from '@/components/RichText.vue';
 import _ from 'lodash';
 import { fontFamilyList } from '@/views/book/courseware/data/table.js';
-import { isEnable } from '@/views/book/courseware/data/common';
+import { isEnable, PUNCT_REGEX } from '@/views/book/courseware/data/common';
 import { toolGetWordPinyinCorrectionList } from '@/api/pinyinCorrection';
 
 export default {
@@ -174,8 +174,10 @@ export default {
         if (_showPinyin === undefined || _showPinyin === null) {
           _showPinyin = 'true';
         }
+        let text = this.dataContent.text.replace(PUNCT_REGEX, '');
         this.$set(this.dataContent, 'showPinyin', _showPinyin);
         this.$set(this.dataContent, 'activeTextStyle', style);
+        this.$set(this.dataContent, 'text', text);
         this.getPinyinList();
       },
       deep: true,

+ 19 - 2
src/views/book/courseware/create/components/base/rich_text/RichText.vue

@@ -131,7 +131,7 @@
 <script>
 import { getRichTextData } from '@/views/book/courseware/data/richText';
 import { PinyinBuild_OldFormat } from '@/api/book';
-import { isEnable } from '@/views/book/courseware/data/common';
+import { isEnable, mergePunctuationToWords } from '@/views/book/courseware/data/common';
 
 import ModuleMixin from '../../common/ModuleMixin';
 import PinyinText from '@/components/PinyinText.vue';
@@ -342,6 +342,24 @@ export default {
       await PinyinBuild_OldFormat(data)
         .then(({ parsed_text, rich_text }) => {
           this.loading = false;
+          let richTextList = [];
+          if (rich_text && Array.isArray(rich_text.text_list)) {
+            richTextList = rich_text.text_list.map((item) => {
+              // 非 word_list 项,原样返回
+              if (item.is_style === 'true' || !Array.isArray(item.word_list)) {
+                return item;
+              }
+              return {
+                ...item,
+                word_list: mergePunctuationToWords(item.word_list),
+              };
+            });
+
+            // ✅ Vue2 响应式安全赋值
+            this.$set(this.data, 'rich_text_list', richTextList);
+          }
+          console.log('rich_text', richTextList);
+
           if (parsed_text) {
             const mergedData = parsed_text.paragraph_list.map((outerArr, i) =>
               outerArr.map((innerArr, j) =>
@@ -372,7 +390,6 @@ export default {
             this.paragraphVersion += 1;
             this.parseFClist(); // 确保在这里调用
           }
-          this.$set(this.data, 'rich_text_list', rich_text.text_list);
         })
         .catch(() => {
           this.loading = false;

+ 135 - 0
src/views/book/courseware/data/common.js

@@ -301,3 +301,138 @@ export const speedRatioList = [
   { value: 1.5, label: '1.5' },
   { value: 2, label: '2.0' },
 ];
+
+export const TRAILING_PUNCT = new Set([
+  '。',
+  ',',
+  '、',
+  ';',
+  ':',
+  '?',
+  '!',
+  '”',
+  '’',
+  '"',
+  "'",
+  ')',
+  ')',
+  ']',
+  '】',
+  '}',
+  '』',
+  '》',
+  '〉',
+  '>',
+]);
+
+export const LEADING_PUNCT = new Set(['“', '‘', '"', "'", '(', '(', '[', '【', '{', '『', '《', '〈', '<']);
+
+export const PUNCT_REGEX = new RegExp(
+  `[${[...LEADING_PUNCT, ...TRAILING_PUNCT].map((c) => c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('')}]`,
+  'g',
+);
+/**
+ * 创建一个单词对象
+ * @param {*} word
+ * @param {*} type 1 为前置标点,2 为后置标点
+ */
+
+function cloneWord(word, type) {
+  if (!word || typeof word !== 'object') return word;
+  let result = {
+    ...word,
+    pinyin_list: Array.isArray(word.pinyin_list) ? [...word.pinyin_list] : [],
+    annota_list: Array.isArray(word.annota_list) ? [...word.annota_list] : [],
+  };
+  // 如果type==1,则在pinyin_list开头加一个全角空格,如果type==2,则在pinyin_list末尾加一个全角空格
+  if (type === 1) {
+    result.pinyin_list.unshift(' ');
+    result.pinyin = ` ${result.pinyin}`;
+  } else if (type === 2) {
+    result.pinyin_list.push(' ');
+    result.pinyin += ' ';
+  }
+  return result;
+}
+
+function isTrailingPunct(text) {
+  return TRAILING_PUNCT.has(text);
+}
+
+function isLeadingPunct(text) {
+  return LEADING_PUNCT.has(text);
+}
+
+/**
+ * ✅ 判断是否是任意标点符号
+ * 用于:前置标点后面是否“值得吸附”
+ */
+export function isPunctuation(text) {
+  if (typeof text !== 'string') return false;
+  return TRAILING_PUNCT.has(text) || LEADING_PUNCT.has(text);
+}
+
+/**
+ * 严格版标点吸附:
+ * - 后置标点:前一个有文本才吸附
+ * - 前置标点:后一个有文本才吸附
+ * - 连续标点:各自独立判断
+ */
+export function mergePunctuationToWords(wordList) {
+  if (!Array.isArray(wordList)) return wordList;
+  const result = [];
+  let lastWord = null;
+
+  for (let i = 0; i < wordList.length; i++) {
+    const word = wordList[i];
+    if (!word || typeof word.text !== 'string') {
+      result.push(word);
+      continue;
+    }
+
+    const text = word.text;
+
+    // 后置标点:前为分词才吸附
+    if (isTrailingPunct(text)) {
+      if (lastWord) {
+        lastWord.text += `${text}`;
+        lastWord.pinyin_list.push(' ');
+        lastWord.pinyin += ' ';
+      } else {
+        result.push(cloneWord(word, 2));
+      }
+      continue;
+    }
+
+    // 前置标点:只看“下一个 word”
+    if (isLeadingPunct(text)) {
+      const nextWord = wordList[i + 1];
+
+      if (
+        nextWord &&
+        typeof nextWord.text === 'string' &&
+        !isPunctuation(nextWord.text) // 下一个不是标点
+      ) {
+        // 吸附到下一个分词前面
+        const clonedNext = cloneWord(nextWord, 1);
+        clonedNext.text = `${text}${clonedNext.text}`;
+        result.push(clonedNext);
+        lastWord = clonedNext;
+
+        i++; // 跳过下一个 word(已经被合并)
+        continue;
+      }
+
+      // 后面没词 or 还是标点 → 原样返回
+      result.push(cloneWord(word));
+      continue;
+    }
+
+    // 普通分词
+    const cloned = cloneWord(word);
+    result.push(cloned);
+    lastWord = cloned;
+  }
+  console.log('result', result);
+  return result;
+}