Jelajahi Sumber

预览优化

dsy 3 hari lalu
induk
melakukan
5602753ca9

+ 1 - 1
.env

@@ -11,4 +11,4 @@ VUE_APP_BookWebSI = '/GCLSBookWebSI/ServiceInterface'
 VUE_APP_EepServer = '/EEPServer/SI'
 
 #version
-VUE_APP_VERSION = '2026.10.09'
+VUE_APP_VERSION = '2026.10.10'

+ 1 - 1
packages/package.json

@@ -1,6 +1,6 @@
 {
   "name": "eep-ui",
-  "version": "0.0.57",
+  "version": "0.0.58",
   "main": "eep/eep-ui.umd.js",
   "style": "eep/eep-ui.css",
   "exports": {

+ 1 - 1
src/components/AuditRemark.vue

@@ -26,7 +26,7 @@
             <SvgIcon :icon-class="item.icon_type" />
             <span class="file-item-name">{{ item.file_name }}</span>
             <AudioPlay v-if="item.icon_type === 'mp3'" :file-id="item.file_url" />
-            <SvgIcon icon-class="uploadPreview" @click="viewDialog(item)" v-else />
+            <SvgIcon v-else icon-class="uploadPreview" @click="viewDialog(item)" />
             <SvgIcon icon-class="download" @click="downLoad(item)" />
           </div>
         </template>

+ 1 - 1
src/components/CommonPreview.vue

@@ -150,7 +150,7 @@
             :component-remark-obj="remark_list_obj"
             :project="project"
             :type="type"
-            :previewContentColor="previewContentColor"
+            :preview-content-color="previewContentColor"
             @computeScroll="computeScroll"
             @addRemark="addRemark"
             @editNote="handEditNote"

+ 6 - 4
src/components/RichText.vue

@@ -212,7 +212,8 @@ export default {
         width: '100%',
         autoresize_bottom_margin: 0,
         plugins: 'link lists image hr media autoresize ax_wordlimit paste', // 移除 lineheight
-        toolbar: this.toolbar, // 工具栏
+        toolbar:
+          typeof this.toolbar === 'string' ? this.toolbar.replace(/\blineheight\b/g, 'lineheightCustom') : this.toolbar, // 工具栏
         lineheight_formats: '0.5 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], // 字间距选项
@@ -297,15 +298,16 @@ export default {
           });
 
           // 自定义行高下拉(因为没有内置 lineheight 插件)
-          editor.ui.registry.addMenuButton('lineheight', {
-            text: '行高',
+          editor.ui.registry.addMenuButton('lineheightCustom', {
+            icon: 'line-height',
+            text: '',
             tooltip: '行高',
             fetch: (callback) => {
               const formats = (this.init.lineheight_formats || '').split(/\s+/).filter(Boolean);
               const items = formats.map((v) => {
                 return {
                   type: 'menuitem',
-                  text: v,
+                  text: v === '1.5' ? '1.5(默认)' : v,
                   onAction: () => {
                     try {
                       const name = `lineheight_${v.replace(/\./g, '_')}`;

+ 6 - 5
src/views/book/courseware/create/components/CreateCanvas.vue

@@ -472,15 +472,16 @@ export default {
         activeElement &&
         (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable);
       if (isInputting) return;
+      if (!this.curComponentId) return;
 
-      const isAltV =
-        event.altKey &&
-        !event.ctrlKey &&
+      const isCtrlC =
+        event.ctrlKey &&
+        !event.altKey &&
         !event.metaKey &&
         !event.shiftKey &&
-        (event.code === 'KeyV' || String(event.key).toLowerCase() === 'c');
+        (event.code === 'KeyC' || String(event.key).toLowerCase() === 'c');
 
-      if (isAltV) {
+      if (isCtrlC) {
         event.preventDefault();
         this.findChildComponentByKey(`grid-${this.curComponentId}`)?.copyComponent();
       }

+ 6 - 6
src/views/book/courseware/create/components/question/notes/Notes.vue

@@ -137,17 +137,17 @@
                 <el-radio label="inner">内部跳转</el-radio>
               </el-radio-group>
             </el-form-item>
-            <el-form-item label="关联链接" v-if="matchObj.type === 'outer'">
-              <el-input v-model="matchObj.con"></el-input>
+            <el-form-item v-if="matchObj.type === 'outer'" label="关联链接">
+              <el-input v-model="matchObj.con" />
             </el-form-item>
-            <el-form-item label="关联节点" v-else>
+            <el-form-item v-else label="关联节点">
               <el-cascader
+                :ref="refId"
                 v-model="matchObj.source_courseware_id_list"
                 :options="courseware_node_list"
                 :show-all-levels="false"
-                @change="handleChangeCourseware"
                 clearable
-                :ref="refId"
+                @change="handleChangeCourseware"
               />
             </el-form-item>
           </el-form>
@@ -183,7 +183,7 @@ export default {
       matchObj: null,
       activeMatchRow: null,
       courseware_node_list: [],
-      refId: 'Notes-' + Math.random().toString(36).substring(2, 12),
+      refId: `Notes-${Math.random().toString(36).substring(2, 12)}`,
     };
   },
   watch: {

+ 2 - 1
src/views/book/courseware/create/components/question/pinyin_base/PinyinBase.vue

@@ -32,7 +32,8 @@
             style="width: 200px"
           />
         </div>
-        <span v-if="data.property.fun_type === 'input'" class="tips"
+        <span
+v-if="data.property.fun_type === 'input'" class="tips"
           >在需要加空的内容处插入 3 个或以上的下划线“_”。</span
         >
         <span v-else-if="data.property.fun_type === 'mark'" class="tips">轻声不需要标注数字0</span>

+ 4 - 4
src/views/book/courseware/create/index.vue

@@ -131,14 +131,14 @@ export default {
         (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA' || activeElement.isContentEditable);
       if (isInputting) return;
 
-      const isAltV =
-        event.altKey &&
-        !event.ctrlKey &&
+      const isCtrlV =
+        event.ctrlKey &&
+        !event.altKey &&
         !event.metaKey &&
         !event.shiftKey &&
         (event.code === 'KeyV' || String(event.key).toLowerCase() === 'v');
 
-      if (isAltV) {
+      if (isCtrlV) {
         event.preventDefault();
         this.pasteComponent('bottom');
       }

+ 1 - 2
src/views/book/courseware/data/newWord.js

@@ -54,7 +54,6 @@ export const frontViewList = [
     value: 'card',
     label: '卡片',
   },
-  
 ];
 
 export function getOption() {
@@ -91,7 +90,7 @@ export function getNewWordProperty() {
     voice_type: '', // 音色
     emotion: '', // 风格,情感
     speed_ratio: '', // 语速
-    front_view:'table', // 主视图 
+    front_view: 'table', // 主视图
   };
 }
 

+ 2 - 2
src/views/book/courseware/data/notes.js

@@ -36,8 +36,8 @@ export function getOption() {
       type: 'outer',
       con: '',
       source_courseware_id_list: [],
-      source_courseware_name:''
-    }
+      source_courseware_name: '',
+    },
   };
 }
 export function getNotesProperty() {

+ 1 - 1
src/views/book/courseware/data/pinyinBase.js

@@ -79,7 +79,7 @@ export function getPinyinBaseProperty() {
     voice_type: '', // 音色
     emotion: '', // 风格,情感
     speed_ratio: '', // 语速
-    content_width:'', // 内容宽度
+    content_width: '', // 内容宽度
   };
 }
 

+ 1 - 1
src/views/book/courseware/preview/components/character/CharacterPreview.vue

@@ -223,6 +223,7 @@
                     {{ items.pinyin }}
                   </div> -->
                   <Strockplayredline
+                    v-if="items && items.type === 'hanzi'"
                     :Book_text="items.con"
                     :play-storkes="isEnable(data.property.is_enable_stroke)"
                     :cur-item="null"
@@ -242,7 +243,6 @@
                           ? data.unified_attrib.topic_color
                           : '#346cda',
                     }"
-                    v-if="items && items.type === 'hanzi'"
                   />
                   <template v-if="items && items.type === 'img'">
                     <el-image

+ 5 - 5
src/views/book/courseware/preview/components/character_base/components/Strockred.vue

@@ -18,6 +18,11 @@ const HanziWriter = require('hanzi-writer');
 export default {
   name: 'Strockred',
   components: {},
+  inject: {
+    bookStatus: {
+      default: 'online',
+    },
+  },
   props: {
     bookText: {
       type: String,
@@ -52,11 +57,6 @@ export default {
       default: 'tian',
     },
   },
-  inject: {
-    bookStatus: {
-      default: 'online',
-    },
-  },
   data() {
     return {
       writer: null,

+ 7 - 7
src/views/book/courseware/preview/components/drawing/DrawingPreview.vue

@@ -47,13 +47,13 @@
     <div class="footer">
       <div class="pen">
         <!-- 选择颜色 -->
-        <el-color-picker v-model="lineColor" @change="changeColor" :disabled="disabled" />
+        <el-color-picker v-model="lineColor" :disabled="disabled" @change="changeColor" />
         <!-- 清除一笔 -->
         <img
           class="clean-btn"
           src="@/assets/drawing-back.png"
-          @click="undo"
           :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
+          @click="undo"
         />
         <!-- 橡皮擦 -->
         <!-- <SvgIcon icon-class="eraser" size="24" @click="eraser" :style="{ color: isEraser ? 'green' : '#000' }" /> -->
@@ -61,28 +61,28 @@
         <img
           class="clean-btn"
           src="@/assets/icon-clean.png"
-          @click="clear"
           :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
+          @click="clear"
         />
         <img
           :src="penIndex == 0 ? thinpenActive : thinpen"
           class="pen-img"
-          @click="selSize(0)"
           :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
+          @click="selSize(0)"
         />
         <img
           :src="penIndex == 1 ? thickpenActive : thickpen"
           class="pen-img"
-          @click="selSize(1)"
           :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
+          @click="selSize(1)"
         />
         <img
           :src="penIndex == 2 ? thicskpenActive : thicskpen"
           class="pen-img"
-          @click="selSize(2)"
           :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }"
+          @click="selSize(2)"
         />
-        <div class="save" @click="save" :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }">
+        <div class="save" :style="{ cursor: disabled ? 'not-allowed' : 'pointer' }" @click="save">
           <SvgIcon icon-class="saves" size="24" title="保存" />
         </div>
       </div>

+ 33 - 39
src/views/book/courseware/preview/components/fill/FillPreview.vue

@@ -40,7 +40,6 @@
                           '--fill-font-family': data.property.fill_font,
                           width: (inputWidthMap[li.mark] || data.property.input_default_width) + 'px',
                           marginBottom: resolveFillControlMargin(li),
-                          ...getFillControlAlignmentStyle(li),
                         },
                       ]"
                       @input="handleInput(li.input, li.mark)"
@@ -311,47 +310,42 @@ export default {
     },
     /**
      * 计算填空控件需要补偿的 margin-bottom。
-     * 词级 margin-bottom 会撑高行盒,控件 vertical-align: bottom 会贴到被撑高的行盒底部,
-     * 因此给控件加上与段落一致的 margin-bottom,使其边框底部回到文本底部;
-     * 无 margin 时返回空字符串,保持原有显示不变。
+     * PinyinText 会按 line-height - 1.5em 为词级元素增加 margin-bottom。
+     * 输入框需要使用相同补偿,才能与词级文本处于同一视觉基线。
      * @param {Object} li 填空块对象
-     * @returns {String} 补偿值,如 '28pt',无则空字符串
+     * @returns {String} 与拼音词块行间距一致的补偿值
      */
     resolveFillControlMargin(li) {
-      const tagItem = (li?.rich_text_list || []).find((item) =>
-        /^<(p|div|h[1-6]|li|blockquote)\b[^>]*>/i.test(String(item?.text || '')),
-      );
-      const raw = String(tagItem?.text || '').match(/margin-bottom\s*:\s*([^;"']+)/i);
-      if (!raw) return '';
-      const match = raw[1].trim().match(/^([\d.]+)\s*(px|pt|em)?$/i);
-      if (!match) return '';
-      const amount = parseFloat(match[1]);
-      const unit = (match[2] || 'px').toLowerCase();
-      // px/pt 为绝对单位,可直接使用;em 相对词级字号,换算为绝对 pt 后再补偿
-      if (unit !== 'em') return raw[1].trim();
-      const fontSize = String(this.pinyinBodyStyles?.fontSize || '16pt');
-      const fsMatch = fontSize.match(/^([\d.]+)\s*(px|pt|em)?$/i);
-      if (!fsMatch) return raw[1].trim();
-      const fsAmount = parseFloat(fsMatch[1]);
-      const fsUnit = (fsMatch[2] || 'pt').toLowerCase();
-      if (fsUnit === 'em') return `${amount * fsAmount}em`;
-      if (fsUnit === 'pt') return `${amount * fsAmount}pt`;
-      return `${amount * fsAmount}px`;
-    },
-    getFillControlAlignmentStyle(li) {
-      const tagItem = (li?.rich_text_list || []).find((item) =>
-        /^<(p|div|h[1-6]|li|blockquote)\b[^>]*>/i.test(String(item?.text || '')),
-      );
-      const lineHeight = String(tagItem?.text || '').match(/line-height\s*:\s*([^;"']+)/i);
-      if (!lineHeight || !/^\d*\.?\d+$/.test(lineHeight[1].trim())) return {};
-
-      const ratio = Number(lineHeight[1].trim());
-      if (ratio < 0 || ratio >= 1.5) return {};
-      return {
-        marginBottom: '',
-        position: 'relative',
-        top: `${1.5 - ratio}em`,
-      };
+      let lineHeight = '';
+      (li?.rich_text_list || []).forEach((item) => {
+        const match = String(item?.text || '').match(/line-height\s*:\s*([^;"']+)/i);
+        if (match) lineHeight = match[1].trim();
+      });
+      if (!/^\d*\.?\d+$/.test(lineHeight)) return '';
+
+      const ratio = Number(lineHeight);
+      const spacing = ratio - 1.5;
+      const lineHeightOffsets = [
+        { ratio: 1, offset: -6 },
+        { ratio: 1.2, offset: -3 },
+        { ratio: 1.5, offset: -2 },
+        { ratio: 2, offset: 2 },
+        { ratio: 2.5, offset: 6 },
+        { ratio: 3, offset: 10 },
+      ];
+      const lower = [...lineHeightOffsets].reverse().find((item) => item.ratio <= ratio);
+      const upper = lineHeightOffsets.find((item) => item.ratio >= ratio);
+      let offset = 0;
+      if (!lower || !upper) {
+        offset = lineHeightOffsets[ratio < 1 ? 0 : lineHeightOffsets.length - 1].offset;
+      } else if (lower.ratio === upper.ratio) {
+        offset = lower.offset;
+      } else {
+        offset = lower.offset + ((ratio - lower.ratio) / (upper.ratio - lower.ratio)) * (upper.offset - lower.offset);
+      }
+      const offsetExpression = offset < 0 ? ` - ${Math.abs(offset)}px` : ` + ${offset}px`;
+
+      return `calc(${spacing}em${offsetExpression})`;
     },
     handleWav(data) {
       this.data.record_list = data;

+ 23 - 25
src/views/book/courseware/preview/components/newWord_template/components/Strockplayredline.vue

@@ -119,32 +119,30 @@ export default {
             onComplete(charData);
           },
         });
+      } else if (_this.hz_json) {
+        _this.writer = HanziWriter.default.create(_this.targetDiv, _this.Book_text, {
+          charDataLoader(char, onComplete) {
+            onComplete(_this.hz_json);
+          },
+          padding: 5,
+          showOutline: true,
+          strokeColor: _this.strokeColor ? _this.strokeColor : '#333',
+        });
       } else {
-        if (_this.hz_json) {
-          _this.writer = HanziWriter.default.create(_this.targetDiv, _this.Book_text, {
-            charDataLoader(char, onComplete) {
-              onComplete(_this.hz_json);
-            },
-            padding: 5,
-            showOutline: true,
-            strokeColor: _this.strokeColor ? _this.strokeColor : '#333',
-          });
-        } else {
-          _this.writer = HanziWriter.default.create(_this.targetDiv, _this.Book_text, {
-            charDataLoader(char, onComplete) {
-              let MethodName = 'hz_resource_manager-GetHZStrokesContent';
-              let data = {
-                hz: char,
-              };
-              GetStaticResources(MethodName, data).then((res) => {
-                onComplete(res);
-              });
-            },
-            padding: 5,
-            showOutline: true,
-            strokeColor: _this.strokeColor ? _this.strokeColor : '#333',
-          });
-        }
+        _this.writer = HanziWriter.default.create(_this.targetDiv, _this.Book_text, {
+          charDataLoader(char, onComplete) {
+            let MethodName = 'hz_resource_manager-GetHZStrokesContent';
+            let data = {
+              hz: char,
+            };
+            GetStaticResources(MethodName, data).then((res) => {
+              onComplete(res);
+            });
+          },
+          padding: 5,
+          showOutline: true,
+          strokeColor: _this.strokeColor ? _this.strokeColor : '#333',
+        });
       }
     },
     handleData(stroke_num) {

File diff ditekan karena terlalu besar
+ 3 - 4
src/views/book/courseware/preview/components/write_base/WriteBasePreview.vue


Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini