Browse Source

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

dusenyao 2 days ago
parent
commit
4c64eeea98

+ 56 - 4
src/components/CommonPreview.vue

@@ -15,14 +15,24 @@
     </div>
 
     <div class="audit-content">
-      <div class="main-container">
+      <div class="main-container" ref="previewMain">
         <main :class="['preview-main', { 'no-audit': !isShowAudit }]">
           <span class="title">
             <SvgIcon icon-class="menu-2" size="24" />
             <span>{{ courseware_info.name_path }}</span>
           </span>
-
-          <CoursewarePreview :data="data" :component-list="component_list" :background="background" />
+          <CoursewarePreview
+            :data="data"
+            :component-list="component_list"
+            :background="background"
+            :can-remark="isTrue(courseware_info.is_my_audit_task) && isTrue(courseware_info.is_can_add_audit_remark)"
+            :show-remark="isShowAudit"
+            :component-remark-obj="remark_list_obj"
+            @computeScroll="computeScroll"
+            @addRemark="addRemark"
+            ref="courserware"
+            v-if="courseware_info.book_name"
+          />
         </main>
       </div>
       <div v-if="isShowAudit" class="remark-list">
@@ -70,6 +80,7 @@
 import CoursewarePreview from '@/views/book/courseware/preview/CoursewarePreview.vue';
 import MenuPopover from '@/views/personal_workbench/common/MenuPopover.vue';
 import RichText from '@/components/RichText.vue';
+import { isTrue } from '@/utils/common';
 
 import {
   GetBookCoursewareInfo,
@@ -137,9 +148,16 @@ export default {
       data: { row_list: [] },
       component_list: [],
       remark_list: [],
+      remark_list_obj: {}, // 存放转为以组件为对象的数组
       visible: false,
       remark_content: '',
       submit_loading: false,
+      isTrue,
+      menuPosition: {
+        x: -1,
+        y: -1,
+        componentId: 'WHOLE',
+      },
     };
   },
   created() {
@@ -217,15 +235,38 @@ export default {
     // 审校批注列表
     getCoursewareAuditRemarkList(id) {
       this.remark_list = [];
+      this.remark_list_obj = {};
       GetCoursewareAuditRemarkList({
         courseware_id: id,
       }).then(({ remark_list }) => {
         this.remark_list = remark_list;
+        remark_list.forEach((item) => {
+          // 组件的审批
+          if (item.component_id !== 'WHOLE') {
+            if (!this.remark_list_obj[item.component_id]) {
+              this.remark_list_obj[item.component_id] = [];
+            }
+            this.remark_list_obj[item.component_id].push(item);
+          }
+        });
       });
     },
-    addRemark() {
+    addRemark(selectNode, x, y, componentId) {
       this.remark_content = '';
       this.visible = true;
+      if (selectNode) {
+        this.menuPosition = {
+          x: x,
+          y: y,
+          componentId: componentId,
+        };
+      } else {
+        this.menuPosition = {
+          x: -1,
+          y: -1,
+          componentId: 'WHOLE',
+        };
+      }
     },
     dialogClose() {
       this.visible = false;
@@ -236,6 +277,9 @@ export default {
       AddCoursewareAuditRemark({
         courseware_id: id || this.id,
         content: this.remark_content,
+        component_id: this.menuPosition.componentId,
+        position_x: this.menuPosition.x,
+        position_y: this.menuPosition.y,
       })
         .then(() => {
           this.submit_loading = false;
@@ -261,6 +305,14 @@ export default {
         })
         .catch(() => {});
     },
+    // 计算previewMain滑动距离
+    computeScroll() {
+      this.$refs.courserware.handleResult(
+        this.$refs.previewMain.scrollTop,
+        this.$refs.previewMain.scrollLeft,
+        this.select_node,
+      );
+    },
   },
 };
 </script>

+ 1 - 0
src/icons/svg/icon-info.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1750763248804" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4816" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M606.8 895.7H214c-47.9 0-86.8-38.9-86.8-86.8V215c0-47.9 38.8-86.8 86.7-86.8h593.9c23-0.1 45.1 9.1 61.4 25.4 16.4 16.4 25.3 38.2 25.4 61.4l-1.4 392.8v0.1c0.2 17.7 14.6 31.9 32.3 31.7 17.4-0.2 31.5-14.2 31.7-31.6l1.3-392.9v-0.1c0-83.2-67.4-150.7-150.6-150.7H214c-83.1-0.1-150.6 67.3-150.6 150.4V808.9c-0.1 83.1 67.2 150.6 150.3 150.7h393.2c17.6 0 31.9-14.3 31.9-31.9 0-17.7-14.3-32-32-32z" p-id="4817"></path><path d="M949.3 905l-122-122c61.5-86.4 41.3-206.4-45.2-267.9s-206.4-41.3-267.9 45.2c-61.5 86.4-41.3 206.4 45.2 267.9 66.7 47.4 156 47.4 222.7 0l122 122c12.5 12.5 32.7 12.5 45.2 0s12.5-32.7 0-45.2zM671 799.8c-70.7 0-127.9-57.3-127.9-127.9s57.2-128 127.9-128 127.9 57.3 127.9 127.9c0 33.9-13.5 66.5-37.5 90.5-23.9 24.1-56.5 37.6-90.4 37.5z m95.9-447.5c17.6 0 31.9-14.4 31.9-32s-14.3-32.1-31.9-32.1l-511.6-0.2c-17.7 0-31.9 14.3-31.9 32 0 17.6 14.3 32.1 31.9 32.1l511.6 0.2zM447.1 544c17.6 0 31.9-14.3 31.9-31.9 0-17.6-14.3-31.9-31.9-31.9H255.3c-17.6 0-31.9 14.3-31.9 31.9s14.3 31.9 31.9 31.9h191.8zM255.3 671.8c-17.6 0-31.9 14.3-31.9 31.9 0 17.6 14.3 31.9 31.9 31.9h127.9c17.6 0.2 32.1-14 32.3-31.6 0.2-17.6-14-32.1-31.6-32.3h-0.7l-127.9 0.1z" p-id="4818"></path></svg>

+ 1 - 0
src/icons/svg/icon-publish.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1750756332954" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6075" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M879.542857 780.251429h-73.142857v-73.142858a27.428571 27.428571 0 1 0-54.857143 0v73.142858h-73.142857a27.428571 27.428571 0 0 0 0 54.857142h73.142857v73.142858a27.428571 27.428571 0 0 0 54.857143 0v-73.142858h73.142857a27.428571 27.428571 0 1 0 0-54.857142zM894.354286 351.634286a36.571429 36.571429 0 0 0-2.011429-49.737143L687.725714 98.742857a36.571429 36.571429 0 0 0-49.371428-2.011428L317.805714 368.822857l-69.485714-3.108571a37.668571 37.668571 0 0 0-27.611429 10.605714L194.194286 402.285714a36.571429 36.571429 0 0 0 0 51.931429l144.091428 143.177143L128 806.948571A36.571429 36.571429 0 0 0 128 859.428571a36.571429 36.571429 0 0 0 25.965714 10.788572A36.571429 36.571429 0 0 0 179.382857 859.428571l210.834286-210.102857 144.457143 143.725715a36.571429 36.571429 0 0 0 51.565714 0l26.697143-26.514286a36.571429 36.571429 0 0 0 10.788571-27.794286l-3.291428-68.754286z m-338.834286 281.6A36.571429 36.571429 0 0 0 546.742857 658.285714l1.828572 45.714286-132.754286-132.022857-132.571429-131.474286 45.897143 2.011429a37.668571 37.668571 0 0 0 25.417143-8.594286L660.114286 174.262857 816.64 329.142857z" fill="#040000" p-id="6076"></path></svg>

+ 2 - 11
src/views/book/courseware/create/components/base/upload_preview/UploadPreview.vue

@@ -17,21 +17,13 @@
         :limit="100"
         @updateFileList="updateFileList"
       />
-      <el-form :model="data" label-width="72px" label-position="left">
-        <el-form-item label="下载">
-          <el-radio-group v-model="data.is_enable_download">
-            <el-radio v-for="{ value, label } in switchOption" :key="value" :label="value">
-              {{ label }}
-            </el-radio>
-          </el-radio-group>
-        </el-form-item>
-      </el-form>
+      <el-form :model="data" label-width="72px" label-position="left"> </el-form>
     </template>
   </ModuleBase>
 </template>
 
 <script>
-import { getUploadPreviewData, switchOption } from '@/views/book/courseware/data/uploadPreview';
+import { getUploadPreviewData } from '@/views/book/courseware/data/uploadPreview';
 import ModuleMixin from '../../common/ModuleMixin';
 import UploadFile from '../common/UploadFile.vue';
 
@@ -42,7 +34,6 @@ export default {
   data() {
     return {
       data: getUploadPreviewData(),
-      switchOption,
       labelText: '文件',
       acceptFileType: '.txt,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.mp3,.wma,.mp4,.mov,.zip,.rar',
       uploadTip: '支持上传txt,pdf,doc,excel,ppt,mp3,wma,mp4,mov,zip,rar等格式文件,单个文件最大500MB',

+ 10 - 4
src/views/book/courseware/create/components/base/upload_preview/UploadRreviewSetting.vue

@@ -2,23 +2,29 @@
   <div>
     <el-form :model="property" :label-position="labelPosition" label-width="72px">
       <SerailNumber :property="property" />
+      <el-form-item label="下载">
+        <el-radio-group v-model="property.is_enable_download">
+          <el-radio v-for="{ value, label } in switchOption" :key="value" :label="value">
+            {{ label }}
+          </el-radio>
+        </el-radio-group>
+      </el-form-item>
     </el-form>
   </div>
 </template>
 
 <script>
 import SettingMixin from '@/views/book/courseware/create/components/common/SettingMixin';
-import { viewMethodList, snGenerationMethodList } from '@/views/book/courseware/data/common';
-import { getPictureProperty } from '@/views/book/courseware/data/picture';
+import { getUploadPreviewProperty, switchOption } from '@/views/book/courseware/data/uploadPreview';
 
 export default {
   name: 'PictureSetting',
   mixins: [SettingMixin],
   data() {
     return {
-      viewMethodList,
+      switchOption,
       labelPosition: 'left',
-      property: getPictureProperty(),
+      property: getUploadPreviewProperty(),
     };
   },
   methods: {},

+ 1 - 1
src/views/book/courseware/create/components/question/judge/Judge.vue

@@ -23,7 +23,7 @@
             <SvgIcon v-if="option_type === option_type_list[1].value" icon-class="cross" size="8" />
             <SvgIcon v-if="option_type === option_type_list[2].value" icon-class="circle" size="10" />
           </div>
-          <span class="delete" @click="deleteOption">
+          <span v-if="i > 0" class="delete" @click="deleteOption">
             <SvgIcon icon-class="delete-2" width="12" height="12" />
           </span>
         </li>

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

@@ -14,7 +14,7 @@ export function getDescribeProperty() {
     sn_display_mode: displayList[0].value,
     sn_style: serialNumberStyleList[0].value,
     sn_background_color: '#ea3232', // 序号背景色
-    view_pinyin: 'true', // 显示拼音
+    view_pinyin: 'false', // 显示拼音
     pinyin_position: pinyinPositionList[0].value, // top bottom
     is_first_sentence_first_hz_pinyin_first_char_upper_case: 'true', // 句首大写
   };

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

@@ -14,7 +14,7 @@ export function getStemProperty() {
     sn_display_mode: displayList[0].value,
     sn_style: serialNumberStyleList[0].value,
     sn_background_color: '#ea3232', // 序号背景色
-    view_pinyin: 'true', // 显示拼音
+    view_pinyin: 'false', // 显示拼音
     pinyin_position: pinyinPositionList[0].value, // top bottom
     is_first_sentence_first_hz_pinyin_first_char_upper_case: 'true', // 句首大写
   };

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

@@ -12,6 +12,7 @@ export function getUploadPreviewProperty() {
     sn_type: serialNumberTypeList[0].value, // 序号类型:letter字母 number数字  capital大写字母 bracket_number括号数字
     sn_position: serialNumberPositionList[3].value, // 序号位置:top-start top top-end,left-start left left-end等
     sn_display_mode: displayList[0].value,
+    is_enable_download: switchOption[0].value,
   };
 }
 
@@ -28,6 +29,5 @@ export function getUploadPreviewData() {
     file_id_list: [], // 文件 id['20032-121212', '20032-121216']
     // 内容中包含的文件列表,
     file_list: [],
-    is_enable_download: switchOption[0].value,
   };
 }

+ 125 - 11
src/views/book/courseware/preview/CoursewarePreview.vue

@@ -1,6 +1,7 @@
 <template>
   <div
     class="courserware"
+    ref="courserware"
     :style="[
       {
         backgroundImage: background.background_image_url ? `url(${background.background_image_url})` : '',
@@ -20,17 +21,52 @@
           <div :key="j" :class="['col', `col-${i}-${j}`]" :style="computedColStyle(col)">
             <!-- 网格 -->
             <template v-for="(grid, k) in col.grid_list">
-              <component
-                :is="previewComponentList[grid.type]"
-                ref="preview"
-                :key="k"
-                :content="computedColContent(grid.id)"
-                :class="[grid.id]"
-                :style="{
-                  gridArea: grid.grid_area,
-                  height: grid.height,
-                }"
-              />
+              <div @contextmenu.prevent="handleContextMenu($event, grid.id)" :key="k">
+                <component
+                  :is="previewComponentList[grid.type]"
+                  ref="preview"
+                  :content="computedColContent(grid.id)"
+                  :class="[grid.id]"
+                  :style="{
+                    gridArea: grid.grid_area,
+                    height: grid.height,
+                  }"
+                />
+              </div>
+
+              <div
+                v-if="showMenu && componentId === grid.id"
+                class="custom-context-menu"
+                :style="{ left: menuPosition.x + 'px', top: menuPosition.y + 'px' }"
+                @click="handleMenuItemClick"
+                :key="'menu' + grid.id + k"
+              >
+                <SvgIcon icon-class="icon-publish" size="24" />
+                添加批注
+              </div>
+              <div
+                :key="'show' + grid.id + k"
+                v-if="showRemark && Object.keys(componentRemarkObj).length !== 0 && componentRemarkObj[grid.id]"
+              >
+                <el-popover
+                  v-for="(items, indexs) in componentRemarkObj[grid.id]"
+                  :key="indexs"
+                  placement="bottom"
+                  width="200"
+                  trigger="click"
+                >
+                  <div v-html="items.content"></div>
+                  <template #reference>
+                    <SvgIcon
+                      icon-class="icon-info"
+                      size="24"
+                      class="remark-info"
+                      slot="reference"
+                      :style="{ left: items.position_x - 12 + 'px', top: items.position_y - 12 + 'px' }"
+                    />
+                  </template>
+                </el-popover>
+              </div>
             </template>
           </div>
         </template>
@@ -63,6 +99,18 @@ export default {
       type: Array,
       required: true,
     },
+    canRemark: {
+      type: Boolean,
+      default: false,
+    },
+    showRemark: {
+      type: Boolean,
+      default: false,
+    },
+    componentRemarkObj: {
+      type: Object,
+      default: () => ({}),
+    },
   },
   data() {
     return {
@@ -70,7 +118,23 @@ export default {
       bookInfo: {
         theme_color: '',
       },
+      showMenu: false,
+      divPosition: {
+        left: 0,
+        top: 0,
+      }, // courserware盒子原始距离页面顶部和左边的距离
+      menuPosition: { x: 0, y: 0, select_node: '' }, // 用于存储菜单的位置
+      componentId: '', // 添加批注的组件id
+    };
+  },
+  mounted() {
+    const element = this.$refs.courserware;
+    const rect = element.getBoundingClientRect();
+    this.divPosition = {
+      left: rect.left,
+      top: rect.top,
     };
+    window.addEventListener('mousedown', this.handleMouseDown);
   },
   methods: {
     /**
@@ -199,12 +263,51 @@ export default {
         gridTemplateRows,
       };
     },
+    handleContextMenu(event, id) {
+      if (this.canRemark) {
+        event.preventDefault(); // 阻止默认的上下文菜单显示
+        this.menuPosition = {
+          x: event.clientX - this.divPosition.left,
+          y: event.clientY - this.divPosition.top,
+        }; // 设置菜单位置
+        this.componentId = id;
+        this.$emit('computeScroll');
+      }
+    },
+    handleResult(top, left, select_node) {
+      this.menuPosition = {
+        x: this.menuPosition.x + left,
+        y: this.menuPosition.y + top,
+        select_node: select_node,
+      }; // 设置菜单位置
+      this.showMenu = true; // 显示菜单
+    },
+    handleMenuItemClick() {
+      this.showMenu = false; // 隐藏菜单
+      this.$emit(
+        'addRemark',
+        this.menuPosition.select_node,
+        this.menuPosition.x,
+        this.menuPosition.y,
+        this.componentId,
+      );
+    },
+    handleMouseDown(event) {
+      if (event.button === 0) {
+        // 0 表示左键
+        this.showMenu = false;
+      }
+    },
+  },
+  beforeDestroy() {
+    window.removeEventListener('mousedown', this.handleMouseDown);
   },
 };
 </script>
 
 <style lang="scss" scoped>
 .courserware {
+  position: relative;
   display: flex;
   flex-direction: column;
   row-gap: 6px;
@@ -227,5 +330,16 @@ export default {
       overflow: hidden;
     }
   }
+
+  .custom-context-menu,
+  .remark-info {
+    position: absolute;
+    z-index: 999;
+    display: flex;
+    gap: 3px;
+    align-items: center;
+    font-size: 14px;
+    cursor: pointer;
+  }
 }
 </style>

+ 2 - 2
src/views/book/courseware/preview/components/image_text/ImageTextPreview.vue

@@ -27,7 +27,7 @@
     >
       <div
         v-for="(itemP, indexP) in data.text_list"
-        :key="indexP"
+        :key="'text' + indexP"
         :class="['position-item', mageazineDetailIndex === indexP ? 'active' : '']"
         :style="{
           width: itemP.width,
@@ -39,7 +39,7 @@
       ></div>
       <div
         v-for="(itemP, indexP) in data.input_list"
-        :key="indexP"
+        :key="'input' + indexP"
         :class="['position-item position-item-input', 'active']"
         :style="{
           width: itemP.width,