Browse Source

富文本拼音录入问题,3d模型 obj 文件材质加载问题

dsy 2 weeks ago
parent
commit
76069a4124

+ 1 - 1
.env

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

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "eep_page",
-  "version": "0.1.2",
+  "version": "2025.9.26",
   "private": true,
   "main": "main.js",
   "description": "智慧梧桐数字教材编辑器",

+ 7 - 2
src/components/RichText.vue

@@ -567,11 +567,13 @@ export default {
         .split(/<[^>]+>/g)
         .filter((item) => item)
         .some((item) => item.match(/[a-zA-Z]+\d(\s|&nbsp;)*/));
+
       if (!isHasPinyin) {
         return;
       }
       // 用标签分割富文本,保留标签
       let reg = /(<[^>]+>)/g;
+
       let text = content
         .split(reg)
         .filter((item) => item)
@@ -589,8 +591,11 @@ export default {
         // 二维数组,转为拼音,并打平为一维数组
         .map((item) => {
           if (/<[^>]+>/g.test(item)) return item;
+
           return item
-            .map(({ number, con }) => (number && con ? addTone(Number(number), con) : number || con || ''))
+            .map((li) =>
+              li.map(({ number, con }) => (number && con ? addTone(Number(number), con) : number || con || '')),
+            )
             .flat();
         })
         // 如果是数组,将数组字符串每两个之间加一个空格
@@ -599,6 +604,7 @@ export default {
           return item.join(' ');
         })
         .join('');
+
       // 更新 v-model
       this.$emit('input', text);
     },
@@ -625,7 +631,6 @@ export default {
     },
     showContentmenu({ pixelsFromLeft, pixelsFromTop }) {
       this.isShow = true;
-      // console.log(pixelsFromLeft, pixelsFromTop);
       this.contentmenu = {
         left: `${this.isViewNote ? pixelsFromLeft : pixelsFromLeft + 14}px`,
         top: `${this.isViewNote ? pixelsFromTop + 62 : pixelsFromTop + 22}px`,

+ 1 - 1
src/utils/common.js

@@ -40,7 +40,7 @@ export const tone_data = [
  * @returns String
  */
 export function addTone(number, con) {
-  const zmList = ['a', 'o', 'e', 'i', 'u', 'v', 'ü', 'A', 'O', 'E', 'I', 'U','n','m'];
+  const zmList = ['a', 'o', 'e', 'i', 'u', 'v', 'ü', 'A', 'O', 'E', 'I', 'U', 'n', 'm'];
   let cons = con;
   if (number) {
     for (let i = 0; i < zmList.length; i++) {

+ 13 - 2
src/views/book/courseware/create/components/base/3d_model/3DModel.vue

@@ -12,7 +12,7 @@
         :label-text="labelText"
         :accept-file-type="acceptFileType"
         :icon-class="iconClass"
-        :limit="1"
+        :limit="limit"
         :single-size="200"
         @updateFileList="updateFileList"
       />
@@ -33,12 +33,23 @@ export default {
     return {
       data: get3DModelData(),
       labelText: '3D模型',
-      acceptFileType: '.fbx,.obj,.gltf,.glb',
+      acceptFileType: '.fbx,.obj,.gltf,.glb,.mtl',
       iconClass: '3d',
+      limit: 1,
     };
   },
   methods: {
     updateFileList({ file_list, file_id_list }) {
+      if (file_list.length > 0) {
+        let suffixName =
+          'name' in file_list[0]
+            ? file_list[0].name.split('.').pop().toLowerCase()
+            : file_list[0].file_name.split('.').pop().toLowerCase();
+        if (suffixName === 'obj') {
+          this.limit = 2;
+        }
+      }
+
       this.data.model_list = file_list;
       this.data.model_id_list = file_id_list;
       this.data.file_id_list = file_id_list;

+ 2 - 2
src/views/book/courseware/create/components/base/common/UploadFile.vue

@@ -328,8 +328,8 @@ export default {
         fileType = ['zip', 'html'];
         typeTip = 'H5游戏文件只能是 zip、html 格式!';
       } else if (this.type === '3DModel') {
-        fileType = ['fbx', 'obj', 'gltf', 'glb'];
-        typeTip = '3D模型文件只能是 fbx、obj、gltf、glb 格式!';
+        fileType = ['fbx', 'obj', 'gltf', 'glb', 'mtl'];
+        typeTip = '3D模型文件只能是 fbx、obj、gltf、glb、mtl 格式!';
       }
       const isNeedType = fileType.includes(suffix);
       if (!isNeedType) {

+ 3 - 2
src/views/book/courseware/create/components/common/ModuleMixin.js

@@ -69,7 +69,7 @@ const mixin = {
     content: {
       handler(newVal) {
         if (this.type !== 'interaction') return;
-        if(newVal) this.data = JSON.parse(newVal);
+        if (newVal) this.data = JSON.parse(newVal);
       },
       immediate: true,
     },
@@ -198,7 +198,8 @@ const mixin = {
       } else if (i >= 0 && j >= 0) {
         data = this.data[attr][i][j];
       }
-      if (text === '' || data.paragraph_list.length > 0) {
+
+      if (text === '') {
         data.pinyin_proofread_word_list = [];
         return;
       }

+ 24 - 6
src/views/book/courseware/create/components/question/matching/Matching.vue

@@ -6,7 +6,15 @@
           <div v-for="(item, j) in li" :key="item.mark" class="option">
             <span class="serial-number">{{ computeOptionMethods[data.property.serial_number_type_list[j]](i) }}</span>
             <span class="option-content">
-              <RichText ref="richText" v-model="item.content" placeholder="请输入" :inline="true" :height="32" />
+              <RichText
+                ref="richText"
+                v-model="item.content"
+                placeholder="请输入"
+                :inline="true"
+                :height="32"
+                :is-view-pinyin="isEnable(data.property.view_pinyin)"
+                @createParsedTextInfoPinyin="handleParsedTextPinyin($event, i, j)"
+              />
             </span>
             <span class="multilingual" @click="openMultilingual(i, j)">
               <SvgIcon icon-class="multilingual" class-name="multilingual" width="12" height="12" />
@@ -75,6 +83,8 @@ export default {
       serialNumberTypeList,
       curSelectRow: -1,
       curSelectColumn: -1,
+      // 用于记录 data.option_list 中 content 的签名
+      optionContentSignature: '',
     };
   },
   computed: {
@@ -151,9 +161,9 @@ export default {
       },
       'handlerMindMap',
     ],
-    'data.property': {
-      handler({ view_pinyin }) {
-        if (!this.isEnable(view_pinyin)) {
+    'data.property.view_pinyin': {
+      handler(val) {
+        if (!this.isEnable(val)) {
           this.data.option_list.forEach((item) => {
             item.paragraph_list = [];
             item.paragraph_list_parameter = {
@@ -163,7 +173,9 @@ export default {
           });
           return;
         }
-        if (this.data.option_list.length > 0 && this.data.option_list[0].paragraph_list.length > 0) return;
+
+        if (this.data.option_list?.length > 0 && this.data.option_list[0].paragraph_list?.length > 0) return;
+
         this.data.option_list.forEach((item, i) => {
           item.forEach((option, j) => {
             const text = option.content.replace(/<[^>]+>/g, '');
@@ -173,7 +185,6 @@ export default {
           });
         });
       },
-      deep: true,
     },
   },
   methods: {
@@ -195,6 +206,13 @@ export default {
       this.curSelectColumn = -1;
       this.curSelectRow = -1;
     },
+    handleParsedTextPinyin(text, i, j) {
+      if (!this.isEnable(this.data.property.view_pinyin)) return;
+      if (this.data.option_list?.length > 0 && this.data.option_list[0].paragraph_list?.length > 0) return;
+      if (!text) return;
+      this.data.option_list[i][j].paragraph_list_parameter.text = text.replace(/<[^>]+>/g, '');
+      this.createParsedTextInfoPinyin(text, i, j);
+    },
   },
 };
 </script>

+ 16 - 4
src/views/book/courseware/preview/components/3d_model/3DModelPreview.vue

@@ -238,17 +238,29 @@ export default {
       );
     },
 
-    loadOBJ(loader, url) {
+    async loadOBJ(loader, url) {
       // 首先尝试加载MTL材质文件
-      const mtlUrl = url.replace(/\.obj$/i, '.mtl');
+      let mtlUrl = '';
       const mtlLoader = new MTLLoader();
+      if (this.data.model_list.length > 1) {
+        const fileStore = await GetFileStoreInfo({ file_id: this.data.model_list[1].file_id });
+        if (!fileStore || fileStore.error) {
+          console.error('mtl 材质文件加载失败:', fileStore);
+        } else {
+          mtlUrl = fileStore.file_url;
+        }
+      }
 
       // 设置材质加载路径
-      const basePath = url.substring(0, url.lastIndexOf('/') + 1);
+      const basePath = mtlUrl.substring(0, mtlUrl.lastIndexOf('/') + 1);
       mtlLoader.setPath(basePath);
+      mtlLoader.setResourcePath(basePath);
+
+      // 获取 mtl 文件名
+      const mtlFileName = mtlUrl.substring(mtlUrl.lastIndexOf('/') + 1);
 
       mtlLoader.load(
-        mtlUrl,
+        mtlFileName,
         (materials) => {
           // 材质文件加载成功
           materials.preload();