Просмотр исходного кода

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

zq 2 дней назад
Родитель
Сommit
87760fff1c

+ 1 - 1
.env

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

+ 1 - 1
package.json

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

+ 143 - 70
src/views/book/courseware/create/components/question/image_text/ImageText.vue

@@ -72,68 +72,92 @@
             border: isText ? '2px solid #165DFF' : '2px solid #f90',
             border: isText ? '2px solid #165DFF' : '2px solid #f90',
           }"
           }"
         ></div>
         ></div>
-        <template v-for="(itemh, indexh) in data.text_list">
-          <div
-            v-if="indexh === hotspotsActiveIndex"
-            :key="indexh"
+        <div
+          v-for="(itemh, indexh) in data.text_list"
+          :key="indexh"
+          :style="{
+            position: 'absolute',
+            top: `${itemh.y}`,
+            left: `${itemh.x}`,
+            width: `${itemh.width}`,
+            height: `${itemh.height}`,
+            border: indexh === hotspotsActiveIndex ? '2px solid #f00' : '2px solid #165DFF',
+          }"
+          @click="handleAcitveSelect('text', indexh)"
+        >
+          <label
             :style="{
             :style="{
               position: 'absolute',
               position: 'absolute',
-              top: `${itemh.y}`,
-              left: `${itemh.x}`,
-              width: `${itemh.width}`,
-              height: `${itemh.height}`,
-              border: '2px solid #165DFF',
+              top: '-13px',
+              right: '-13px',
+              width: '26px',
+              height: '26px',
+              border: indexh === hotspotsActiveIndex ? '2px solid #f00' : '2px solid #165DFF',
+              textAlign: 'center',
+              borderRadius: '50%',
+              boxShadow: ' 0px 5px 5px -3px #0000001A',
+              background: '#fff',
+              color: indexh === hotspotsActiveIndex ? '#f00' : '#165DFF',
             }"
             }"
+            >{{ indexh + 1 }}</label
           >
           >
-            <label
-              :style="{
-                position: 'absolute',
-                top: '-13px',
-                right: '-13px',
-                width: '26px',
-                height: '26px',
-                border: '2px solid #165DFF',
-                textAlign: 'center',
-                borderRadius: '50%',
-                boxShadow: ' 0px 5px 5px -3px #0000001A',
-                background: '#fff',
-                color: '#165DFF',
-              }"
-              >{{ indexh + 1 }}</label
-            >
-          </div>
-        </template>
-        <template v-for="(itemh, indexhs) in data.input_list">
-          <div
-            v-if="indexhs === inputActiveIndex"
-            :key="indexhs"
+          <span
             :style="{
             :style="{
               position: 'absolute',
               position: 'absolute',
-              top: `${itemh.y}`,
-              left: `${itemh.x}`,
-              width: `${itemh.width}`,
-              height: `${itemh.height}`,
-              border: '2px solid #f90',
+              bottom: '0',
+              left: '0',
+              borderRadius: '0 2px 0 0',
+              background: indexh === hotspotsActiveIndex ? '#f00' : '#165DFF',
+              color: '#fff',
+              fontSize: '12px',
+              padding: '0 3px',
             }"
             }"
+            >文字框</span
           >
           >
-            <label
-              :style="{
-                position: 'absolute',
-                top: '-13px',
-                right: '-13px',
-                width: '26px',
-                height: '26px',
-                border: '2px solid #f90',
-                textAlign: 'center',
-                borderRadius: '50%',
-                boxShadow: ' 0px 5px 5px -3px #0000001A',
-                background: '#fff',
-                color: '#f90',
-              }"
-              >{{ indexhs + 1 }}</label
-            >
-          </div>
-        </template>
+        </div>
+        <div
+          v-for="(itemh, indexhs) in data.input_list"
+          :key="indexhs"
+          :style="{
+            position: 'absolute',
+            top: `${itemh.y}`,
+            left: `${itemh.x}`,
+            width: `${itemh.width}`,
+            height: `${itemh.height}`,
+            border: indexhs === inputActiveIndex ? '2px solid #ff0000' : '2px solid #f90',
+          }"
+          @click="handleAcitveSelect('input', indexhs)"
+        >
+          <label
+            :style="{
+              position: 'absolute',
+              top: '-13px',
+              right: '-13px',
+              width: '26px',
+              height: '26px',
+              border: indexhs === inputActiveIndex ? '2px solid #ff0000' : '2px solid #f90',
+              textAlign: 'center',
+              borderRadius: '50%',
+              boxShadow: ' 0px 5px 5px -3px #0000001A',
+              background: '#fff',
+              color: '#f90',
+            }"
+            >{{ indexhs + 1 }}</label
+          >
+          <span
+            :style="{
+              position: 'absolute',
+              bottom: '0',
+              left: '0',
+              borderRadius: '0 2px 0 0',
+              background: indexhs === inputActiveIndex ? '#f00' : '#f90',
+              color: '#fff',
+              fontSize: '12px',
+              padding: '0 3px',
+            }"
+            >输入框</span
+          >
+        </div>
       </div>
       </div>
       <template v-if="data.image_list[0] && data.text_list.length > 0">
       <template v-if="data.image_list[0] && data.text_list.length > 0">
         <h4>文本框内容</h4>
         <h4>文本框内容</h4>
@@ -157,7 +181,10 @@
               v-if="hotspotsActiveIndex !== indexh"
               v-if="hotspotsActiveIndex !== indexh"
               type="primary"
               type="primary"
               size="small"
               size="small"
-              @click="hotspotsActiveIndex = indexh"
+              @click="
+                hotspotsActiveIndex = indexh;
+                inputActiveIndex = null;
+              "
               >编辑</el-button
               >编辑</el-button
             >
             >
             <el-button v-else type="primary" size="small" @click="hotspotsActiveIndex = null">保存</el-button>
             <el-button v-else type="primary" size="small" @click="hotspotsActiveIndex = null">保存</el-button>
@@ -193,7 +220,10 @@
               v-if="inputActiveIndex !== indexh"
               v-if="inputActiveIndex !== indexh"
               type="primary"
               type="primary"
               size="small"
               size="small"
-              @click="inputActiveIndex = indexh"
+              @click="
+                inputActiveIndex = indexh;
+                hotspotsActiveIndex = null;
+              "
             >
             >
               编辑
               编辑
             </el-button>
             </el-button>
@@ -264,10 +294,10 @@ export default {
       acceptFileType: '.jpg,.png,.jpeg',
       acceptFileType: '.jpg,.png,.jpeg',
       iconClass: 'picture',
       iconClass: 'picture',
       isSelecting: false,
       isSelecting: false,
-      startX: 0,
-      startY: 0,
-      endX: 0,
-      endY: 0,
+      startX: null,
+      startY: null,
+      endX: null,
+      endY: null,
       hotspotsActiveIndex: null, // 当前编辑文本框热区索引
       hotspotsActiveIndex: null, // 当前编辑文本框热区索引
       genloading: false, // 字幕节点loading
       genloading: false, // 字幕节点loading
       isText: true, // 框选是文本还是输入框
       isText: true, // 框选是文本还是输入框
@@ -282,6 +312,8 @@ export default {
       showStyleFlag: false,
       showStyleFlag: false,
       styleData: null,
       styleData: null,
       randomId: Math.random().toString(36).substring(2, 12),
       randomId: Math.random().toString(36).substring(2, 12),
+      activeType: null,
+      activeIndex: null,
     };
     };
   },
   },
   watch: {
   watch: {
@@ -304,6 +336,15 @@ export default {
     'data.text_list': 'handleMindMap',
     'data.text_list': 'handleMindMap',
   },
   },
   methods: {
   methods: {
+    handleAcitveSelect(type, index) {
+      this.activeType = type;
+      this.activeIndex = index;
+      if (type === 'text') {
+        this.hotspotsActiveIndex = index;
+      } else {
+        this.inputActiveIndex = index;
+      }
+    },
     updateFileList({ file_list, file_id_list, file_info_list }) {
     updateFileList({ file_list, file_id_list, file_info_list }) {
       this.data.image_list = file_list;
       this.data.image_list = file_list;
       this.data.image_id_list = file_id_list;
       this.data.image_id_list = file_id_list;
@@ -333,7 +374,6 @@ export default {
     startSelection(event) {
     startSelection(event) {
       this.isSelecting = true;
       this.isSelecting = true;
       let clientRect = document.getElementById(`selectableArea${this.randomId}`).getBoundingClientRect();
       let clientRect = document.getElementById(`selectableArea${this.randomId}`).getBoundingClientRect();
-
       this.startX = event.clientX - clientRect.left;
       this.startX = event.clientX - clientRect.left;
       this.startY = event.clientY - clientRect.top;
       this.startY = event.clientY - clientRect.top;
     },
     },
@@ -345,6 +385,12 @@ export default {
       this.endY = event.clientY - clientRect.top;
       this.endY = event.clientY - clientRect.top;
     },
     },
     endSelection() {
     endSelection() {
+      console.log('this.startX' + this.startX);
+      console.log('this.endX' + this.endX);
+      console.log('this.startY' + this.startY);
+      console.log('this.endY' + this.endY);
+
+      if (!this.isSelecting || this.startX === this.endX || !this.endX) return;
       this.isSelecting = false;
       this.isSelecting = false;
       const width = Math.abs(this.endX - this.startX);
       const width = Math.abs(this.endX - this.startX);
       const height = Math.abs(this.endY - this.startY);
       const height = Math.abs(this.endY - this.startY);
@@ -358,16 +404,20 @@ export default {
         y,
         y,
         text: '',
         text: '',
       };
       };
-      this.startX = 0;
-      this.endX = 0;
-      this.startY = 0;
-      this.endY = 0;
+      this.startX = null;
+      this.endX = null;
+      this.startY = null;
+      this.endY = null;
       if (width && height && this.isText) {
       if (width && height && this.isText) {
         this.data.text_list.push(obj);
         this.data.text_list.push(obj);
+        this.activeType = 'text';
+        this.activeIndex = this.data.text_list.length - 1;
 
 
         this.hotspotsActiveIndex = this.data.text_list.length - 1;
         this.hotspotsActiveIndex = this.data.text_list.length - 1;
       } else if (width && height && !this.isText) {
       } else if (width && height && !this.isText) {
         this.data.input_list.push(obj);
         this.data.input_list.push(obj);
+        this.activeType = 'input';
+        this.activeIndex = this.data.input_list.length - 1;
 
 
         this.inputActiveIndex = this.data.input_list.length - 1;
         this.inputActiveIndex = this.data.input_list.length - 1;
       }
       }
@@ -563,20 +613,43 @@ export default {
     handleGlobalEsc(event) {
     handleGlobalEsc(event) {
       if (event.key === 'Escape' || event.key === 'Esc' || event.keyCode === 27) {
       if (event.key === 'Escape' || event.key === 'Esc' || event.keyCode === 27) {
         this.isSelecting = false;
         this.isSelecting = false;
-        this.startX = 0;
-        this.startY = 0;
-        this.endX = 0;
-        this.endY = 0;
+        this.startX = null;
+        this.startY = null;
+        this.endX = null;
+        this.endY = null;
+      }
+    },
+    handleGlobalDelete(event) {
+      if (event.keyCode === 8 || event.key === 'Backspace' || event.key === 'Delete') {
+        if (this.activeIndex !== null) {
+          if (this.activeType === 'text') {
+            this.data.text_list.splice(this.activeIndex, 1);
+            this.hotspotsActiveIndex = null;
+          } else {
+            this.data.input_list.splice(this.activeIndex, 1);
+            this.inputActiveIndex = null;
+          }
+          this.activeType = null;
+          this.activeIndex = null;
+          this.isSelecting = false;
+          this.startX = null;
+          this.startY = null;
+          this.endX = null;
+          this.endY = null;
+        }
       }
       }
     },
     },
   },
   },
   mounted() {
   mounted() {
-    // 添加全局事件监听器
+    // 添加全局事件监听器esc
     document.addEventListener('keyup', this.handleGlobalEsc);
     document.addEventListener('keyup', this.handleGlobalEsc);
+    // 添加全局事件监听器delete
+    document.addEventListener('keyup', this.handleGlobalDelete);
   },
   },
   beforeDestroy() {
   beforeDestroy() {
     // 移除全局事件监听器
     // 移除全局事件监听器
     document.removeEventListener('keyup', this.handleGlobalEsc);
     document.removeEventListener('keyup', this.handleGlobalEsc);
+    document.removeEventListener('keyup', this.handleGlobalDelete);
   },
   },
 };
 };
 </script>
 </script>

+ 95 - 18
src/views/book/courseware/create/components/question/video_interaction/ExerciseAdd.vue

@@ -23,7 +23,7 @@
           :border-color="''"
           :border-color="''"
           :component-move="componentMove"
           :component-move="componentMove"
         />
         />
-        <!-- <component :is="getSettingCom" ref="setting" /> -->
+        <component :is="getSettingCom" ref="setting" @addAnswerAndAnalysis="addAnswerAndAnalysis" />
       </div>
       </div>
     </div>
     </div>
     <footer style="text-align: right">
     <footer style="text-align: right">
@@ -38,23 +38,60 @@ import SelectPage from '@/views/book/courseware/create/components/question/selec
 import SelectSetting from '@/views/book/courseware/create/components/question/select/SelectSetting.vue';
 import SelectSetting from '@/views/book/courseware/create/components/question/select/SelectSetting.vue';
 import Judge from '@/views/book/courseware/create/components/question/judge/Judge.vue';
 import Judge from '@/views/book/courseware/create/components/question/judge/Judge.vue';
 import JudgeSetting from '@/views/book/courseware/create/components/question/judge/JudgeSetting.vue';
 import JudgeSetting from '@/views/book/courseware/create/components/question/judge/JudgeSetting.vue';
+import SortPage from '@/views/book/courseware/create/components/question/sort/Sort.vue';
+import SortSetting from '@/views/book/courseware/create/components/question/sort/SortSetting.vue';
+import MatchingPage from '@/views/book/courseware/create/components/question/matching/Matching.vue';
+import MatchingSetting from '@/views/book/courseware/create/components/question/matching/MatchingSetting.vue';
+import FillPage from '@/views/book/courseware/create/components/question/fill/Fill.vue';
+import FillSetting from '@/views/book/courseware/create/components/question/fill/FillSetting.vue';
+import RecordInput from '@/views/book/courseware/create/components/question/record_input/RecordInput.vue';
+import RecordInputSetting from '@/views/book/courseware/create/components/question/record_input/RecordInputSetting.vue';
 import RichText from '@/components/RichText.vue';
 import RichText from '@/components/RichText.vue';
+
 import { SaveCoursewareExercise } from '@/api/book';
 import { SaveCoursewareExercise } from '@/api/book';
+import { getSelectData } from '@/views/book/courseware/data/select';
+
 export default {
 export default {
   name: 'ExerciseAdd',
   name: 'ExerciseAdd',
-  components: { SelectPage, SelectSetting, Judge, JudgeSetting, RichText },
-  props: ['exerciseContent'],
+  components: {
+    SelectPage,
+    SelectSetting,
+    Judge,
+    JudgeSetting,
+    SortPage,
+    SortSetting,
+    MatchingPage,
+    MatchingSetting,
+    FillPage,
+    FillSetting,
+    RecordInput,
+    RecordInputSetting,
+    RichText,
+  },
+  props: {
+    exerciseContent: {
+      type: [Object, null],
+      required: true,
+    },
+  },
   data() {
   data() {
     return {
     return {
       typeList: [
       typeList: [
         {
         {
-          label: '选择题',
           value: 'select',
           value: 'select',
+          label: '选择题',
         },
         },
         {
         {
-          label: '判断题',
           value: 'judge',
           value: 'judge',
+          label: '判断题',
         },
         },
+        {
+          value: 'sort',
+          label: '排序题',
+        },
+        { value: 'matching', label: '连线题' },
+        { value: 'fill', label: '填空题' },
+        { value: 'record_input', label: '录音' },
       ],
       ],
       typeValue: 'select',
       typeValue: 'select',
       title: '',
       title: '',
@@ -70,6 +107,15 @@ export default {
           return SelectPage;
           return SelectPage;
         case 'judge':
         case 'judge':
           return Judge;
           return Judge;
+        case 'sort':
+          return SortPage;
+        case 'matching':
+          return MatchingPage;
+        case 'fill':
+          return FillPage;
+        case 'record_input':
+          return RecordInput;
+
         default:
         default:
           return SelectPage;
           return SelectPage;
       }
       }
@@ -80,24 +126,49 @@ export default {
           return SelectSetting;
           return SelectSetting;
         case 'judge':
         case 'judge':
           return JudgeSetting;
           return JudgeSetting;
+        case 'sort':
+          return SortSetting;
+        case 'matching':
+          return MatchingSetting;
+        case 'fill':
+          return FillSetting;
+        case 'record_input':
+          return RecordInputSetting;
         default:
         default:
           return SelectSetting;
           return SelectSetting;
       }
       }
     },
     },
   },
   },
-  // 生命周期 - 创建完成(可以访问当前this实例)
-  created() {
-    if (this.exerciseContent) {
-      this.content = this.exerciseContent.content_exercise;
-      this.title = this.exerciseContent.content_title;
-      this.typeValue = this.exerciseContent.content_exercise
-        ? JSON.parse(this.exerciseContent.content_exercise).type
-        : 'select';
-      this.exercise_id = this.exerciseContent.exercise_id;
-    }
+  watch: {
+    exerciseContent: {
+      handler(newVal) {
+        if (newVal) {
+          this.content = this.exerciseContent.content_exercise;
+          this.title = this.exerciseContent.content_title;
+          this.typeValue = this.exerciseContent.content_exercise
+            ? JSON.parse(this.exerciseContent.content_exercise).type
+            : 'select';
+          this.exercise_id = this.exerciseContent.exercise_id;
+
+          this.$nextTick(() => {
+            this.$refs.setting.setSetting(this.$refs.component.data.property, {});
+          });
+        } else {
+          this.content = getSelectData();
+          this.title = '';
+          this.typeValue = 'select';
+          this.exercise_id = '';
+        }
+      },
+      immediate: true,
+      deep: true,
+    },
   },
   },
   methods: {
   methods: {
     componentMove() {},
     componentMove() {},
+    addAnswerAndAnalysis(type) {
+      this.$refs.component.addAnswerAndAnalysis(type);
+    },
     submitAdd() {
     submitAdd() {
       this.loading = true;
       this.loading = true;
       let data = {
       let data = {
@@ -112,9 +183,14 @@ export default {
           if (res.status === 1) {
           if (res.status === 1) {
             this.$message.success('操作成功');
             this.$message.success('操作成功');
             if (this.exercise_id) {
             if (this.exercise_id) {
-              this.$emit('submitAdd');
-            } else {
               this.$emit('submitAdd', res.exercise_id, this.typeList.find((p) => p.value === this.typeValue).label);
               this.$emit('submitAdd', res.exercise_id, this.typeList.find((p) => p.value === this.typeValue).label);
+            } else {
+              this.$emit(
+                'submitAdd',
+                res.exercise_id,
+                this.typeList.find((p) => p.value === this.typeValue).label,
+                'add',
+              );
             }
             }
           }
           }
         })
         })
@@ -158,7 +234,8 @@ export default {
 }
 }
 </style>
 </style>
 <style lang="scss">
 <style lang="scss">
-.tox-tinymce-aux {
+.tox-tinymce-aux,
+.tox-tinymce-inline {
   z-index: 9999 !important;
   z-index: 9999 !important;
 }
 }
 </style>
 </style>

+ 13 - 34
src/views/book/courseware/create/components/question/video_interaction/VideoInteraction.vue

@@ -33,41 +33,11 @@
           </li>
           </li>
         </ul>
         </ul>
       </div>
       </div>
-      <!-- 上传 -->
-      <!-- <el-dialog
-        :visible.sync="sourceAddFlag"
-        width="500px"
-        append-to-body
-        :show-close="true"
-        title="上传文件"
-        :close-on-click-modal="false"
-        class="module-content"
-      >
-        <UploadFile
-          v-if="sourceAddFlag"
-          key="upload_resources"
-          type="video_interaction_file"
-          :total-size="20000"
-          :file-list="file_list"
-          :file-id-list="file_id_list"
-          :label-text="''"
-          :accept-file-type="'*'"
-          :icon-class="''"
-          :limit="1"
-          :single-size="2000"
-          :upload-tip="''"
-          @updateFileList="updateFileLists"
-        />
-        <footer style="text-align: right">
-          <el-button @click="handleCancle">取 消</el-button>
-          <el-button :loading="loading" type="primary" @click="submitAdd">确 定</el-button>
-        </footer>
-      </el-dialog> -->
       <!-- 编辑练习题题目 -->
       <!-- 编辑练习题题目 -->
       <el-dialog
       <el-dialog
         v-if="exerciseFlag"
         v-if="exerciseFlag"
         :visible.sync="exerciseFlag"
         :visible.sync="exerciseFlag"
-        width="90%"
+        width="1400px"
         append-to-body
         append-to-body
         :show-close="true"
         :show-close="true"
         title="练习题"
         title="练习题"
@@ -102,6 +72,7 @@ import ExerciseAdd from './ExerciseAdd.vue';
 
 
 import { getVideoInteractionData } from '@/views/book/courseware/data/videoInteraction';
 import { getVideoInteractionData } from '@/views/book/courseware/data/videoInteraction';
 import { GetCoursewareExercise } from '@/api/book';
 import { GetCoursewareExercise } from '@/api/book';
+import { getSelectData } from '@/views/book/courseware/data/select';
 
 
 export default {
 export default {
   name: 'VideoInteractionPage',
   name: 'VideoInteractionPage',
@@ -182,14 +153,17 @@ export default {
       document.getElementById('interaction-video').play();
       document.getElementById('interaction-video').play();
     },
     },
     // 确定新增资源
     // 确定新增资源
-    submitAdd(id, type) {
-      if (id) {
+    submitAdd(id, type, isAdd) {
+      if (isAdd) {
         this.data.file_info_list.push({
         this.data.file_info_list.push({
           currentTime: this.currentTime,
           currentTime: this.currentTime,
           file_name: type,
           file_name: type,
           id,
           id,
         });
         });
         this.file_id_list.push(id);
         this.file_id_list.push(id);
+      } else {
+        let index = this.data.file_info_list.findIndex((item) => item.id === id);
+        this.data.file_info_list[index].file_name = type;
       }
       }
 
 
       this.exerciseFlag = false;
       this.exerciseFlag = false;
@@ -203,7 +177,12 @@ export default {
       this.file_list = [];
       this.file_list = [];
       this.file_id_list = [];
       this.file_id_list = [];
       // this.sourceAddFlag = true;
       // this.sourceAddFlag = true;
-      this.exerciseContent = null;
+      let selectData = getSelectData();
+      this.exerciseContent = {
+        content_exercise: JSON.stringify(selectData),
+        content_title: '',
+        exercise_id: '',
+      };
       this.exerciseFlag = true;
       this.exerciseFlag = true;
     },
     },
     // 删除文件
     // 删除文件