소스 검색

组件添加批注

natasha 3 일 전
부모
커밋
b311833695

+ 43 - 4
src/components/CommonPreview.vue

@@ -15,14 +15,22 @@
     </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)"
+            @computeScroll="computeScroll"
+            @addRemark="addRemark"
+            ref="courserware"
+            v-if="courseware_info.book_name"
+          />
         </main>
       </div>
       <div v-if="isShowAudit" class="remark-list">
@@ -70,6 +78,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,
@@ -140,6 +149,12 @@ export default {
       visible: false,
       remark_content: '',
       submit_loading: false,
+      isTrue,
+      menuPosition: {
+        x: -1,
+        y: -1,
+        componentId: 'WHOLE',
+      },
     };
   },
   created() {
@@ -223,9 +238,22 @@ export default {
         this.remark_list = remark_list;
       });
     },
-    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 +264,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 +292,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_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>

+ 83 - 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,29 @@
           <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>
             </template>
           </div>
         </template>
@@ -63,6 +76,10 @@ export default {
       type: Array,
       required: true,
     },
+    canRemark: {
+      type: Boolean,
+      default: false,
+    },
   },
   data() {
     return {
@@ -70,6 +87,21 @@ 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,
     };
   },
   methods: {
@@ -199,12 +231,42 @@ 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,
+      );
+    },
   },
 };
 </script>
 
 <style lang="scss" scoped>
 .courserware {
+  position: relative;
   display: flex;
   flex-direction: column;
   row-gap: 6px;
@@ -227,5 +289,15 @@ export default {
       overflow: hidden;
     }
   }
+
+  .custom-context-menu {
+    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,