Bladeren bron

Merge branch 'master' of http://gcls-git.helxsoft.cn/GCLS/eep_page

zq 2 weken geleden
bovenliggende
commit
4bdcf00f7f
33 gewijzigde bestanden met toevoegingen van 696 en 302 verwijderingen
  1. 1 1
      .env
  2. 4 7
      src/api/project.js
  3. 14 7
      src/components/ColorPicker.vue
  4. 24 20
      src/components/CommonPreview.vue
  5. 1 1
      src/components/PinyinText.vue
  6. 13 8
      src/components/RichText.vue
  7. 2 2
      src/views/book/courseware/create/components/FullTextSettings.vue
  8. 1 1
      src/views/book/courseware/create/components/PreviewEdit.vue
  9. 4 3
      src/views/book/courseware/create/components/SetBackground.vue
  10. 1 1
      src/views/book/courseware/create/components/SetComponentBackground.vue
  11. 8 7
      src/views/book/courseware/create/components/question/article/Article.vue
  12. 8 5
      src/views/book/courseware/create/components/question/dialogue_article/Article.vue
  13. 80 58
      src/views/book/courseware/create/components/question/fill/Fill.vue
  14. 3 4
      src/views/book/courseware/create/components/question/fill/FillSetting.vue
  15. 0 2
      src/views/book/courseware/create/components/question/record_input/RecordInputSetting.vue
  16. 29 0
      src/views/book/courseware/create/components/question/sort/Sort.vue
  17. 12 10
      src/views/book/courseware/create/components/question/table/Table.vue
  18. 3 9
      src/views/book/courseware/data/fill.js
  19. 1 0
      src/views/book/courseware/data/sort.js
  20. 17 89
      src/views/book/courseware/preview/CoursewarePreview.vue
  21. 77 0
      src/views/book/courseware/preview/common/utils/coursewareStyle.js
  22. 14 10
      src/views/book/courseware/preview/components/article/NotesModelChs.vue
  23. 125 38
      src/views/book/courseware/preview/components/fill/FillPreview.vue
  24. 4 3
      src/views/book/courseware/preview/components/judge/JudgePreview.vue
  25. 4 2
      src/views/book/courseware/preview/components/new_word/NewWordPreview.vue
  26. 1 1
      src/views/book/courseware/preview/components/new_word/components/writeTableZoom.vue
  27. 1 0
      src/views/book/courseware/preview/components/sort/SortPreview.vue
  28. 235 6
      src/views/book/courseware/preview/components/table/TablePreview.vue
  29. 1 1
      src/views/personal_workbench/edit_task/edit/UseTemplate.vue
  30. 0 1
      src/views/personal_workbench/edit_task/preview/index.vue
  31. 7 2
      src/views/personal_workbench/project/components/BookUnifiedAttr.vue
  32. 1 1
      src/views/personal_workbench/template_list/preview/CommonPreview.vue
  33. 0 2
      src/views/project_manage/org/project/index.vue

+ 1 - 1
.env

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

+ 4 - 7
src/api/project.js

@@ -378,19 +378,16 @@ export function DeleteProjectInvitePerson(data) {
  * @description 得到背景色列表(常用)
  * @param {object} data
  */
-export function GetBackgroundColorList_Common(data) {
-  return http.post(
-    `${process.env.VUE_APP_EepServer}?MethodName=project_resource_manager-GetBackgroundColorList_Common`,
-    data,
-  );
+export function GetColorList_Common(data) {
+  return http.post(`${process.env.VUE_APP_EepServer}?MethodName=project_resource_manager-GetColorList_Common`, data);
 }
 /**
  * @description 得到背景色列表(最近使用)
  * @param {object} data
  */
-export function GetBackgroundColorList_RecentlyUsed(data) {
+export function GetColorList_RecentlyUsed(data) {
   return http.post(
-    `${process.env.VUE_APP_EepServer}?MethodName=project_resource_manager-GetBackgroundColorList_RecentlyUsed`,
+    `${process.env.VUE_APP_EepServer}?MethodName=project_resource_manager-GetColorList_RecentlyUsed`,
     data,
   );
 }

+ 14 - 7
src/components/ColorPicker.vue

@@ -8,11 +8,18 @@
 </template>
 
 <script>
-import { GetBackgroundColorList_Common, GetBackgroundColorList_RecentlyUsed } from '@/api/project';
+import { GetColorList_Common, GetColorList_RecentlyUsed } from '@/api/project';
 
 export default {
   name: 'ColorPicker',
   inheritAttrs: false,
+  props: {
+    // 常用颜色类型
+    type: {
+      type: Number,
+      default: 0,
+    },
+  },
   data() {
     return {
       commonList: [], // 常用颜色
@@ -28,17 +35,17 @@ export default {
     },
   },
   created() {
-    this.getBackgroundColorList_Common();
-    this.getBackgroundColorList_RecentlyUsed();
+    this.getColorList_Common();
+    this.getColorList_RecentlyUsed();
   },
   methods: {
-    getBackgroundColorList_Common() {
-      GetBackgroundColorList_Common().then(({ color_list }) => {
+    getColorList_Common() {
+      GetColorList_Common({ type: this.type }).then(({ color_list }) => {
         this.commonList = color_list;
       });
     },
-    getBackgroundColorList_RecentlyUsed() {
-      GetBackgroundColorList_RecentlyUsed().then(({ color_list }) => {
+    getColorList_RecentlyUsed() {
+      GetColorList_RecentlyUsed({ type: this.type }).then(({ color_list }) => {
         this.recentlyUsedList = color_list;
       });
     },

+ 24 - 20
src/components/CommonPreview.vue

@@ -4,7 +4,8 @@
       <div class="menu-container">
         {{ courseware_info.book_name }}
       </div>
-      <div class="courseware">
+
+      <div class="courseware-top">
         <span class="name-path">{{ courseware_info.name_path }}</span>
         <span class="flow-nodename">{{ courseware_info.cur_audit_flow_node_name }}</span>
         <slot name="middle" :courseware="courseware_info"></slot>
@@ -111,11 +112,11 @@
           <SvgIcon icon-class="catalogue" size="54" />
         </div>
 
-        <main :class="['preview-main', { 'no-audit': !isShowAudit }]" :style="computedCommonPreviewStyle()">
+        <main :class="['preview-main', { 'no-audit': !isShowAudit }]" :style="computedCommonPreviewStyle">
           <div class="preview-left" :style="{ backgroundColor: background.background?.is_global ? '' : '#fff' }"></div>
           <CoursewarePreview
             v-if="courseware_info.book_name"
-            ref="courserware"
+            ref="courseware"
             :is-show-group="isShowGroup"
             :group-show-all="groupShowAll"
             :group-row-list="content_group_row_list"
@@ -516,6 +517,7 @@ import {
 import { toggleFullScreen } from '@/utils/common';
 import * as OpenCC from 'opencc-js';
 import { isTrue } from '@/utils/validate';
+import { buildCoursewareStyle } from '@/views/book/courseware/preview/common/utils/coursewareStyle';
 import { CreateCoursewarePreviewURL } from '@/api/app';
 
 export default {
@@ -742,6 +744,9 @@ export default {
       if (this.dialogType === 2) return '反馈';
       return '笔记';
     },
+    computedCommonPreviewStyle() {
+      return buildCoursewareStyle(this.background, 'commonPreview');
+    },
   },
   watch: {
     isJudgeCorrect(newVal) {
@@ -1020,7 +1025,7 @@ export default {
           this.submit_loading = false;
           this.visibleRemark = false;
           this.getCoursewareAuditRemarkList(id || this.id);
-          this.$refs.courserware.resetRemark();
+          this.$refs.courseware.resetRemark();
         })
         .catch(() => {
           this.submit_loading = false;
@@ -1029,7 +1034,7 @@ export default {
     // 关闭审核批注弹窗
     closeVisibleRemark() {
       this.visibleRemark = false;
-      this.$refs.courserware.resetRemark();
+      this.$refs.courseware.resetRemark();
     },
     // 删除批注
     deleteRemarks(id) {
@@ -1049,7 +1054,7 @@ export default {
     // 定位批注
     async handleLocationRemarks(offsetTop) {
       // if (componentId) {
-      //   let node = await this.$refs.courserware.findChildComponentByKey(componentId);
+      //   let node = await this.$refs.courseware.findChildComponentByKey(componentId);
       //   if (node) {
       await this.$nextTick();
       this.$refs.previewMain.scrollTo({
@@ -1062,7 +1067,7 @@ export default {
     },
     // 计算previewMain滑动距离
     computeScroll() {
-      this.$refs.courserware.handleResult(
+      this.$refs.courseware.handleResult(
         this.$refs.previewMain.scrollTop,
         this.$refs.previewMain.scrollLeft,
         this.select_node,
@@ -1105,7 +1110,7 @@ export default {
       let [nodeId, componentId] = data.split('#');
       if (nodeId) this.selectNode(nodeId);
       if (componentId) {
-        let node = await this.$refs.courserware.findChildComponentByKey(componentId);
+        let node = await this.$refs.courseware.findChildComponentByKey(componentId);
         if (node) {
           await this.$nextTick();
           this.$refs.previewMain.scrollTo({
@@ -1243,7 +1248,7 @@ export default {
     async handleFileClick(courseware_id, component_id) {
       if (courseware_id) this.selectNode(courseware_id);
       if (component_id) {
-        let node = await this.$refs.courserware.findChildComponentByKey(component_id);
+        let node = await this.$refs.courseware.findChildComponentByKey(component_id);
         if (node) {
           await this.$nextTick();
           this.$refs.previewMain.scrollTo({
@@ -1274,7 +1279,9 @@ export default {
     },
 
     /**
-     * 使用OpenCC解析HTML并仅转换文本节点,保留属性(包括内联样式/font-family)保持不变。防止字体名称像“微软雅黑' 正在转换为'微軟雅黑'.
+     * 使用OpenCC解析HTML并仅转换文本节点,保留属性(包括内联样式/font-family)保持不变。防止字体名称如:“微软雅黑' 转换为 '微軟雅黑'
+     * @param {string} html - 要转换的HTML字符串
+     * @returns {string} - 转换后的HTML字符串
      */
     convertHtmlPreserveAttributes(html) {
       try {
@@ -1308,7 +1315,7 @@ export default {
     },
 
     simulateAnswer(disabled = true) {
-      this.$refs.courserware.simulateAnswer(this.isJudgeCorrect, this.isShowAnswer, disabled);
+      this.$refs.courseware.simulateAnswer(this.isJudgeCorrect, this.isShowAnswer, disabled);
     },
 
     /**
@@ -1323,7 +1330,7 @@ export default {
       this.selectNode(nodeId);
       this.isShowGroup = false;
       this.groupShowAll = true;
-      this.$refs.courserware.clearRowCheckList();
+      this.$refs.courseware.clearRowCheckList();
     },
     /**
      * 计算章节名称样式
@@ -1401,9 +1408,9 @@ export default {
         return;
       }
 
-      if (this.$refs.courserware && this.$refs.courserware.handleLocation) {
+      if (this.$refs.courseware && this.$refs.courseware.handleLocation) {
         item.type = type;
-        this.$refs.courserware.handleLocation(item);
+        this.$refs.courseware.handleLocation(item);
       }
     },
 
@@ -1752,10 +1759,10 @@ export default {
      * @returns {object} 选中分组行的课件信息
      */
     computedSelectedGroupCoursewareInfo() {
-      return this.$refs.courserware.computedSelectedGroupCoursewareInfo();
+      return this.$refs.courseware.computedSelectedGroupCoursewareInfo();
     },
     saveCoursewareStyleTemplate(data) {
-      return this.$refs.courserware.saveCoursewareStyleTemplate(data);
+      return this.$refs.courseware.saveCoursewareStyleTemplate(data);
     },
 
     async submitChapterAllCoursewareToAuditFlow(chapter_id) {
@@ -1790,9 +1797,6 @@ export default {
         this.$set(component, 'background', background);
       }
     },
-    computedCommonPreviewStyle() {
-      return this.$refs.courserware?.computedCourserwareStyle('commonPreview');
-    },
     /**
      * 获取 main-container 视口内第一个可见的组件 id
      * @returns {string} 第一个可见组件 id;不存在时返回空字符串
@@ -1852,7 +1856,7 @@ $total-width: $courseware-width + $courseware-left-margin + $courseware-right-ma
       border-right: $border;
     }
 
-    > .courseware {
+    > .courseware-top {
       display: flex;
       flex-grow: 1;
       column-gap: 16px;

+ 1 - 1
src/components/PinyinText.vue

@@ -616,7 +616,7 @@ export default {
     },
 
     // 获取单个字符的样式(包括着重点)
-    getCharStyle(word, block, charIndex) {
+    getCharStyle(word, block) {
       const baseStyle = { ...word.activeTextStyle };
       baseStyle['font-size'] = baseStyle.fontSize;
       baseStyle['font-family'] = baseStyle.fontFamily;

+ 13 - 8
src/components/RichText.vue

@@ -1325,6 +1325,11 @@ export default {
       }
     },
 
+    /**
+     * 获取富文本内容中第一个字符的样式信息,返回一个包含字体、字号、颜色、加粗、下划线、删除线等样式属性的对象
+     * 该函数通过查找编辑器内容中的第一个文本节点,并逐级向上遍历其父元素,获取计算样式来确定最终的样式属性值
+     * @returns {object} 包含字体、字号、颜色、加粗、下划线、删除线等样式属性的对象
+     */
     getFirstCharStyles() {
       const editor = tinymce.activeEditor;
       if (!editor) return {};
@@ -1383,15 +1388,15 @@ export default {
       return styles;
     },
 
+    /**
+     * 在编辑器内容中查找第一个文本节点的函数
+     * @param {HTMLElement} element - 要搜索的根元素,通常是编辑器的内容区域
+     * @returns {Text|null} - 返回找到的第一个文本节点,如果没有找到则返回null
+     */
     findFirstTextNode(element) {
-      const walker = document.createTreeWalker(
-        element,
-        NodeFilter.SHOW_TEXT,
-        {
-          acceptNode: (node) => (node.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT),
-        },
-        false,
-      );
+      const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, {
+        acceptNode: (node) => (node.textContent.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT),
+      });
       return walker.nextNode();
     },
   },

+ 2 - 2
src/views/book/courseware/create/components/FullTextSettings.vue

@@ -10,7 +10,7 @@
     <el-form ref="form" :model="unified_attrib" label-width="80px" size="small">
       <el-form-item label="主题色">
         <div class="color-group">
-          <ColorPicker v-model="unified_attrib.topic_color" />
+          <ColorPicker v-model="unified_attrib.topic_color" :type="2" />
           <span>辅助色</span>
           <el-color-picker v-model="unified_attrib.assist_color" />
           <span class="link" @click="generateAssistColor">自动生成辅助色</span>
@@ -79,7 +79,7 @@
         </el-button>
       </el-form-item>
       <el-form-item label="文字颜色">
-        <ColorPicker v-model="unified_attrib.text_color" />
+        <ColorPicker v-model="unified_attrib.text_color" :type="1" />
         <el-button type="primary" class="row-button" @click="applySingleAttrToSelectedComponents('text_color')">
           应用选中组件
         </el-button>

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

@@ -128,7 +128,7 @@ export default {
   },
   mounted() {
     const element = this.$refs.previewRoot;
-    // 监听 courserware 高度变化,获取其高度
+    // 监听 courseware 高度变化,获取其高度
     this.resizeObserver = new ResizeObserver(() => {
       const rect = element.getBoundingClientRect();
       this.heightPrompt = rect.height > 1620;

+ 4 - 3
src/views/book/courseware/create/components/SetBackground.vue

@@ -96,7 +96,7 @@
             <el-checkbox v-model="background.has_color">颜色</el-checkbox>
           </div>
           <div class="setup-content">
-            <ColorPicker v-model="background.color" show-alpha />
+            <ColorPicker v-model="background.color" show-alpha :type="0" />
           </div>
         </div>
         <div class="setup-item">
@@ -278,7 +278,8 @@ export default {
         const isGlobal = this.backgroundData.is_global;
         const courseware = isGlobal
           ? document.querySelector('main.preview-main')
-          : document.querySelector('div.courserware');
+          : document.querySelector('div.courseware.preview');
+
         if (courseware) {
           const rect = courseware.getBoundingClientRect();
           const coursewareRatio = rect.width / rect.height;
@@ -316,7 +317,7 @@ export default {
     'background.is_global'(newVal) {
       const courseware = newVal
         ? document.querySelector('main.preview-main')
-        : document.querySelector('div.courserware');
+        : document.querySelector('div.courseware');
       if (courseware) {
         const rect = courseware.getBoundingClientRect();
         const coursewareRatio = rect.width / rect.height;

+ 1 - 1
src/views/book/courseware/create/components/SetComponentBackground.vue

@@ -87,7 +87,7 @@
             <el-radio v-model="background.mode" label="color">颜色</el-radio>
           </div>
           <div class="setup-content">
-            <ColorPicker v-model="background.color" show-alpha />
+            <ColorPicker v-model="background.color" show-alpha :type="0" />
           </div>
         </div>
         <div class="setup-item">

+ 8 - 7
src/views/book/courseware/create/components/question/article/Article.vue

@@ -125,7 +125,7 @@
               :key="windex + '-' + wIndex"
               class="text-item"
               :style="{
-                textAlign: windex === 0 && wIndex === 0 ? 'left' : '',
+                textAlign: wIndex === 0 ? 'left' : '',
               }"
             >
               <span
@@ -134,11 +134,9 @@
                   fontSize: articleAttrib.pinyin_size,
                 }"
                 v-if="isEnable(data.property.view_pinyin)"
-                @click="selectItem(wItem)"
+                @click="selectItem(wItem, wIndex)"
                 >{{
-                  windex === 0 &&
-                  wIndex === 0 &&
-                  data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case === 'true'
+                  wIndex === 0 && data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case === 'true'
                     ? wItem.pinyin_up
                     : wItem.pinyin
                 }}</span
@@ -177,7 +175,7 @@
           <div class="content">
             <div class="words-box">
               <span class="pinyin">
-                {{ itemActive.pinyin }}
+                {{ itemActiveIndex === 0 ? itemActive.pinyin_up : itemActive.pinyin }}
               </span>
               <span class="words">
                 {{ itemActive.chs }}
@@ -396,6 +394,7 @@ export default {
       visible: false,
       subtitleList: [],
       itemActive: null,
+      itemActiveIndex: null,
       dialogFlag: false,
       checkPinyinInput: '',
       oldInput: '',
@@ -1122,8 +1121,9 @@ export default {
         });
       }
     },
-    selectItem(item) {
+    selectItem(item, index) {
       this.itemActive = item;
+      this.itemActiveIndex = index;
       this.pinyinList = [];
       toolGetWordPinyinCorrectionList({
         word: item.chs,
@@ -1193,6 +1193,7 @@ export default {
             this.checkPinyinInput = str.trim();
 
             this.itemActive.pinyin = this.checkPinyinInput.replace(/\s+/g, '');
+            this.itemActive.pinyin_up = this.checkPinyinInput.replace(/\s+/g, '');
             this.$message.success('保存成功');
             // this.itemActive = null;
             this.checkPinyinInput = '';

+ 8 - 5
src/views/book/courseware/create/components/question/dialogue_article/Article.vue

@@ -135,7 +135,7 @@
                       :key="windex + '-' + wIndex"
                       class="text-item"
                       :style="{
-                        textAlign: windex === 0 && wIndex === 0 ? 'left' : '',
+                        textAlign: wIndex === 0 ? 'left' : '',
                       }"
                     >
                       <span
@@ -144,9 +144,8 @@
                         :style="{
                           fontSize: articleAttrib.pinyin_size,
                         }"
-                        @click="selectItem(wItem)"
+                        @click="selectItem(wItem, wIndex)"
                         >{{
-                          windex === 0 &&
                           wIndex === 0 &&
                           data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case === 'true'
                             ? wItem.pinyin_up
@@ -289,7 +288,7 @@
           <div class="contents">
             <div class="words-box">
               <span class="pinyin">
-                {{ itemActive.pinyin }}
+                {{ itemActiveIndex === 0 ? itemActive.pinyin_up : itemActive.pinyin }}
               </span>
               <span class="words">
                 {{ itemActive.chs }}
@@ -592,6 +591,8 @@ export default {
       visible: false,
       subtitleList: [],
       itemActive: null,
+      itemActiveIndex: null,
+
       dialogFlag: false,
       checkPinyinInput: '',
       oldInput: '',
@@ -1573,8 +1574,9 @@ export default {
         });
       }
     },
-    selectItem(item) {
+    selectItem(item, index) {
       this.itemActive = item;
+      this.itemActiveIndex = index;
       this.pinyinList = [];
       toolGetWordPinyinCorrectionList({
         word: item.chs,
@@ -1644,6 +1646,7 @@ export default {
             this.checkPinyinInput = str.trim();
 
             this.itemActive.pinyin = this.checkPinyinInput.replace(/\s+/g, '');
+            this.itemActive.pinyin_up = this.checkPinyinInput.replace(/\s+/g, '');
             this.$message.success('保存成功');
             // this.itemActive = null;
             this.checkPinyinInput = '';

+ 80 - 58
src/views/book/courseware/create/components/question/fill/Fill.vue

@@ -9,17 +9,23 @@
           ref="richText"
           v-model="data.content"
           :is-fill="true"
-          toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright"
+          toolbar="fontselect fontsizeselect forecolor backcolor | lineheight paragraphSpacing underline | bold italic strikethrough alignleft aligncenter alignright bullist numlist dotEmphasis"
           :wordlimit-num="false"
           :font-size="data?.unified_attrib?.font_size"
           :font-family="data?.unified_attrib?.font"
           :font-color="data?.unified_attrib?.text_color"
+          @handleRichTextBlur="identifyText"
         />
         <div v-if="data.property.fill_type === fillTypeList[1].value" class="select-vocabulary">
           <h5 class="title">选词列表:</h5>
           <el-button size="mini" @click="openAddWord">添加词汇</el-button>
           <ul class="word-list">
-            <li v-for="item in data.word_list" :key="item.mark" v-html="sanitizeHTML(item.content)"></li>
+            <li v-for="(item, index) in data.word_list" :key="item.mark" class="word-item">
+              <span v-html="sanitizeHTML(item.content)"></span>
+              <el-button type="text" size="mini" class="delete-word" @click="removeWord(index)">
+                <SvgIcon icon-class="delete-black" size="12" />
+              </el-button>
+            </li>
           </ul>
         </div>
 
@@ -69,7 +75,6 @@
         <div v-for="(item, i) in data.model_essay" :key="i" class="pinyin-text-list">
           <template v-for="(li, j) in item">
             <PinyinText
-              v-if="li.type === 'text'"
               :key="`${i}-${j}`"
               ref="PinyinText"
               :paragraph-list="li.paragraph_list"
@@ -107,7 +112,7 @@ import UploadAudio from '@/views/book/courseware/create/components/question/fill
 import PinyinText from '@/components/PinyinText.vue';
 import AddWord from './components/AddWord.vue';
 
-import { getFillData, arrangeTypeList, fillFontList, fillTypeList } from '@/views/book/courseware/data/fill';
+import { getFillData, arrangeTypeList, fillTypeList } from '@/views/book/courseware/data/fill';
 import { addTone, handleToneValue } from '@/views/book/courseware/data/common';
 import { getRandomNumber } from '@/utils';
 import { TextToAudioFile } from '@/api/app';
@@ -143,21 +148,33 @@ export default {
   methods: {
     // 识别文本
     identifyText() {
-      this.data.model_essay = [];
       this.data.answer.answer_list = [];
+      const content = this.data.content || '';
+
+      // 使用 class 为 rich-fill 的 span 以及连续 3 个及以上下划线作为分割符
+      if (!content || !content.match(/<span[^>]*class="[^"]*rich-fill[^"]*"[^>]*>(.*?)<\/span>|_{3,}/gi)) {
+        this.data.model_essay = [
+          [
+            {
+              content,
+              type: 'text',
+              paragraph_list: [],
+              paragraph_list_parameter: { text: '', pinyin_proofread_word_list: [] },
+            },
+          ],
+        ];
+        return;
+      }
+
+      const splitSource = content.split('\n').map((item) => {
+        // rich-fill 和 ___ 均转为统一占位符,交给 splitRichText 处理
+        return this.splitRichText(
+          item.replace(/<span[^>]*class="[^"]*rich-fill[^"]*"[^>]*>.*?<\/span>|_{3,}/gi, '###$&###'),
+        );
+      });
+
+      this.data.model_essay = splitSource;
 
-      this.data.content
-        .split(/<(p|div)[^>]*>(.*?)<\/(p|div)>/g)
-        .filter((s) => s && !s.match(/^(p|div)$/))
-        .forEach((item) => {
-          if (item.charCodeAt() === 10) return;
-          let str = item
-            // 去除所有的 font-size 样式
-            .replace(/font-size:\s*\d+(\.\d+)?px;/gi, '')
-            // 匹配 class 名为 rich-fill 的 span 标签和三个以上的_,并将它们组成数组
-            .replace(/<span class="rich-fill".*?>(.*?)<\/span>|([_]{3,})/gi, '###$1$2###');
-          this.data.model_essay.push(this.splitRichText(str));
-        });
       this.handleViewPinyin();
     },
     /**
@@ -194,25 +211,32 @@ export default {
           continue;
         }
 
-        // 奇数索引为输入段(被 ### 包裹的内容)
-        const isUnderline = /^_{3,}$/.test(content);
+        // 奇数索引为输入段(被 ### 包裹的分割符)
+        const separatorContent = content;
+        const isUnderline = /^_{3,}$/.test(separatorContent);
+        const richFillMatch = separatorContent.match(/^<span[^>]*class="[^"]*rich-fill[^"]*"[^>]*>(.*?)<\/span>$/i);
+        const answerValue = isUnderline ? '' : richFillMatch ? richFillMatch[1] : separatorContent;
         const mark = getRandomNumber();
+
         arr.push({
-          content: isUnderline ? '' : content,
+          content: separatorContent,
           type: 'input',
+          input: '',
           audio_answer_list: [],
           mark,
+          paragraph_list: [],
+          paragraph_list_parameter: {
+            text: '',
+            pinyin_proofread_word_list: [],
+          },
         });
 
         // 同步更新答案列表
         this.data.answer.answer_list.push({
-          value: isUnderline ? '' : content,
+          value: answerValue,
           mark,
           type: isUnderline ? 'any_one' : 'only_one',
         });
-
-        // 将 content 设置为空,为预览准备
-        arr[arr.length - 1].content = '';
       }
       return arr;
     },
@@ -222,18 +246,17 @@ export default {
      * @param {Number} index 当前索引
      * @param {Array} parts 分割后的数组
      * @param {String} content 当前内容
-     * @returns {String} 包含前一个标签的内容
+     * @returns {String} 包含两个标签内容中最后一个html标签的内容
      */
     setTag(index, parts, content) {
+      if (index < 2) return content;
       let _content = content;
-      const isEndWithTag = /<\/[^>]+>$/.test(_content);
+      const isEndWithTag = /<\/[^>]+>$/.test(_content); // 判断是否以标签结尾
       let startTag = '';
-      for (let j = index - 1; j >= 0; j--) {
-        const prev = parts[j] ?? '';
-        const m = prev.match(/^<[^>]+>/);
-        if (m) {
-          startTag = m[0];
-        }
+      const part = parts[index - 2] ?? '';
+      const tagMatch = part.match(/<[^>]+>/g);
+      if (tagMatch) {
+        startTag = tagMatch[tagMatch.length - 1]; // 获取最后一个标签
       }
       _content = `${startTag}${_content}`;
       if (!isEndWithTag) {
@@ -282,43 +305,24 @@ export default {
      * @description 处理思维导图数据
      */
     handleMindMap() {
-      const { fill_font, arrange_type } = this.data.property;
-      const fontLabel = fillFontList.find((item) => item.value === fill_font)?.label || '';
+      const { arrange_type } = this.data.property;
       const arrangeLabel = arrangeTypeList.find((item) => item.value === arrange_type)?.label || '';
       this.data.mind_map.node_list = [
         {
-          name: `${arrangeLabel}${fontLabel}填空组件`,
+          name: `${arrangeLabel}填空组件`,
         },
       ];
     },
     handleViewPinyin() {
       if (!this.isEnable(this.data.property.view_pinyin)) {
-        this.data.model_essay.forEach((item) => {
-          item.forEach((option) => {
-            option.paragraph_list = [];
-            option.paragraph_list_parameter = {
-              text: '',
-              pinyin_proofread_word_list: [],
-            };
-          });
-        });
-        return;
-      }
-
-      if (
-        this.data.model_essay.length > 0 &&
-        this.data.model_essay[0].length > 0 &&
-        this.data.model_essay[0][0].paragraph_list.length > 0
-      ) {
         return;
       }
 
       this.data.model_essay.forEach((item, i) => {
         item.forEach((option, j) => {
           const text = option.content;
-
-          if (!text) return;
           option.paragraph_list_parameter.text = text;
+
           this.createParsedTextInfoPinyin(text, i, j, 'model_essay');
         });
       });
@@ -337,6 +341,9 @@ export default {
         mark: getRandomNumber(),
       });
     },
+    removeWord(index) {
+      this.data.word_list.splice(index, 1);
+    },
   },
 };
 </script>
@@ -363,7 +370,10 @@ export default {
       gap: 8px;
       margin-top: 8px;
 
-      li {
+      .word-item {
+        display: flex;
+        gap: 6px;
+        align-items: center;
         padding: 4px 6px;
         border: $border;
         border-radius: 4px;
@@ -371,6 +381,13 @@ export default {
         :deep p {
           margin: 0;
         }
+
+        .delete-word {
+          display: flex;
+          align-items: center;
+          padding: 0;
+          line-height: 1;
+        }
       }
     }
   }
@@ -442,8 +459,13 @@ export default {
 }
 
 .pinyin-text-list {
-  display: flex;
-  flex-wrap: wrap;
-  align-items: center;
+  :deep .pinyin-area {
+    display: inline;
+  }
+
+  :deep .pinyin-area .rich-text-container,
+  :deep .pinyin-area .pinyin-paragraph {
+    display: inline;
+  }
 }
 </style>

+ 3 - 4
src/views/book/courseware/create/components/question/fill/FillSetting.vue

@@ -55,7 +55,7 @@
       </template>
       <el-form-item label="填空字体">
         <el-select v-model="property.fill_font" placeholder="请选择">
-          <el-option v-for="{ value, label } in fillFontList" :key="value" :label="label" :value="value" />
+          <el-option v-for="{ value, label } in fontList" :key="value" :label="label" :value="value" />
         </el-select>
       </el-form-item>
       <el-form-item label="横线长度">
@@ -109,13 +109,12 @@ import {
   arrangeTypeList,
   audioPositionList,
   audioGenerationMethodList,
-  fillFontList,
   switchOption,
   fillTypeList,
 } from '@/views/book/courseware/data/fill';
 
 import { GetTextToAudioConfParamList } from '@/api/app';
-import { speedRatioList } from '@/views/book/courseware/data/common';
+import { speedRatioList, fontList } from '@/views/book/courseware/data/common';
 
 export default {
   name: 'FillSetting',
@@ -126,7 +125,7 @@ export default {
       arrangeTypeList,
       audioPositionList,
       audioGenerationMethodList,
-      fillFontList,
+      fontList,
       switchOption,
       fillTypeList,
       voice_type_list: [],

+ 0 - 2
src/views/book/courseware/create/components/question/record_input/RecordInputSetting.vue

@@ -16,7 +16,6 @@ import {
   arrangeTypeList,
   audioPositionList,
   audioGenerationMethodList,
-  fillFontList,
   switchOption,
 } from '@/views/book/courseware/data/recordInput';
 
@@ -29,7 +28,6 @@ export default {
       arrangeTypeList,
       audioPositionList,
       audioGenerationMethodList,
-      fillFontList,
       switchOption,
     };
   },

+ 29 - 0
src/views/book/courseware/create/components/question/sort/Sort.vue

@@ -1,6 +1,15 @@
 <template>
   <ModuleBase ref="base" :type="data.type">
     <template #content>
+      <el-input
+        v-model="data.content"
+        type="textarea"
+        placeholder="你 喜欢 喝 茶 还是 咖啡 ?"
+        :autosize="{ minRows: 2, maxRows: 6 }"
+        resize="none"
+        @change="handleContentChange"
+      />
+
       <div class="sort-wrapper" :style="getSortWrapperStyle()">
         <template v-for="(item, i) in data.option_list">
           <div
@@ -181,6 +190,25 @@ export default {
       item.custom_serial_number = item.custom_serial_number.replace(/[^0-9/]/g, '');
     },
     /**
+     * @description 处理内容输入,自动根据空格分隔生成选项内容
+     */
+    handleContentChange() {
+      const words = (this.data.content || '').trim().split(/\s+/).filter(Boolean); // 根据空格分隔输入内容,并过滤掉空字符串
+      const targetCount = Math.max(this.data.option_list.length, words.length);
+
+      while (this.data.option_list.length < targetCount) {
+        this.data.option_list.push(getOption());
+      }
+
+      this.data.option_list.forEach((item, i) => {
+        item.content = words[i] || '';
+      });
+
+      if (this.data.property.option_count !== targetCount) {
+        this.data.property.option_count = targetCount;
+      }
+    },
+    /**
      * @description 处理思维导图数据
      */
     handleMindMap() {
@@ -210,6 +238,7 @@ export default {
 .sort-wrapper {
   display: grid;
   gap: 12px;
+  margin-top: 16px;
 
   .sort-rich {
     position: relative;

+ 12 - 10
src/views/book/courseware/create/components/question/table/Table.vue

@@ -117,7 +117,7 @@
               :font-family="data?.unified_attrib?.font"
               :font-color="data?.unified_attrib?.text_color"
               :inline="true"
-              :itemIndex="i + '#' + j"
+              :item-index="i + '#' + j"
               toolbar="fontselect fontsizeselect forecolor backcolor | underline | bold italic strikethrough alignleft aligncenter alignright image media link"
               @handleRichTextBlur="handleBlurCon"
             />
@@ -156,13 +156,14 @@
                 v-if="data.option_list[i][j].cell.isCross"
                 style="display: flex; align-items: center; font-size: 14px"
                 >勾叉答案<i
-                  @click="toggle(li)"
                   :class="[
                     { 'el-icon-check': li.crossAnswer === statusList[1] },
                     { 'el-icon-close': li.crossAnswer === statusList[2] },
                   ]"
                   style="display: block; width: 14px; height: 14px; border: 1px solid #000"
-              /></span>
+                  @click="toggle(li)"
+                ></i
+              ></span>
             </div>
           </div>
         </div>
@@ -181,8 +182,9 @@
         :translations="data.multilingual"
         @SubmitTranslation="handleMultilingualTranslation"
       />
-      <el-divider v-if="isEnable(data.property.view_pinyin)" content-position="left"
-        >拼音效果<el-button
+      <el-divider v-if="isEnable(data.property.view_pinyin)" content-position="left">
+        拼音效果
+        <el-button
           v-show="isEnable(data.property.view_pinyin)"
           type="text"
           icon="el-icon-refresh"
@@ -194,8 +196,8 @@
         <template v-for="(item, index) in data.option_list">
           <PinyinText
             v-for="(items, indexs) in item"
-            :key="index + '_' + indexs"
             :id="'table_pinyin_text_' + index + '_' + indexs"
+            :key="index + '_' + indexs"
             ref="PinyinText"
             :rich-text-list="items.rich_text_list"
             :pinyin-position="data.property.pinyin_position"
@@ -207,11 +209,11 @@
         </template>
       </template>
       <el-dialog
+        v-if="editCellFlag"
         title="配置单元格"
         :visible="editCellFlag"
         width="460px"
         :close-on-click-modal="false"
-        v-if="editCellFlag"
         @close="editCellFlag = false"
       >
         <el-form :model="activeCell" :inline="true">
@@ -340,7 +342,7 @@ export default {
             this.data.property.is_first_sentence_first_hz_pinyin_first_char_upper_case;
           this.data.option_list.forEach((item, index) => {
             item.forEach((items, indexs) => {
-              this.createParsedTextInfoPinyin(items.content, index + '#' + indexs);
+              this.createParsedTextInfoPinyin(items.content, `${index}#${indexs}`);
             });
           });
         }
@@ -365,7 +367,7 @@ export default {
           this.data.paragraph_list_parameter.is_first_sentence_first_hz_pinyin_first_char_upper_case = val;
           this.data.option_list.forEach((item, index) => {
             item.forEach((items, indexs) => {
-              this.createParsedTextInfoPinyin(items.content, index + '#' + indexs);
+              this.createParsedTextInfoPinyin(items.content, `${index}#${indexs}`);
             });
           });
         }
@@ -468,7 +470,7 @@ export default {
         } else {
           this.data.option_list.forEach((item, index) => {
             item.forEach((items, indexs) => {
-              this.createParsedTextInfoPinyin(items.content, index + '#' + indexs);
+              this.createParsedTextInfoPinyin(items.content, `${index}#${indexs}`);
             });
           });
         }

+ 3 - 9
src/views/book/courseware/data/fill.js

@@ -6,6 +6,7 @@ import {
   switchOption,
   pinyinPositionList,
   serialNumberStyleList,
+  fontList,
 } from '@/views/book/courseware/data/common';
 
 export { arrangeTypeList, switchOption };
@@ -40,13 +41,6 @@ export const fillTypeList = [
   { value: 'voice', label: '语音' },
 ];
 
-// 填空字体
-export const fillFontList = [
-  { value: 'chinese', label: '中文', font: 'arial' },
-  { value: 'english', label: '英文', font: 'arial' },
-  { value: 'pinyin', label: '拼音', font: 'PINYIN-B' },
-];
-
 export function getFillProperty() {
   return {
     serial_number: 1,
@@ -59,7 +53,7 @@ export function getFillProperty() {
     arrange_type: arrangeTypeList[1].value,
     audio_position: audioPositionList[0].value,
     audio_generation_method: audioGenerationMethodList[0].value,
-    fill_font: fillFontList[0].value,
+    fill_font: fontList[0].value,
     is_enable_voice_answer: switchOption[1].value,
     view_pinyin: 'false', // 显示拼音
     pinyin_position: pinyinPositionList[0].value,
@@ -67,7 +61,7 @@ export function getFillProperty() {
     voice_type: '', // 音色
     emotion: '', // 风格,情感
     speed_ratio: '', // 语速
-    input_default_width: 80, // 输入框默认宽度
+    input_default_width: 160, // 输入框默认宽度
   };
 }
 

+ 1 - 0
src/views/book/courseware/data/sort.js

@@ -57,6 +57,7 @@ export function getSortData() {
     mind_map: {
       node_list: [{ name: '3选项横排设定显示排序组件' }],
     },
+    content: '',
     answer_list: [], // 答案列表
     analysis_list: [], // 解析列表
   };

+ 17 - 89
src/views/book/courseware/preview/CoursewarePreview.vue

@@ -1,9 +1,9 @@
 <template>
   <div
     id="selectable-area-preview"
-    ref="courserware"
-    class="courserware"
-    :style="computedCourserwareStyle('courseware')"
+    ref="courseware"
+    class="courseware preview"
+    :style="computedCoursewareStyle"
     @mouseup="handleTextSelection"
     @mousedown="startSelection"
     @mousemove="updateSelection"
@@ -174,6 +174,7 @@
 <script>
 import { previewComponentList } from '@/views/book/courseware/data/bookType';
 import { getToken, getConfig } from '@/utils/auth';
+import { buildCoursewareStyle } from '@/views/book/courseware/preview/common/utils/coursewareStyle';
 import _ from 'lodash';
 const Base64 = require('js-base64').Base64;
 
@@ -267,6 +268,12 @@ export default {
       visible_id: this.$route.query?.visible_id || '', // 可见组件 id
     };
   },
+  computed: {
+    // 计算课件背景样式
+    computedCoursewareStyle() {
+      return buildCoursewareStyle(this.background, 'courseware');
+    },
+  },
   watch: {
     groupRowList: {
       handler(val) {
@@ -286,7 +293,7 @@ export default {
     },
   },
   mounted() {
-    const element = this.$refs.courserware;
+    const element = this.$refs.courseware;
     const rect = element.getBoundingClientRect();
     this.divPosition = {
       left: rect.left,
@@ -464,9 +471,10 @@ export default {
 
             // 如果有标签类组件,获取对应名称
             if (findKey) {
-              let item = this.isEdit
-                ? this.findChildComponentByKey(`grid-${findKey}`)
-                : this.$refs.previewEdit.findChildComponentByKey(`preview-${findKey}`);
+              let item = this.$refs.preview.find(
+                (child) => child.$el && child.$el.dataset && child.$el.dataset.id === findKey,
+              );
+
               if (['describe', 'stem'].includes(findType)) {
                 groupName = item.data.content.replace(/<[^>]+>/g, '');
               } else if (findType === 'label') {
@@ -655,86 +663,6 @@ export default {
         gridTemplateRows,
       };
     },
-
-    /**
-     * 计算课件背景样式
-     * @param {'courseware' | 'commonPreview'} type 从哪个组件调用 'courseware' 或 'commonPreview'
-     * @returns {object} 课件背景样式对象
-     */
-    computedCourserwareStyle(type) {
-      const {
-        background_image_url: bcImgUrl = '',
-        background_position: pos = {},
-        background: back,
-      } = this.background || {};
-
-      // 如果是 commonPreview 但背景不是全域的,或者是 courseware 但背景是全域的,都不应用背景样式
-      if (type === 'commonPreview' && !back?.is_global) {
-        return {};
-      }
-      if (type === 'courseware' && back?.is_global) {
-        return {};
-      }
-
-      let canvasStyle = {
-        backgroundSize: bcImgUrl ? `${pos.width}% ${pos.height}%` : '',
-        backgroundPosition: bcImgUrl ? `${pos.left}% ${pos.top}%` : '',
-        backgroundImage: bcImgUrl ? `url(${bcImgUrl})` : '',
-      };
-
-      if (back) {
-        if (!back.has_image) {
-          canvasStyle['backgroundBlendMode'] = '';
-          canvasStyle['backgroundImage'] = '';
-          canvasStyle['backgroundRepeat'] = '';
-          canvasStyle['backgroundPosition'] = '';
-          canvasStyle['backgroundSize'] = '';
-        }
-
-        if (back.imageMode === 'fill') {
-          canvasStyle['backgroundRepeat'] = 'repeat';
-          canvasStyle['backgroundSize'] = '';
-          canvasStyle['backgroundPosition'] = '';
-        } else {
-          canvasStyle['backgroundRepeat'] = 'no-repeat';
-        }
-
-        if (back.imageMode === 'stretch') {
-          canvasStyle['backgroundSize'] = '100% 100%';
-        }
-
-        if (back.imageMode === 'adapt') {
-          canvasStyle['backgroundSize'] = 'contain';
-        }
-
-        if (back.imageMode === 'auto') {
-          canvasStyle['backgroundPosition'] = `${pos.imgX}% ${pos.imgY}%`;
-        }
-
-        if (back.has_color) {
-          canvasStyle['backgroundColor'] = back.color;
-        } else {
-          canvasStyle['backgroundColor'] = '#fff';
-        }
-
-        if (back.enable_border) {
-          canvasStyle['border'] = `${back.border_width}px ${back.border_style} ${back.border_color}`;
-        } else {
-          canvasStyle['border'] = 'none';
-        }
-
-        if (back.enable_radius) {
-          canvasStyle['border-top-left-radius'] = `${back.top_left_radius}px`;
-          canvasStyle['border-top-right-radius'] = `${back.top_right_radius}px`;
-          canvasStyle['border-bottom-left-radius'] = `${back.bottom_left_radius}px`;
-          canvasStyle['border-bottom-right-radius'] = `${back.bottom_right_radius}px`;
-        } else {
-          canvasStyle['border-radius'] = '0';
-        }
-      }
-
-      return canvasStyle;
-    },
     handleContextMenu(event, id) {
       if (this.canRemark) {
         event.preventDefault(); // 阻止默认的上下文菜单显示
@@ -861,7 +789,7 @@ export default {
         this.selectHandleInfo = selectHandleInfo;
 
         if (!this.canRemark) this.showToolbar = true;
-        const container = document.querySelector('.courserware');
+        const container = document.querySelector('.courseware');
         const boxRect = container.getBoundingClientRect();
         const selectRect = range.getBoundingClientRect();
         this.contentmenu = {
@@ -1227,7 +1155,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
-.courserware {
+.courseware {
   position: relative;
   display: flex;
   flex-direction: column;

+ 77 - 0
src/views/book/courseware/preview/common/utils/coursewareStyle.js

@@ -0,0 +1,77 @@
+/**
+ * 构建课件背景样式
+ * @param {object} background 背景信息
+ * @param {string} type 预览类型,commonPreview: 公共预览,courseware: 课件预览
+ * @returns {object} 样式对象
+ */
+export function buildCoursewareStyle(background, type) {
+  const { background_image_url: bcImgUrl = '', background_position: pos = {}, background: back } = background || {};
+
+  if (type === 'commonPreview' && !back?.is_global) {
+    return {};
+  }
+  if (type === 'courseware' && back?.is_global) {
+    return {};
+  }
+
+  const canvasStyle = {
+    backgroundSize: bcImgUrl ? `${pos.width}% ${pos.height}%` : '',
+    backgroundPosition: bcImgUrl ? `${pos.left}% ${pos.top}%` : '',
+    backgroundImage: bcImgUrl ? `url(${bcImgUrl})` : '',
+  };
+
+  if (!back) {
+    return canvasStyle;
+  }
+
+  if (!back.has_image) {
+    canvasStyle.backgroundBlendMode = '';
+    canvasStyle.backgroundImage = '';
+    canvasStyle.backgroundRepeat = '';
+    canvasStyle.backgroundPosition = '';
+    canvasStyle.backgroundSize = '';
+  }
+
+  if (back.imageMode === 'fill') {
+    canvasStyle.backgroundRepeat = 'repeat';
+    canvasStyle.backgroundSize = '';
+    canvasStyle.backgroundPosition = '';
+  } else {
+    canvasStyle.backgroundRepeat = 'no-repeat';
+  }
+
+  if (back.imageMode === 'stretch') {
+    canvasStyle.backgroundSize = '100% 100%';
+  }
+
+  if (back.imageMode === 'adapt') {
+    canvasStyle.backgroundSize = 'contain';
+  }
+
+  if (back.imageMode === 'auto') {
+    canvasStyle.backgroundPosition = `${pos.imgX}% ${pos.imgY}%`;
+  }
+
+  if (back.has_color) {
+    canvasStyle.backgroundColor = back.color;
+  } else {
+    canvasStyle.backgroundColor = '#fff';
+  }
+
+  if (back.enable_border) {
+    canvasStyle.border = `${back.border_width}px ${back.border_style} ${back.border_color}`;
+  } else {
+    canvasStyle.border = 'none';
+  }
+
+  if (back.enable_radius) {
+    canvasStyle['border-top-left-radius'] = `${back.top_left_radius}px`;
+    canvasStyle['border-top-right-radius'] = `${back.top_right_radius}px`;
+    canvasStyle['border-bottom-left-radius'] = `${back.bottom_left_radius}px`;
+    canvasStyle['border-bottom-right-radius'] = `${back.bottom_right_radius}px`;
+  } else {
+    canvasStyle.borderRadius = '0';
+  }
+
+  return canvasStyle;
+}

+ 14 - 10
src/views/book/courseware/preview/components/article/NotesModelChs.vue

@@ -1,6 +1,6 @@
 <!--  -->
 <template>
-  <div v-if="curQue" class="NNPE-ArticleView" id="notes-model">
+  <div v-if="curQue" id="notes-model" class="NNPE-ArticleView">
     <div
       v-if="
         ((curQue.mp3_list && curQue.mp3_list.length > 0 && curQue.mp3_list[0].url) ||
@@ -78,12 +78,12 @@
                   }"
                 />
                 <video
+                  v-else
                   :src="item.sourceList[0].file_url_open"
                   width="100%"
                   height="400"
                   controls
                   controlsList="nodownload"
-                  v-else
                 ></video>
               </template>
 
@@ -565,12 +565,12 @@
                   }"
                 />
                 <video
+                  v-else
                   :src="item.sourceList[0].file_url_open"
                   width="100%"
                   height="400"
                   controls
                   controlsList="nodownload"
-                  v-else
                 ></video>
               </template>
             </div>
@@ -1051,6 +1051,7 @@
       </div>
     </div>
     <el-dialog
+      v-if="notesFlag"
       :visible.sync="notesFlag"
       :show-close="true"
       :title="'笔记'"
@@ -1058,14 +1059,19 @@
       width="367px"
       class="notes-dialog"
       @close="handleData"
-      v-if="notesFlag"
     >
       <p>{{ notesObj.title }}</p>
-      <el-input type="textarea" :rows="4" maxlength="200" show-word-limit placeholder="请输入" v-model="notesObj.notes">
-      </el-input>
+      <el-input
+        v-model="notesObj.notes"
+        type="textarea"
+        :rows="4"
+        maxlength="200"
+        show-word-limit
+        placeholder="请输入"
+      />
       <div class="btn-box">
-        <el-button type="danger" plain size="small" @click="deleteNotes" v-if="notesObj.id">删除</el-button>
-        <el-button type="primary" @click="handleSave" size="small" :loading="loading">保存</el-button>
+        <el-button v-if="notesObj.id" type="danger" plain size="small" @click="deleteNotes">删除</el-button>
+        <el-button type="primary" size="small" :loading="loading" @click="handleSave">保存</el-button>
       </div>
     </el-dialog>
   </div>
@@ -1369,7 +1375,6 @@ export default {
           .on('selection:create', ({ sources }) => {
             // sources = sources.map(hs => ({hs}));
             if (sources && sources[0]) {
-              console.log(sources[0]);
               _this.notesObj.title = sources[0].text;
               _this.notesObj.pos = JSON.stringify({
                 sent_id: _this.activeSentObj,
@@ -1532,7 +1537,6 @@ export default {
       // });
     },
     mouseupClick(obj, index) {
-      console.log(obj);
       this.activeSentObj = index;
     },
   }, // 如果页面有keep-alive缓存功能,这个函数会触发

+ 125 - 38
src/views/book/courseware/preview/components/fill/FillPreview.vue

@@ -25,16 +25,25 @@
               <span v-else :key="j" v-html="convertText(sanitizeHTML(li.content))"></span>
             </template>
             <template v-if="li.type === 'input'">
+              <!-- 输入填空 -->
               <template v-if="data.property.fill_type === fillTypeList[0].value">
                 <el-input
                   :key="j"
-                  v-model="li.content"
+                  :ref="`input-${li.mark}`"
+                  v-model="li.input"
                   :disabled="disabled"
-                  :class="[data.property.fill_font, ...computedAnswerClass(li.mark)]"
-                  :style="[{ width: Math.max(data.property.input_default_width, li.content.length * 21.3) + 'px' }]"
+                  :class="[...computedAnswerClass(li.mark)]"
+                  :style="[
+                    {
+                      fontFamily: data.property.fill_font,
+                      width: (inputWidthMap[li.mark] || data.property.input_default_width) + 'px',
+                    },
+                  ]"
+                  @input="handleInput(li.input, li.mark)"
                 />
               </template>
 
+              <!-- 选词填空 -->
               <template v-else-if="data.property.fill_type === fillTypeList[1].value">
                 <el-popover :key="j" placement="top" trigger="click">
                   <div class="word-list">
@@ -48,17 +57,16 @@
                     </span>
                   </div>
 
-                  <el-input
+                  <span
                     slot="reference"
-                    v-model="li.content"
-                    :readonly="true"
-                    :class="[data.property.fill_font, ...computedAnswerClass(li.mark)]"
-                    class="pinyin"
-                    :style="[{ width: Math.max(data.property.input_default_width, li.content.length * 21.3) + 'px' }]"
-                  />
+                    class="select-content"
+                    :style="[{ minWidth: data.property.input_default_width + 'px' }]"
+                    v-html="sanitizeHTML(li.input)"
+                  ></span>
                 </el-popover>
               </template>
 
+              <!-- 手写填空 -->
               <template v-else-if="data.property.fill_type === fillTypeList[2].value">
                 <span :key="j" class="write-click" @click="handleWriteClick(li.mark)">
                   <img
@@ -70,6 +78,7 @@
                 </span>
               </template>
 
+              <!-- 语音填空 -->
               <template v-else-if="data.property.fill_type === fillTypeList[3].value">
                 <SoundRecordBox
                   ref="record"
@@ -145,14 +154,7 @@ import SoundRecord from '../../common/SoundRecord.vue';
 import SoundRecordBox from '@/views/book/courseware/preview/components/record_input/SoundRecord.vue';
 import WriteDialog from './components/WriteDialog.vue';
 
-import {
-  getFillData,
-  fillFontList,
-  fillTypeList,
-  arrangeTypeList,
-  audioPositionList,
-} from '@/views/book/courseware/data/fill';
-import { getPlainText } from '@/utils/common';
+import { getFillData, fillTypeList, arrangeTypeList, audioPositionList } from '@/views/book/courseware/data/fill';
 
 export default {
   name: 'FillPreview',
@@ -168,16 +170,13 @@ export default {
       data: getFillData(),
       fillTypeList,
       modelEssay: [],
+      inputWidthMap: {},
       selectedWordList: [], // 用于存储选中的词汇
       writeVisible: false,
       writeMark: '',
     };
   },
   computed: {
-    fontFamily() {
-      const fontItem = fillFontList.find(({ value }) => this.data.property.fill_font === value);
-      return fontItem ? fontItem.font : '';
-    },
     isShowAnswer() {
       return (
         (Array.isArray(this.data.answer_list) && this.data.answer_list.length > 0) ||
@@ -218,10 +217,10 @@ export default {
         this.answer.answer_list = list
           .map((item) => {
             return item
-              .map(({ type, content, audio_answer_list, mark }) => {
+              .map(({ type, input, audio_answer_list, mark }) => {
                 if (type === 'input') {
                   return {
-                    value: content,
+                    value: input,
                     mark,
                     audio_answer_list,
                     write_base64: '',
@@ -242,7 +241,7 @@ export default {
         this.modelEssay.forEach((item) => {
           item.forEach((li) => {
             if (li.mark === mark) {
-              li.content = value;
+              li.input = value;
             }
           });
         });
@@ -282,7 +281,7 @@ export default {
     handleSelectWord(content, mark, li) {
       if (!content || !mark || !li) return;
 
-      li.content = getPlainText(content);
+      li.input = content;
       this.selectedWordList.push(mark);
     },
     /**
@@ -308,32 +307,63 @@ export default {
       const isFront = this.data.property.audio_position === audioPositionList[0].value;
       const isEnableVoice = this.data.property.is_enable_voice_answer === 'true';
       const isHasAudio = this.data.audio_file_id.length > 0;
-      let _list = [
+      let areaList = [
         { name: 'audio', value: '24px' },
         { name: 'fill', value: '1fr' },
       ];
 
       if (!isHasAudio) {
-        _list[0].value = '0px';
+        areaList.shift();
       }
 
-      if (!isFront) {
-        _list = _list.reverse();
+      if (!isFront && isHasAudio) {
+        areaList = areaList.reverse();
       }
+
       let gridArea = '';
       let gridTemplateRows = '';
       let gridTemplateColumns = '';
+
       if (isRow) {
-        gridArea = `"${_list[0].name} ${_list[1].name}${isEnableVoice ? ' record' : ''}" ${this.showLang ? ' "lang lang lang"' : ''}`;
-        gridTemplateRows = `auto ${this.showLang ? 'auto' : ''}`;
-        gridTemplateColumns = `${_list[0].value} ${_list[1].value}${isEnableVoice ? ' 160px' : ''}`;
+        const rowAreas = areaList.map(({ name }) => name);
+        const rowCols = areaList.map(({ value }) => value);
+        const templateRows = ['auto'];
+
+        if (isEnableVoice) {
+          rowAreas.push('record');
+          rowCols.push('160px');
+        }
+
+        const areaRows = [`"${rowAreas.join(' ')}"`];
+
+        if (this.showLang) {
+          areaRows.push(`"${Array(rowAreas.length).fill('lang').join(' ')}"`);
+          templateRows.push('auto');
+        }
+
+        gridArea = areaRows.join(' ');
+        gridTemplateRows = templateRows.join(' ');
+        gridTemplateColumns = rowCols.join(' ');
       } else {
-        gridArea = `"${_list[0].name}" "${_list[1].name}" ${isEnableVoice ? `" record" ` : ''} ${this.showLang ? ' "lang"' : ''}`;
-        gridTemplateRows = `${_list[0].value} ${_list[1].value} ${isEnableVoice ? ' 32px' : ''} ${this.showLang ? 'auto' : ''}`;
+        const areaRows = areaList.map(({ name }) => `"${name}"`);
+        const templateRows = areaList.map(({ value }) => value);
+
+        if (isEnableVoice) {
+          areaRows.push('"record"');
+          templateRows.push('32px');
+        }
+
+        if (this.showLang) {
+          areaRows.push('"lang"');
+          templateRows.push('auto');
+        }
+
+        gridArea = areaRows.join(' ');
+        gridTemplateRows = templateRows.join(' ');
         gridTemplateColumns = '1fr';
       }
 
-      let style = {
+      return {
         'grid-auto-flow': isRow ? 'column' : 'row',
         'column-gap': isRow && isHasAudio ? '16px' : undefined,
         'row-gap': isRow || !isHasAudio ? undefined : '8px',
@@ -341,7 +371,6 @@ export default {
         'grid-template-rows': gridTemplateRows,
         'grid-template-columns': gridTemplateColumns,
       };
-      return style;
     },
     /**
      * 计算答题对错选项字体颜色
@@ -383,7 +412,7 @@ export default {
       this.modelEssay.forEach((item) => {
         item.forEach((li) => {
           if (li.type === 'input') {
-            li.content = '';
+            li.input = '';
             li.write_base64 = '';
           }
         });
@@ -416,6 +445,50 @@ export default {
 
       return noTextContentData;
     },
+    /**
+     * 处理输入框内容变化
+     * @description 根据输入框值计算所需长度,动态调整输入框宽度,输入框宽度不小于默认宽度,且不超过组件最大宽度 1000px,需要考虑输入框前面的宽度,保证输入框不会超出组件范围
+     * @param {String} value 输入框当前值
+     * @param {String} mark 选项标识
+     */
+    handleInput(value, mark) {
+      if (!mark) return;
+
+      const text = `${value || ''}`;
+      const defaultWidth = Number(this.data?.property?.input_default_width) || 120;
+      const fontSize = 16;
+      const fontFamily = this.data?.property?.fill_font || 'arial';
+
+      const canvas = document.createElement('canvas');
+      const context = canvas.getContext('2d');
+      let textWidth = 0;
+
+      if (context) {
+        context.font = `${fontSize}pt ${fontFamily}`;
+        textWidth = context.measureText(text || ' ').width;
+      }
+
+      // 额外留出输入框内边距和边框余量,避免刚好卡边。
+      const contentWidth = Math.ceil(textWidth + 24);
+
+      const inputRef = this.$refs[`input-${mark}`];
+      const inputVm = Array.isArray(inputRef) ? inputRef[0] : inputRef;
+      const inputEl = inputVm?.$el;
+      const containerEl = this.$el;
+
+      let availableWidth = 1000;
+      if (inputEl && containerEl) {
+        const inputRect = inputEl.getBoundingClientRect();
+        const containerRect = containerEl.getBoundingClientRect();
+        const rightLimit = containerRect.left + Math.min(containerRect.width, 1000);
+        availableWidth = Math.floor(rightLimit - inputRect.left - 12);
+      }
+
+      const maxWidth = Math.max(40, availableWidth);
+      const nextWidth = Math.min(Math.max(contentWidth, defaultWidth), maxWidth);
+
+      this.$set(this.inputWidthMap, mark, nextWidth);
+    },
   },
 };
 </script>
@@ -428,6 +501,7 @@ export default {
 
   .main {
     display: grid;
+    gap: 10px;
     align-items: center;
   }
 
@@ -465,6 +539,19 @@ export default {
       }
     }
 
+    .select-content {
+      display: inline-block;
+      height: 32px;
+      margin: 0 10px;
+      vertical-align: bottom;
+      cursor: pointer;
+      border-bottom: 1px solid $font-color;
+
+      :deep p {
+        margin: 0;
+      }
+    }
+
     .el-input {
       display: inline-flex;
       align-items: center;

+ 4 - 3
src/views/book/courseware/preview/components/judge/JudgePreview.vue

@@ -202,9 +202,10 @@ export default {
       if (!this.isJudgingRightWrong) return '';
       let selectOption = this.answer.answer_list.find((item) => item.mark === mark); // 查找是否已选中的选项
       if (!selectOption) return '';
-      return this.data.answer.answer_list.find((item) => item.mark === mark)?.option_type === selectOption.option_type
-        ? 'right'
-        : 'wrong';
+      // 正确选项的类型
+      const correctOption = this.data.answer.answer_list.find((item) => item.mark === mark)?.option_type;
+      if (!correctOption) return '';
+      return correctOption === selectOption.option_type ? 'right' : 'wrong';
     },
     // 计算是否显示正确答案的样式
     computedIsShowRightAnswer(mark, option_type) {

+ 4 - 2
src/views/book/courseware/preview/components/new_word/NewWordPreview.vue

@@ -1362,7 +1362,9 @@
                                     : '',
                               }"
                             >
-                              {{ item.pinyin.split(' ')[indexh] ? item.pinyin.split(' ')[indexh] : '&nbsp;' }}
+                              {{
+                                item.pinyin.trim().split(' ')[indexh] ? item.pinyin.trim().split(' ')[indexh] : '&nbsp;'
+                              }}
                             </p>
                             <Strockplay
                               v-if="itemh.hzDetail.hz_json"
@@ -1908,7 +1910,7 @@ export default {
         // }
         let flag = false;
         if (item.pinyin) {
-          flag = item.pinyin.split(' ').length === item.new_word.length;
+          flag = item.pinyin.trim().split(' ').length === item.new_word.length;
         }
         this.$set(item, 'show_left', true);
         this.$set(item, 'isFlipped', false);

+ 1 - 1
src/views/book/courseware/preview/components/new_word/components/writeTableZoom.vue

@@ -125,7 +125,7 @@
                   v-if="data.pinyin && data.pyhz"
                   :style="{ color: attrib && attrib.topic_color ? attrib.topic_color : '' }"
                 >
-                  {{ data.pinyin.split(' ')[indexh] ? data.pinyin.split(' ')[indexh] : '&nbsp;' }}
+                  {{ data.pinyin.trim().split(' ')[indexh] ? data.pinyin.trim().split(' ')[indexh] : '&nbsp;' }}
                 </p>
                 <Strockplay
                   class-name="adult-strockplay"

+ 1 - 0
src/views/book/courseware/preview/components/sort/SortPreview.vue

@@ -210,6 +210,7 @@ export default {
     getNoTextContentData() {
       let noTextContentData = JSON.parse(JSON.stringify(this.data));
       const resetFieldMap = {
+        content: '',
         analysis_list: [],
         answer_list: [],
       };

+ 235 - 6
src/views/book/courseware/preview/components/table/TablePreview.vue

@@ -58,7 +58,10 @@
                           v-for="(item, index) in col.rich_text_list"
                           :key="index"
                           class="pinyin-text"
-                          :class="[index === 0 ? 'pinyin-text-left' : '']"
+                          :class="[
+                            index === 0 ? 'pinyin-text-left' : '',
+                            item.type === 'audio' ? 'pinyin-text-audio' : '',
+                          ]"
                         >
                           <!-- 文字块:包含 word_list -->
                           <span
@@ -167,8 +170,38 @@
                               class="inline-image"
                             />
                           </div>
+                          <!-- 视频块 -->
+                          <div
+                            v-else-if="item.type === 'video'"
+                            :key="'video-' + index"
+                            :style="item.containerStyle"
+                            class="video-container"
+                          >
+                            <video
+                              :poster="item.poster"
+                              :width="item.width"
+                              :height="item.height"
+                              :style="item.mediaStyle"
+                              controls
+                              class="inline-video"
+                            >
+                              <source :src="item.src" :type="item.videoType" />
+                              您的浏览器不支持视频播放
+                            </video>
+                          </div>
+                          <!-- 音频块 -->
+                          <div
+                            v-else-if="item.type === 'audio'"
+                            :key="'audio-' + index"
+                            :style="item.containerStyle"
+                            class="audio-container"
+                          >
+                            <audio :src="item.src" :style="item.mediaStyle" controls class="inline-audio">
+                              您的浏览器不支持音频播放
+                            </audio>
+                          </div>
                           <!-- 换行符 -->
-                          <br v-else-if="block.type === 'newline'" :key="'newline-' + index" />
+                          <br v-else-if="item.type === 'newline'" :key="'newline-' + index" />
 
                           <!-- 
                         <template v-else>
@@ -355,7 +388,10 @@
                             v-for="(item, index) in col.rich_text_list"
                             :key="index"
                             class="pinyin-text"
-                            :class="[index === 0 ? 'pinyin-text-left' : '']"
+                            :class="[
+                              index === 0 ? 'pinyin-text-left' : '',
+                              item.type === 'audio' ? 'pinyin-text-audio' : '',
+                            ]"
                           >
                             <!-- 文字块:包含 word_list -->
                             <span
@@ -415,8 +451,39 @@
                                 class="inline-image"
                               />
                             </div>
+                            <!-- 视频块 -->
+                            <div
+                              v-else-if="item.type === 'video'"
+                              :key="'video-' + index"
+                              :style="item.containerStyle"
+                              class="video-container"
+                            >
+                              <video
+                                :poster="item.poster"
+                                :width="item.width"
+                                :height="item.height"
+                                :style="item.mediaStyle"
+                                controls
+                                class="inline-video"
+                              >
+                                <source :src="item.src" :type="item.videoType" />
+                                您的浏览器不支持视频播放
+                              </video>
+                            </div>
+
+                            <!-- 音频块 -->
+                            <div
+                              v-else-if="item.type === 'audio'"
+                              :key="'audio-' + index"
+                              :style="item.containerStyle"
+                              class="audio-container"
+                            >
+                              <audio :src="item.src" :style="item.mediaStyle" controls class="inline-audio">
+                                您的浏览器不支持音频播放
+                              </audio>
+                            </div>
                             <!-- 换行符 -->
-                            <br v-else-if="block.type === 'newline'" :key="'newline-' + index" />
+                            <br v-else-if="item.type === 'newline'" :key="'newline-' + index" />
                           </div>
                         </template>
                       </template>
@@ -612,20 +679,80 @@ export default {
     this.computedTableCellShow();
   },
   methods: {
+    // 预处理 richTextList,合并分散的视频标签
+    normalizedRichTextList(col) {
+      if (!col.rich_text_list || col.rich_text_list.length === 0) return [];
+
+      const result = [];
+      let i = 0;
+      while (i < col.rich_text_list.length) {
+        const item = col.rich_text_list[i];
+
+        // 检查是否是视频开始标签(且不包含结束标签)
+        if (
+          item.text &&
+          typeof item.text === 'string' &&
+          item.text.includes('<video') &&
+          !item.text.includes('</video>')
+        ) {
+          let combinedText = item.text;
+          let j = i + 1;
+
+          // 向后查找 source 和结束标签,直到遇到 </video> 或超出范围
+          while (j < col.rich_text_list.length) {
+            const nextItem = col.rich_text_list[j];
+            if (nextItem.text) {
+              combinedText += `${nextItem.text}`;
+            }
+
+            // 如果找到了结束标签,合并完成,跳出内部循环
+            if (nextItem.text && nextItem.text.includes('</video>')) {
+              j++; // 跳过结束标签
+              break;
+            }
+            j++;
+          }
+
+          // 将合并后的内容作为一个新项加入结果
+          result.push({
+            ...item,
+            text: combinedText,
+            is_merged_video: true, // 标记一下,可选
+          });
+
+          // 更新主循环索引,跳过已合并的项
+          i = j;
+        } else {
+          // 其他项直接加入
+          result.push(item);
+          i++;
+        }
+      }
+
+      return result;
+    },
     // 解析 richTextList 为可渲染的块
     parsedBlocks(col) {
-      if (!col.rich_text_list || col.rich_text_list.length === 0) return [];
+      const listToParse = this.normalizedRichTextList(col);
+
+      if (!listToParse || listToParse.length === 0) return [];
+      // if (!col.rich_text_list || col.rich_text_list.length === 0) return [];
       const blocks = [];
       let textBlockIndex = 0;
       let oldIndex = -1;
       let paragraphIndex = 0;
       const tagStack = [];
 
-      for (const item of col.rich_text_list) {
+      for (const item of listToParse) {
         oldIndex += 1;
 
         if (item.text && typeof item.text === 'string' && item.text.includes('<img')) {
           blocks.push(this.parseImageBlock(item, tagStack));
+        } else if (item.text && typeof item.text === 'string' && item.text.includes('<video')) {
+          // item.text 是完整的 video 标签串
+          blocks.push(this.parseVideoBlock(item, tagStack));
+        } else if (item.text && typeof item.text === 'string' && item.text.includes('<audio')) {
+          blocks.push(this.parseAudioBlock(item, tagStack));
         } else if (item.is_style === 'true' || item.is_style === true) {
           this.handleStyleTag(item, tagStack);
         } else if (item.text === '\n') {
@@ -820,6 +947,83 @@ export default {
       this.mergeStyleString(styleObj, styleStr);
       return styleObj;
     },
+    // 解析视频块
+    parseVideoBlock(item, tagStack) {
+      const videoMatch = item.text.match(/<video\s+([^>]*)>/i);
+      if (!videoMatch) return null;
+
+      const attrs = videoMatch[1];
+      const posterMatch = attrs.match(/poster=["']([^"']*)["']/i);
+      const widthMatch = attrs.match(/width=["']?(\d+)["']?/i);
+      const heightMatch = attrs.match(/height=["']?(\d+)["']?/i);
+      const styleMatch = attrs.match(/style=["']([^"']*)["']/i);
+
+      // 直接从完整的文本中匹配 source
+      const sourceMatch = item.text.match(/<source\s+([^>]*)\/?\s*>/i);
+      let src = '';
+      let videoType = '';
+
+      if (sourceMatch) {
+        const sourceAttrs = sourceMatch[1];
+        const srcMatch = sourceAttrs.match(/src\s*=\s*["']?([^"'\s>]+)["']?/i);
+        const typeMatch = sourceAttrs.match(/type\s*=\s*["']?([^"'\s>]+)["']?/i);
+
+        src = srcMatch ? srcMatch[1] : '';
+        videoType = typeMatch ? typeMatch[1] : '';
+      }
+
+      const containerStyleObj = {};
+      tagStack.forEach((tagItem) => {
+        if (tagItem.style) {
+          this.mergeStyleString(containerStyleObj, tagItem.style);
+        }
+      });
+
+      const mediaStyleObj = {};
+      if (styleMatch) {
+        this.mergeStyleString(mediaStyleObj, styleMatch[1]);
+      }
+
+      return {
+        type: 'video',
+        poster: posterMatch ? posterMatch[1] : '',
+        width: widthMatch ? widthMatch[1] : null,
+        height: heightMatch ? heightMatch[1] : null,
+        src,
+        videoType,
+        containerStyle: containerStyleObj,
+        mediaStyle: mediaStyleObj,
+      };
+    },
+
+    // 解析音频块
+    parseAudioBlock(item, tagStack) {
+      const audioMatch = item.text.match(/<audio\s+([^>]*)>/i);
+      if (!audioMatch) return null;
+
+      const attrs = audioMatch[1];
+      const srcMatch = attrs.match(/src=["']([^"']*)["']/i);
+      const styleMatch = attrs.match(/style=["']([^"']*)["']/i);
+
+      const containerStyleObj = {};
+      tagStack.forEach((tagItem) => {
+        if (tagItem.style) {
+          this.mergeStyleString(containerStyleObj, tagItem.style);
+        }
+      });
+
+      const mediaStyleObj = {};
+      if (styleMatch) {
+        this.mergeStyleString(mediaStyleObj, styleMatch[1]);
+      }
+
+      return {
+        type: 'audio',
+        src: srcMatch ? srcMatch[1] : '',
+        containerStyle: containerStyleObj,
+        mediaStyle: mediaStyleObj,
+      };
+    },
     // 合并样式字符串到对象
     mergeStyleString(styleObj, styleStr) {
       styleStr.split(';').forEach((rule) => {
@@ -1269,6 +1473,31 @@ $border-color: #e6e6e6;
           height: auto;
         }
       }
+
+      .video-container {
+        display: inline-block;
+        margin: 26px 5px 26px 0;
+
+        .inline-video {
+          display: inline-block;
+          max-width: 100%;
+          height: auto;
+        }
+      }
+
+      .audio-container {
+        display: inline-block;
+
+        .inline-audio {
+          display: inline-block;
+          width: 100%;
+          max-width: 500px;
+        }
+      }
+
+      .pinyin-text-audio {
+        width: 100%;
+      }
     }
 
     :deep .el-input--small .el-input__inner {

+ 1 - 1
src/views/personal_workbench/edit_task/edit/UseTemplate.vue

@@ -65,7 +65,7 @@
         </div>
         <CoursewarePreview
           v-if="coursewareData.row_list?.length > 0"
-          ref="courserware"
+          ref="courseware"
           :is-show-group="false"
           :group-show-all="true"
           :group-row-list="content_group_row_list"

+ 0 - 1
src/views/personal_workbench/edit_task/preview/index.vue

@@ -149,7 +149,6 @@ export default {
           let courseware_info = {};
           if (form.is_select_part_courseware_mode === 'true') {
             courseware_info = this.$refs.preview.computedSelectedGroupCoursewareInfo();
-
             if (courseware_info?.component_id_list.length === 0) {
               this.$message.warning('请选择要保存的内容');
               return;

+ 7 - 2
src/views/personal_workbench/project/components/BookUnifiedAttr.vue

@@ -12,7 +12,7 @@
         <el-form ref="form" :model="unified_attrib" label-width="80px" size="small">
           <el-form-item label="主题色">
             <div class="color-group">
-              <el-color-picker v-model="unified_attrib.topic_color" />
+              <ColorPicker v-model="unified_attrib.topic_color" :type="2" />
               <span>辅助色</span>
               <el-color-picker v-model="unified_attrib.assist_color" />
               <span class="link" @click="generateAssistColor">自动生成辅助色</span>
@@ -45,7 +45,7 @@
             <el-input-number v-model="unified_attrib.line_height" :min="0" :max="20" :step="0.1" />
           </el-form-item>
           <el-form-item label="文字颜色">
-            <el-color-picker v-model="unified_attrib.text_color" />
+            <ColorPicker v-model="unified_attrib.text_color" :type="1" />
           </el-form-item>
           <el-form-item label="对齐方式">
             <el-select v-model="unified_attrib.align" placeholder="请选择对齐方式">
@@ -76,6 +76,8 @@
 </template>
 
 <script>
+import ColorPicker from '@/components/ColorPicker.vue';
+
 import { pinyinPositionList, isEnable } from '@/views/book/courseware/data/common';
 import { GetBookUnifiedAttrib, ApplyBookUnifiedAttrib, SaveBookUnifiedAttrib } from '@/api/book';
 import { ToAuxiliaryColor } from '@/api/app';
@@ -83,6 +85,9 @@ import { unified_attrib } from '@/common/data';
 
 export default {
   name: 'BookUnifiedAttrPage',
+  components: {
+    ColorPicker,
+  },
   props: {
     visible: {
       type: Boolean,

+ 1 - 1
src/views/personal_workbench/template_list/preview/CommonPreview.vue

@@ -39,7 +39,7 @@
           <div class="preview-left"></div>
           <CoursewarePreview
             v-if="courseware_info.book_name || courseware_info.name"
-            ref="courserware"
+            ref="courseware"
             :is-show-group="false"
             :group-show-all="true"
             :group-row-list="content_group_row_list"

+ 0 - 2
src/views/project_manage/org/project/index.vue

@@ -75,8 +75,6 @@ export default {
      * @param {string} request_status 申请状态
      */
     projectAuditOperate(project_id, is_pass, request_status) {
-      console.log(request_status);
-
       this.$confirm(`确定要执行${is_pass === 'true' ? '审核通过' : '审核拒绝'}吗?`, '提示', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',