Browse Source

富文本增加右缩进,有字间距的情况拼音也居中

zq 1 month ago
parent
commit
8803ecd83b

+ 38 - 17
src/components/PinyinText.vue

@@ -192,7 +192,7 @@
 <script>
 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 { isEnable, PUNCT_REGEX } from '@/views/book/courseware/data/common';
 import AudioPlay from '@/views/book/courseware/preview/components/character_base/components/AudioPlay.vue';
 
 // MathHtml 组件用于渲染数学公式的 HTML 内容,保留 MathJax 的原始结构
@@ -686,22 +686,27 @@ export default {
 
       const styleObj = combinedStyle ? this.parseStyleToObject(combinedStyle) : null;
 
-      // 提取 paddingLeft 用于段落缩进,从 styleObj 中移除
       const sentenceStyle = {};
-      if (styleObj && styleObj.paddingLeft) {
-        sentenceStyle.paddingLeft = styleObj.paddingLeft;
-        delete styleObj.paddingLeft;
-      }
-      // 提取 lineHeight 用于行高,从 styleObj 中移除
-      if (styleObj && styleObj.lineHeight) {
-        sentenceStyle.lineHeight = styleObj.lineHeight;
-        delete styleObj.lineHeight;
-      }
-
       let parentStyle = {};
-      if (styleObj && styleObj.textAlign === 'center') {
-        parentStyle['text-align'] = 'center';
-        parentStyle['display'] = 'block';
+      if (styleObj) {
+        // 提取 paddingLeft 用于段落缩进,从 styleObj 中移除
+        if (styleObj.paddingLeft) {
+          sentenceStyle.paddingLeft = styleObj.paddingLeft;
+          delete styleObj.paddingLeft;
+        }
+        if (styleObj.paddingRight) {
+          sentenceStyle.paddingRight = styleObj.paddingRight;
+          delete styleObj.paddingRight;
+        }
+        // 提取 lineHeight 用于行高,从 styleObj 中移除
+        if (styleObj.lineHeight) {
+          sentenceStyle.lineHeight = styleObj.lineHeight;
+          delete styleObj.lineHeight;
+        }
+        if (styleObj.textAlign) {
+          parentStyle['text-align'] = styleObj.textAlign;
+          parentStyle['display'] = 'block';
+        }
       }
 
       return {
@@ -909,6 +914,22 @@ export default {
       styles['font-style'] = 'normal';
       styles['letter-spacing'] = '0';
 
+      // 如果block.styleObj里面有letter-spacing属性,则给styles里面加margin-left
+      if (block.styleObj && block.styleObj.letterSpacing) {
+        const allCount = Array.from(item.text).length;
+        const charCount = item.text.replace(PUNCT_REGEX, '').length;
+        const letterSpacing = block.styleObj.letterSpacing;
+        // 提取数值部分
+        const match = letterSpacing.match(/^([\d.]+)(\D*)$/);
+        if (match) {
+          const value = parseFloat(match[1]);
+          const unit = match[2] || 'em';
+          const compensation =
+            Math.max(value + 0.5, (charCount * value) / 2) + (allCount - charCount) * value * (charCount === 1 ? 0 : 2);
+          styles['margin-left'] = `-${compensation}${unit}`;
+        }
+      }
+
       return styles;
     },
     removeTagFromStack(tagStack, tagName) {
@@ -992,7 +1013,7 @@ export default {
     }
 
     .pinyin-sentence {
-      display: inline;
+      display: inline-block;
       white-space: normal;
     }
 
@@ -1030,7 +1051,7 @@ export default {
 
   .pinyin-paragraph {
     .pinyin-sentence {
-      display: inline;
+      display: inline-block;
       white-space: normal;
     }
   }

+ 74 - 4
src/components/RichText.vue

@@ -93,7 +93,7 @@ export default {
       type: [String, Boolean],
       /* eslint-disable max-len */
       default:
-        'fontselect fontsizeselect forecolor backcolor lineheight paragraphSpacing letterSpacing fullwidthspace indent outdent customUnderline bold italic strikethrough alignleft aligncenter alignright bullist numlist dotEmphasis image media link blockquote hr mathjax',
+        'fontselect fontsizeselect forecolor backcolor lineheight paragraphSpacing letterSpacing fullwidthspace indent outdent rightindent rightoutdent customUnderline bold italic strikethrough alignleft aligncenter alignright bullist numlist dotEmphasis image media link blockquote hr mathjax',
     },
     wordlimitNum: {
       type: [Number, Boolean],
@@ -211,8 +211,8 @@ export default {
         plugins: 'link lists image hr media autoresize ax_wordlimit paste', // 移除 lineheight
         toolbar: this.toolbar, // 工具栏
         lineheight_formats: '0.5 1.0 1.2 1.5 2.0 2.5 3.0', // 行高选项(倍数)
-        paragraphheight_formats: [1.0, 1.2, 1.5, 2.0, 2.5, 3.0], // 段落间距
-        letterSpacing_formats: [1.0, 1.2, 1.5, 2.0, 2.5, 3.0], // 字间距选项
+        paragraphheight_formats: [0.5, 1.0, 1.2, 1.5, 2.0, 2.5, 3.0], // 段落间距
+        letterSpacing_formats: [0.5, 1.0, 1.2, 1.5, 2.0, 2.5, 3.0], // 字间距选项
         contextmenu: false, // 右键菜单
         menubar: false, // 菜单栏
         branding: false, // 品牌
@@ -269,6 +269,12 @@ export default {
                 remove_similar: true,
               });
             }
+            if (!editor.formatter.has('rightIndent')) {
+              editor.formatter.register('rightIndent', {
+                block: 'p',
+                styles: { 'padding-right': '40px' },
+              });
+            }
             if (!editor.formatter.has('coloredunderline')) {
               editor.formatter.register('coloredunderline', {
                 inline: 'span',
@@ -416,7 +422,7 @@ export default {
               });
               items.push({
                 type: 'menuitem',
-                text: '清除间距',
+                text: '清除段落间距',
                 onAction: () => {
                   // 清除所有间距格式
                   this.init.paragraphheight_formats.forEach((config) => {
@@ -509,6 +515,70 @@ export default {
             },
           });
 
+          // 注册右缩进自定义图标
+          editor.ui.registry.addIcon(
+            'rightindent',
+            '<svg viewBox="0 0 18 18" width="18" height="18"><rect x="1.5" y="2" width="15" height="1.8" rx="0.9" fill="currentColor"/><rect x="1.5" y="6.6" width="9" height="1.8" rx="0.9" fill="currentColor"/><rect x="1.5" y="11.2" width="9" height="1.8" rx="0.9" fill="currentColor"/><rect x="1.5" y="15.8" width="15" height="1.8" rx="0.9" fill="currentColor"/><polyline points="16.5,8 14.625,10 16.5,12" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>',
+          );
+          editor.ui.registry.addIcon(
+            'rightoutdent',
+            '<svg viewBox="0 0 18 18" width="18" height="18"><rect x="1.5" y="2" width="15" height="1.8" rx="0.9" fill="currentColor"/><rect x="1.5" y="6.6" width="9" height="1.8" rx="0.9" fill="currentColor"/><rect x="1.5" y="11.2" width="9" height="1.8" rx="0.9" fill="currentColor"/><rect x="1.5" y="15.8" width="15" height="1.8" rx="0.9" fill="currentColor"/><polyline points="15,8 16.875,10 15,12" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>',
+          );
+
+          // 获取当前段落的右缩进像素值
+          const getRightIndentPx = (editor) => {
+            const node = editor.selection.getNode();
+            const p = editor.dom.getParent(node, 'p');
+            if (!p) return 0;
+            const pr = editor.dom.getStyle(p, 'padding-right', true);
+            const num = parseFloat(pr);
+            return isNaN(num) ? 0 : num;
+          };
+
+          // 增加右缩进按钮
+          editor.ui.registry.addButton('rightindent', {
+            icon: 'rightindent',
+            tooltip: '增加右缩进',
+            onAction: () => {
+              const cur = getRightIndentPx(editor);
+              const newVal = cur + 40;
+              editor.formatter.remove('rightIndent');
+              editor.formatter.register('rightIndent', {
+                block: 'p',
+                styles: { 'padding-right': `${newVal}px` },
+              });
+              editor.formatter.apply('rightIndent');
+            },
+          });
+
+          // 减少右缩进按钮
+          editor.ui.registry.addButton('rightoutdent', {
+            icon: 'rightoutdent',
+            tooltip: '减少右缩进',
+            onAction: () => {
+              const cur = getRightIndentPx(editor);
+              if (cur <= 40) {
+                editor.formatter.remove('rightIndent');
+              } else {
+                const newVal = cur - 40;
+                editor.formatter.remove('rightIndent');
+                editor.formatter.register('rightIndent', {
+                  block: 'p',
+                  styles: { 'padding-right': `${newVal}px` },
+                });
+                editor.formatter.apply('rightIndent');
+              }
+            },
+            onSetup: (api) => {
+              const handler = () => {
+                const cur = getRightIndentPx(editor);
+                api.setDisabled(cur <= 0);
+              };
+              editor.on('NodeChange', handler);
+              return () => editor.off('NodeChange', handler);
+            },
+          });
+
           // 注册自定义全角空格图标
           editor.ui.registry.addIcon(
             'fullwidthspace',

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

@@ -18,7 +18,7 @@
           <el-button type="primary" @click="saveWord">保存</el-button>
         </div>
       </div>
-      <el-input v-model="noPosContent" class="no-pos-content" type="textarea" :autosize="{ minRows: 27 }" />
+      <el-input v-model="noPosContent" class="no-pos-content" type="textarea" :autosize="{ minRows: 22 }" />
     </div>
   </div>
 </template>

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

@@ -65,8 +65,8 @@
         class="pinyin-input"
         @change="convertTonePinyin"
       >
-        <el-option v-for="item in pinyinList" :key="item.pinyin" :value="item.pinyin.replace(/,/g, '')">
-          <span style="float: left">{{ item.pinyin ? item.pinyin.replace(/,/g, '') : '' }}</span>
+        <el-option v-for="item in pinyinList" :key="item.pinyin" :value="item.pinyin?.replace(/,/g, '')">
+          <span style="float: left">{{ item.pinyin ? item.pinyin?.replace(/,/g, '') : '' }}</span>
           <span style="float: right; font-size: 13px; color: #8492a6">{{
             item.storage_type === 1 ? '机构库' : item.storage_type === 2 ? '全域库' : '个人库'
           }}</span>
@@ -176,7 +176,7 @@ export default {
         if (_showPinyin === undefined || _showPinyin === null) {
           _showPinyin = 'true';
         }
-        let text = this.dataContent.text.replace(PUNCT_REGEX, '');
+        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);