ソースを参照

1. 修复空格无宽度 2.修复插入模板内容移动后数据丢失

dsy 3 ヶ月 前
親
コミット
7d15356dbf

+ 1 - 1
.env

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

+ 1 - 1
packages/package.json

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

+ 15 - 4
src/components/ColorPicker.vue

@@ -29,10 +29,21 @@ export default {
   },
   computed: {
     groupedPredefineColors() {
-      return [
-        ...this.commonList.map(({ color }) => `#${color}`),
-        ...this.recentlyUsedList.map(({ color }) => `#${color}`),
-      ];
+      let colors = [];
+      if ([1, 2].includes(this.type)) {
+        colors = [
+          ...this.commonList.map(({ color }) => `#${color}`),
+          ...this.recentlyUsedList.map(({ color }) => `#${color}`),
+        ];
+      }
+      if (this.type === 0) {
+        colors = [
+          ...this.commonList.map(({ r, g, b, a }) => `rgba(${r},${g},${b},${a})`),
+          ...this.recentlyUsedList.map(({ r, g, b, a }) => `rgba(${r},${g},${b},${a})`),
+        ];
+      }
+
+      return colors.length ? colors : undefined;
     },
   },
   created() {

+ 3 - 1
src/components/PinyinText.vue

@@ -1033,8 +1033,10 @@ export default {
     }
 
     .py-char {
-      ruby-align: center;
+      display: inline-block;
       line-height: 1em;
+      white-space: pre;
+      ruby-align: center;
     }
 
     .pinyin {

+ 62 - 26
src/views/book/courseware/create/components/CreateCanvas.vue

@@ -342,6 +342,23 @@ export default {
         }
       },
     },
+    isEdit: {
+      handler(val) {
+        if (val) {
+          const loading = this.$loading({
+            lock: true,
+            text: '组件加载中...',
+            spinner: 'el-icon-loading',
+          });
+          const timer = setInterval(() => {
+            if (this.componentIsAllLoader()) {
+              loading.close();
+              clearInterval(timer);
+            }
+          }, 200);
+        }
+      },
+    },
   },
   created() {
     const loading = this.$loading({
@@ -349,8 +366,8 @@ export default {
       text: '组件加载中...',
       spinner: 'el-icon-loading',
     });
-    ContentGetCoursewareContent({ id: this.courseware_id })
-      .then(({ content, content_group_row_list, title_list, my_project_resource_popedom }) => {
+    ContentGetCoursewareContent({ id: this.courseware_id }).then(
+      ({ content, content_group_row_list, title_list, my_project_resource_popedom }) => {
         if (content) {
           let parsedContent = JSON.parse(content);
           if (
@@ -363,9 +380,14 @@ export default {
           } else {
             this.data = parsedContent;
           }
-          this.componentsDataLoaded = true;
-          loading.close();
         }
+        const timer = setInterval(() => {
+          if (this.componentIsAllLoader()) {
+            loading.close();
+            clearInterval(timer);
+            this.componentsDataLoaded = true;
+          }
+        }, 200);
         if (content_group_row_list) this.content_group_row_list = JSON.parse(content_group_row_list);
 
         if (title_list) this.title_list = title_list || [];
@@ -381,10 +403,8 @@ export default {
         );
 
         this.projectResourcePopedom = my_project_resource_popedom || [];
-      })
-      .finally(() => {
-        loading.close();
-      });
+      },
+    );
     this.getBookUnifiedAttr();
   },
   mounted() {
@@ -399,6 +419,24 @@ export default {
   },
   methods: {
     /**
+     * 判断所有组件是否加载完成
+     * @returns {boolean} 是否所有组件加载完成
+     */
+    componentIsAllLoader() {
+      const toList = (ref) => {
+        if (Array.isArray(ref)) return ref;
+        return ref ? [ref] : [];
+      };
+
+      if (this.isEdit) {
+        const components = toList(this.$refs?.component);
+        return components.length === 0 || components.every((item) => item?.property?.isGetContent);
+      }
+
+      const previews = toList(this.$refs?.previewEdit?.$refs?.preview);
+      return previews.length === 0 || previews.every((item) => item?.loader);
+    },
+    /**
      * 将可见组件滚动到视图中心
      */
     async visibleIdScrollIntoView() {
@@ -578,16 +616,7 @@ export default {
      * @param {string} type 类型
      */
     async saveCoursewareContent(type) {
-      let isAllLoader = false;
-
-      if (this.$refs?.component === undefined || this.$refs?.component.length === 0) {
-        isAllLoader = true;
-      } else if (this.isEdit) {
-        isAllLoader = this.$refs?.component?.every((item) => item.property.isGetContent);
-      } else {
-        isAllLoader = this.$refs?.previewEdit?.$refs?.preview?.every((item) => item.loader);
-      }
-      if (!isAllLoader) {
+      if (!this.componentIsAllLoader()) {
         this.$message.warning('有组件内容未加载完成,请稍后再试');
         return false;
       }
@@ -1253,27 +1282,31 @@ export default {
      * 组件拖拽移动后,先删除组件,再根据拖拽位置计算插入行、列、格子
      */
     async handleComponentMove() {
+      // 放置位置无效(curRow 未在 dragEnd 中被正确更新),放弃移动
+      if (this.curRow < -1) {
+        this.$message.warning('组件放置位置无效,已取消移动');
+        this.resetMoveData();
+        return;
+      }
+
       const oldID = this.curParams?.id;
       const component = this.findChildComponentByKey(`grid-${oldID}`);
       // 获取组件内容,保存后再插入到新的位置
-      const requestData = component.getComponentRequestContent();
+      const requestData = component?.getComponentRequestContent();
 
       const data = component?.data;
       this.curType = data?.type || 'select';
       let col = component?.$attrs['data-col'];
       let row = component?.$attrs['data-row'];
       let grid = component?.$attrs['data-grid'];
-      let rowNum = 0; // 当前行组件数量
-      this.data.row_list[row].col_list.forEach((item) => {
-        rowNum += item.grid_list.length;
-      });
+      const oldId = component?.oldId || component?.$attrs?.oldId;
 
       this.curParams = {
         ...this.curParams,
         col,
         row,
         grid,
-        rowNum,
+        oldId,
       };
 
       // 根据 oldID 改变 data.row_list 中,将对应ID 改为新ID,并返回新ID,在删除时使用
@@ -1296,8 +1329,6 @@ export default {
       this.$nextTick(() => {
         this.deleteComponent(deleteID);
       });
-
-      this.resetMoveData();
     },
     /**
      * 根据旧组件 id 将 row_list 中对应 grid 的 id 替换为新 id,并返回新 id
@@ -1364,6 +1395,7 @@ export default {
     calculateGridObject() {
       const id = this.curParams?.id || `ID-${getRandomNumber(12, true)}`;
       const letter = `L${getRandomNumber(6, true)}`;
+      const oldId = this.curParams?.oldId;
 
       let row = this.data.row_list[this.curRow];
       let col = row.col_list[this.curCol];
@@ -1380,6 +1412,7 @@ export default {
           edit_height: 'auto',
           row: rowNum,
           type: this.curType,
+          oldId,
         });
         // 在新加入的 grid 后面的 row 都加 1
         grid.forEach((item, i) => {
@@ -1399,6 +1432,7 @@ export default {
           edit_height: 'auto',
           row: rowNum,
           type: this.curType,
+          oldId,
         });
 
         let allRowNum = grid.filter(({ row }) => row === rowNum).length;
@@ -1448,6 +1482,7 @@ export default {
             height: 'auto',
             edit_height: 'auto',
             type: this.curType,
+            oldId: this.curParams?.oldId,
           },
         ],
       });
@@ -1479,6 +1514,7 @@ export default {
                 edit_height: 'auto',
                 row: 1,
                 type: this.curType,
+                oldId: this.curParams?.oldId,
               },
             ],
           },

+ 17 - 0
src/views/book/courseware/create/components/PreviewEdit.vue

@@ -123,6 +123,7 @@ export default {
     };
   },
   created() {
+    this.init();
     document.addEventListener('mousemove', this.dragMove);
     document.addEventListener('mouseup', this.dragEnd);
   },
@@ -143,6 +144,22 @@ export default {
     }
   },
   methods: {
+    init() {
+      const loading = this.$loading({
+        lock: true,
+        text: '预览组件加载中...',
+        spinner: 'el-icon-loading',
+      });
+
+      const timer = setInterval(() => {
+        let isLoader = this.rowList.length === 0 || this.$refs?.preview?.every((item) => item.loader);
+
+        if (isLoader) {
+          loading.close();
+          clearInterval(timer);
+        }
+      }, 200);
+    },
     handleHeightChange(id, newHeight) {
       this.$emit('handleHeightChange', id, newHeight);
     },

+ 49 - 41
src/views/book/courseware/create/components/common/ModuleMixin.js

@@ -21,6 +21,7 @@ const mixin = {
       isEnable,
       property: {
         isGetContent: false, // 是否已获取内容
+        isSilentPropertyWatch: false, // 初始化组件内容时,临时静默 property 监听
       },
       borderColorObj: Vue.observable({ value: this.borderColor }), // 边框颜色
       multilingualVisible: false, // 多语言弹窗
@@ -115,59 +116,66 @@ const mixin = {
   methods: {
     GetCoursewareComponentContent() {
       if (this.courseware_id === '' || this.id === '') return;
+      this.property.isSilentPropertyWatch = true;
       let temporaryCoursewareID = this.getTemporaryCoursewareID();
       let coursewareID = temporaryCoursewareID || this.courseware_id;
       let component_id = temporaryCoursewareID.length > 0 ? this.oldId || this.id : this.id;
 
-      ContentGetCoursewareComponentContent({ courseware_id: coursewareID, component_id }).then(({ content }) => {
-        if (content) {
-          let oldData = JSON.parse(content);
-          if (oldData.type === 'audio') {
-            let p = oldData.property || {};
-            if (!p.file_name_display_mode) p.file_name_display_mode = 'true';
-            if (p.view_method === 'independent' && !p.style_mode) {
-              p.style_mode = 'middle';
+      ContentGetCoursewareComponentContent({ courseware_id: coursewareID, component_id })
+        .then(({ content }) => {
+          if (content) {
+            let oldData = JSON.parse(content);
+            if (oldData.type === 'audio') {
+              let p = oldData.property || {};
+              if (!p.file_name_display_mode) p.file_name_display_mode = 'true';
+              if (p.view_method === 'independent' && !p.style_mode) {
+                p.style_mode = 'middle';
+              }
+              if (!p.style_mode) p.style_mode = 'big';
+              if (p.view_method === 'icon') {
+                p.file_name_display_mode = 'false';
+                p.view_method = 'independent';
+                p.style_mode = 'small';
+              }
             }
-            if (!p.style_mode) p.style_mode = 'big';
-            if (p.view_method === 'icon') {
-              p.file_name_display_mode = 'false';
-              p.view_method = 'independent';
-              p.style_mode = 'small';
-            }
-          }
-          this.data = oldData;
-        } else {
-          const bookUnifiedAttr = this.getBookUnifiedAttr();
+            this.data = oldData;
+          } else {
+            const bookUnifiedAttr = this.getBookUnifiedAttr();
 
-          this.$set(this.data, 'unified_attrib', bookUnifiedAttr);
+            this.$set(this.data, 'unified_attrib', bookUnifiedAttr);
 
-          // 初始化时,如果有 view_pinyin 则根据全教材设置配置设 view_pinyin 和 pinyin_position
-          if ('view_pinyin' in this.data.property) {
-            this.data.property.view_pinyin = bookUnifiedAttr?.view_pinyin ?? 'false';
-            this.data.property.pinyin_position = bookUnifiedAttr?.pinyin_position ?? 'top';
-          }
+            // 初始化时,如果有 view_pinyin 则根据全教材设置配置设 view_pinyin 和 pinyin_position
+            if ('view_pinyin' in this.data.property) {
+              this.data.property.view_pinyin = bookUnifiedAttr?.view_pinyin ?? 'false';
+              this.data.property.pinyin_position = bookUnifiedAttr?.pinyin_position ?? 'top';
+            }
 
-          if ('frame_color' in this.data.property) {
-            // 框颜色初始化为主题色
-            this.data.property.frame_color = bookUnifiedAttr?.topic_color ?? '#F13232';
-          }
+            if ('frame_color' in this.data.property) {
+              // 框颜色初始化为主题色
+              this.data.property.frame_color = bookUnifiedAttr?.topic_color ?? '#F13232';
+            }
 
-          if ('sn_background_color' in this.data.property) {
-            // 序号背景色
-            this.data.property.sn_background_color = bookUnifiedAttr?.topic_color ?? '#EA3232';
+            if ('sn_background_color' in this.data.property) {
+              // 序号背景色
+              this.data.property.sn_background_color = bookUnifiedAttr?.topic_color ?? '#EA3232';
+            }
           }
-        }
 
-        this.property.isGetContent = true;
+          this.property.isGetContent = true;
 
-        this.$watch(
-          'data',
-          () => {
-            this.$emit('changeData');
-          },
-          { deep: true },
-        );
-      });
+          this.$watch(
+            'data',
+            () => {
+              this.$emit('changeData');
+            },
+            { deep: true },
+          );
+        })
+        .finally(() => {
+          this.$nextTick(() => {
+            this.property.isSilentPropertyWatch = false;
+          });
+        });
     },
     /**
      * @description 复制组件

+ 14 - 8
src/views/book/courseware/create/components/question/fill/Fill.vue

@@ -3,7 +3,7 @@
   <ModuleBase ref="base" :type="data.type">
     <template #content>
       <!-- eslint-disable max-len -->
-      <div class="fill-wrapper">
+      <div v-loading="loading" class="fill-wrapper">
         <RichText
           v-if="property.isGetContent"
           ref="richText"
@@ -14,7 +14,6 @@
           :font-size="data?.unified_attrib?.font_size"
           :font-family="data?.unified_attrib?.font"
           :font-color="data?.unified_attrib?.text_color"
-          @handleRichTextBlur="parsedContentPinyin"
         />
         <div v-if="data.property.fill_type === fillTypeList[1].value" class="select-vocabulary">
           <h5 class="title">选词列表:</h5>
@@ -142,6 +141,7 @@ export default {
       sanitizeHTML,
       visibleWord: false,
       rich_text_list: [],
+      loading: false,
     };
   },
   computed: {
@@ -159,6 +159,7 @@ export default {
     'data.property.fill_font': 'handleMindMap',
     'data.property': {
       handler(val) {
+        if (this.property.isSilentPropertyWatch) return;
         if (val.view_pinyin === 'true') {
           this.parsedContentPinyin();
         }
@@ -168,18 +169,23 @@ export default {
   },
   methods: {
     async parsedContentPinyin() {
+      this.loading = true;
       PinyinBuild_OldFormat({
         text: this.data.content,
         is_first_sentence_first_hz_pinyin_first_char_upper_case:
           this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case,
         is_fill_space: 'true',
         is_rich_text: 'true',
-      }).then(({ rich_text }) => {
-        if (rich_text) {
-          this.rich_text_list = rich_text.text_list;
-          this.data.model_essay = this.parseRichText();
-        }
-      });
+      })
+        .then(({ rich_text }) => {
+          if (rich_text) {
+            this.rich_text_list = rich_text.text_list;
+            this.data.model_essay = this.parseRichText();
+          }
+        })
+        .finally(() => {
+          this.loading = false;
+        });
     },
     // 解析富文本,构建 model_essay 结构
     parseRichText() {

+ 5 - 4
src/views/book/courseware/preview/components/common/PreviewMixin.js

@@ -99,8 +99,8 @@ const mixin = {
   },
   methods: {
     getCoursewareComponentContent() {
-      ContentGetCoursewareComponentContent({ courseware_id: this.coursewareId, component_id: this.id }).then(
-        ({ content }) => {
+      ContentGetCoursewareComponentContent({ courseware_id: this.coursewareId, component_id: this.id })
+        .then(({ content }) => {
           if (content) {
             let oldData = JSON.parse(content);
             if (oldData.type === 'audio') {
@@ -118,9 +118,10 @@ const mixin = {
             }
             this.data = oldData;
           }
+        })
+        .finally(() => {
           this.loader = true;
-        },
-      );
+        });
     },
 
     /**

+ 5 - 15
src/views/project_manage/org/book/index.vue

@@ -9,26 +9,16 @@
             {{ cur_page_begin_index + $index }}
           </template>
         </el-table-column>
-        <el-table-column prop="sn" label="编号" width="140" header-align="center" />
-        <el-table-column prop="name" label="名称" width="240" header-align="center" />
-        <el-table-column prop="project_sn" label="项目编号" width="140" header-align="center" />
-        <el-table-column prop="project_name" label="教材名称" width="240" header-align="center" />
-        <el-table-column prop="org_name" label="所属机构" width="120" header-align="center" />
-        <el-table-column prop="content_count" label="教材页面数" width="100" header-align="center" />
-        <el-table-column prop="shangjia_time" label="上架时间" width="180" header-align="center" />
-        <el-table-column prop="version" label="版本号" width="90" header-align="center" align="center" />
+        <el-table-column prop="sn" label="编号" width="150" header-align="center" />
+        <el-table-column prop="name" label="教材名称" width="480" header-align="center" />
+        <el-table-column prop="version" label="版本" width="120" header-align="center" align="center" />
+        <el-table-column prop="author" label="作者" width="180" header-align="center" />
+        <el-table-column prop="shangjia_date" label="上架日期" width="120" header-align="center" align="center" />
         <el-table-column prop="revise_count" label="修订版次" align="center" header-align="center">
           <template slot-scope="{ row }">
             {{ row.revise_count === 0 ? `首版` : `第${row.revise_count}次修订` }}
           </template>
         </el-table-column>
-        <el-table-column label="已申请下架" align="center" header-align="center">
-          <template slot-scope="{ row }">
-            <span style="color: #f53f3f">
-              {{ isTrue(row.is_request_xiajia) ? '√' : '' }}
-            </span>
-          </template>
-        </el-table-column>
 
         <el-table-column label="操作" fixed="right" width="260" align="center" header-align="center">
           <template slot-scope="{ row }">

+ 10 - 3
src/views/project_manage/org/project/index.vue

@@ -2,13 +2,13 @@
   <div class="project org">
     <ProjectMenu cur-key="org/project" />
 
-    <div id="query-form">
+    <div class="query-form">
       <el-form inline>
         <el-form-item prop="name" label="名称">
-          <el-input v-model="queryForm.name" clearable @change="pageQueryProjectList_OrgManager()" />
+          <el-input v-model="queryForm.name" clearable />
         </el-form-item>
         <el-form-item prop="status" label="状态">
-          <el-select v-model="queryForm.status" @change="pageQueryProjectList_OrgManager()">
+          <el-select v-model="queryForm.status">
             <el-option label="全部" :value="-1" />
             <el-option label="制作中" :value="0" />
             <el-option label="审核中" :value="1" />
@@ -17,6 +17,9 @@
             <el-option label="已下架" :value="4" />
           </el-select>
         </el-form-item>
+        <el-form-item class="search-box">
+          <el-button class="search-btn" type="primary" @click="pageQueryProjectList_OrgManager()">查询</el-button>
+        </el-form-item>
       </el-form>
     </div>
 
@@ -182,5 +185,9 @@ export default {
 .project {
   @include page-base;
   @include table-list;
+
+  .query-form {
+    padding-top: 12px;
+  }
 }
 </style>