Browse Source

组件拖拽移动

dsy 1 week ago
parent
commit
8de9b70fde

+ 107 - 7
src/views/book/courseware/create/components/CreateCanvas.vue

@@ -159,6 +159,7 @@ export default {
       getBookUnifiedAttr: () => this.book_unified_attrib,
       getBookUnifiedAttr: () => this.book_unified_attrib,
       getTitleList: () => this.title_list,
       getTitleList: () => this.title_list,
       getProjectResourcePopedom: () => this.projectResourcePopedom,
       getProjectResourcePopedom: () => this.projectResourcePopedom,
+      moveComponentDragStart: this.dragStart,
     };
     };
   },
   },
   props: {
   props: {
@@ -190,6 +191,8 @@ export default {
       content_group_row_list: [], // 行分组id列表
       content_group_row_list: [], // 行分组id列表
       gridBorderColorList: ['#3fc7cc', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#d863ff', '#724fff'], // 网格边框颜色列表
       gridBorderColorList: ['#3fc7cc', '#67C23A', '#E6A23C', '#F56C6C', '#909399', '#d863ff', '#724fff'], // 网格边框颜色列表
       curType: 'divider',
       curType: 'divider',
+      curParams: {}, // 当前组件参数
+      scrollInterval: null, // 滚动定时器
       componentList,
       componentList,
       curRow: -2,
       curRow: -2,
       curCol: -1,
       curCol: -1,
@@ -947,18 +950,20 @@ export default {
     /**
     /**
      * 拖拽开始
      * 拖拽开始
      * 用点击模拟拖拽
      * 用点击模拟拖拽
-     * @param {MouseEvent} event
-     * @param {string} type
+     * @param {MouseEvent} event 鼠标事件
+     * @param {string} type 组件类型
+     * @param {object} params 组件参数
      */
      */
-    dragStart(event, type) {
+    dragStart(event, type, params = {}) {
       // 获取鼠标位置
       // 获取鼠标位置
       const { clientX, clientY } = event;
       const { clientX, clientY } = event;
       document.body.style.userSelect = 'none'; // 禁止选中文本
       document.body.style.userSelect = 'none'; // 禁止选中文本
       this.drag.dragging = true;
       this.drag.dragging = true;
       this.curType = type;
       this.curType = type;
+      this.curParams = params;
       // 在鼠标位置创建一个拖拽元素
       // 在鼠标位置创建一个拖拽元素
       const dragging = document.createElement('div');
       const dragging = document.createElement('div');
-      dragging.className = 'canvas-dragging';
+      dragging.className = `canvas-dragging${type === 'component' ? ' component-dragging' : ''}`;
       this.drag.clientX = clientX;
       this.drag.clientX = clientX;
       this.drag.clientY = clientY;
       this.drag.clientY = clientY;
       document.body.appendChild(dragging);
       document.body.appendChild(dragging);
@@ -980,6 +985,31 @@ export default {
       this.enterCanvas = isInsideCanvas;
       this.enterCanvas = isInsideCanvas;
       if (!isInsideCanvas) return;
       if (!isInsideCanvas) return;
 
 
+      // 当移动组件时,如果鼠标位置距离画布上下边距小于 20px,则自动滚动画布,做一个定时器每隔 100ms 判断一次,直到鼠标位置距离边距大于 20px 或者离开画布
+      if (this.curType === 'component') {
+        const middleDom = document.querySelector('.create-middle');
+        // 获取 middleDom 相对于 event 的位置
+        const middleRect = middleDom.getBoundingClientRect();
+        const middleHeight = middleRect.height;
+        const offsetTop = clientY - middleRect.top;
+        const offsetBottom = middleRect.bottom - clientY;
+        if (this.scrollInterval) {
+          clearInterval(this.scrollInterval);
+        }
+        if (offsetTop < 20) {
+          this.scrollInterval = setInterval(() => {
+            middleDom.scrollTop = Math.max(0, middleDom.scrollTop - 10);
+          }, 10);
+        } else if (offsetBottom < 20) {
+          this.scrollInterval = setInterval(() => {
+            middleDom.scrollTop = Math.min(middleDom.scrollHeight - middleHeight, middleDom.scrollTop + 10);
+          }, 10);
+        } else {
+          clearInterval(this.scrollInterval);
+          this.scrollInterval = null;
+        }
+      }
+
       this.showRecentLine(clientX, clientY);
       this.showRecentLine(clientX, clientY);
     },
     },
     /**
     /**
@@ -1029,23 +1059,88 @@ export default {
         this.drag.dragging = false;
         this.drag.dragging = false;
       }
       }
 
 
+      if (this.scrollInterval) {
+        clearInterval(this.scrollInterval);
+        this.scrollInterval = null;
+      }
+
       if (!this.isEdit) return;
       if (!this.isEdit) return;
 
 
       if (this.enterCanvas) {
       if (this.enterCanvas) {
+        if (this.curType === 'component') {
+          this.handleComponentMove();
+        }
+
         if (this.curRow >= -1 && this.curCol <= -1) {
         if (this.curRow >= -1 && this.curCol <= -1) {
           this.calculateRowInsertedObject();
           this.calculateRowInsertedObject();
         }
         }
 
 
         if (this.curRow >= -1 && this.curCol > -1 && this.curGrid <= -1) {
         if (this.curRow >= -1 && this.curCol > -1 && this.curGrid <= -1) {
+          // 当拖拽组件和放置位置在同一列内,且行内组件数量为 1,将 curRow 减 1,才能正确插入
+          if ('col' in this.curParams) {
+            const { col, row, rowNum } = this.curParams;
+            if (col === this.curCol && row === this.curRow && rowNum === 1) {
+              this.curRow = Math.max(0, this.curRow - 1);
+              this.calculateRowInsertedObject();
+              return;
+            }
+          }
           this.calculateColObject();
           this.calculateColObject();
         }
         }
 
 
         if (this.curRow >= -1 && this.curCol > -1 && this.curGrid > -1) {
         if (this.curRow >= -1 && this.curCol > -1 && this.curGrid > -1) {
+          if ('col' in this.curParams) {
+            const { col, row, grid } = this.curParams;
+
+            // 当拖拽组件和放置位置在同一列内,将 curGrid 减 1,对应上面的删除组件,才能正确插入
+            if (col === this.curCol && row === this.curRow && grid < this.curGrid) {
+              this.curGrid = Math.max(0, this.curGrid - 1);
+            }
+
+            // 当拖拽组件和放置位置在同一列内,且行内组件数量为 1,将 curRow 减 1,才能正确插入
+            if (col === this.curCol && row === this.curRow && grid === this.curGrid) {
+              this.curRow = Math.max(0, this.curRow - 1);
+              this.calculateRowInsertedObject();
+              return;
+            }
+          }
+
           this.calculateGridObject();
           this.calculateGridObject();
         }
         }
       }
       }
+      this.curType = '';
+      this.curParams = {};
       this.enterCanvas = false;
       this.enterCanvas = false;
     },
     },
+    /**
+     * 处理组件移动
+     * 组件拖拽移动后,先删除组件,再根据拖拽位置计算插入行、列、格子
+     */
+    handleComponentMove() {
+      const component = this.findChildComponentByKey(`grid-${this.curParams.id}`);
+      const data = component?.data;
+      this.curType = data?.type || 'select';
+      let col = component?.$attrs['data-col'];
+      let row = component?.$attrs['data-row'];
+      let grid = component?.$attrs['data-grid'];
+      let rowNum = 0; // 当前行组件数量
+      this.data.row_list[row].col_list.forEach((item) => {
+        rowNum += item.grid_list.length;
+      });
+
+      this.curParams = {
+        ...this.curParams,
+        col,
+        row,
+        grid,
+        rowNum,
+      };
+      this.deleteComponent(this.curParams.id);
+    },
+    /**
+     * 计算行样式
+     * @param {number} i 行索引
+     */
     computedRowStyle(i) {
     computedRowStyle(i) {
       let row = this.data.row_list[i];
       let row = this.data.row_list[i];
       let col = row.col_list;
       let col = row.col_list;
@@ -1071,7 +1166,7 @@ export default {
      * 计算网格插入的对象
      * 计算网格插入的对象
      */
      */
     calculateGridObject() {
     calculateGridObject() {
-      const id = `ID-${getRandomNumber(12, true)}`;
+      const id = this.curParams?.id || `ID-${getRandomNumber(12, true)}`;
       const letter = `L${getRandomNumber(6, true)}`;
       const letter = `L${getRandomNumber(6, true)}`;
 
 
       let row = this.data.row_list[this.curRow];
       let row = this.data.row_list[this.curRow];
@@ -1127,7 +1222,7 @@ export default {
      * 计算列插入的对象
      * 计算列插入的对象
      */
      */
     calculateColObject() {
     calculateColObject() {
-      const id = `ID-${getRandomNumber(12, true)}`;
+      const id = this.curParams?.id || `ID-${getRandomNumber(12, true)}`;
       const letter = `L${getRandomNumber(6, true)}`;
       const letter = `L${getRandomNumber(6, true)}`;
       const col_id = `C${getRandomNumber(8, true)}`;
       const col_id = `C${getRandomNumber(8, true)}`;
 
 
@@ -1164,7 +1259,7 @@ export default {
      * 计算行插入的对象
      * 计算行插入的对象
      */
      */
     calculateRowInsertedObject() {
     calculateRowInsertedObject() {
-      const id = `ID-${getRandomNumber(12, true)}`;
+      const id = this.curParams?.id || `ID-${getRandomNumber(12, true)}`;
       const letter = `L${getRandomNumber(6, true)}`;
       const letter = `L${getRandomNumber(6, true)}`;
       const row_id = `R${getRandomNumber(6, true)}`;
       const row_id = `R${getRandomNumber(6, true)}`;
       const col_id = `C${getRandomNumber(8, true)}`;
       const col_id = `C${getRandomNumber(8, true)}`;
@@ -1655,5 +1750,10 @@ export default {
   border-radius: 4px;
   border-radius: 4px;
   opacity: 0.5;
   opacity: 0.5;
   transform: translate(-40%, -40%);
   transform: translate(-40%, -40%);
+
+  &.component-dragging {
+    background-color: #f9ffe0;
+    border-color: #f9ffe0;
+  }
 }
 }
 </style>
 </style>

+ 11 - 1
src/views/book/courseware/create/components/common/ModuleBase.vue

@@ -12,7 +12,7 @@
     ></div>
     ></div>
     <div class="module" draggable="false">
     <div class="module" draggable="false">
       <div class="module-top">
       <div class="module-top">
-        <span class="title">{{ componentNameList[type] }}</span>
+        <span class="title" @mousedown="dragComponentStart($event)">{{ componentNameList[type] }}</span>
         <div class="module-icon">
         <div class="module-icon">
           <el-checkbox v-model="checked" />
           <el-checkbox v-model="checked" />
           <span><SvgIcon icon-class="copy" size="10" @click="copyComponent" /></span>
           <span><SvgIcon icon-class="copy" size="10" @click="copyComponent" /></span>
@@ -52,6 +52,7 @@ export default {
     'handleComponentMove',
     'handleComponentMove',
     'borderColor',
     'borderColor',
     'copyComponent',
     'copyComponent',
+    'moveComponentDragStart',
   ],
   ],
   props: {
   props: {
     type: {
     type: {
@@ -128,6 +129,14 @@ export default {
 
 
       document.body.style.cursor = 'auto';
       document.body.style.cursor = 'auto';
     },
     },
+    /**
+     * 拖拽组件移动开始
+     * @param {MouseEvent} event 鼠标事件
+     */
+    dragComponentStart(event) {
+      event.stopPropagation();
+      this.moveComponentDragStart(event, 'component', { id: this.id });
+    },
     clickWrapper() {
     clickWrapper() {
       this.showSetting();
       this.showSetting();
     },
     },
@@ -179,6 +188,7 @@ export default {
       display: flex;
       display: flex;
       justify-content: space-between;
       justify-content: space-between;
       margin-bottom: 3px;
       margin-bottom: 3px;
+      cursor: move;
 
 
       .title {
       .title {
         font-size: 12px;
         font-size: 12px;

+ 5 - 0
src/views/book/courseware/create/index.vue

@@ -120,6 +120,11 @@ export default {
     back() {
     back() {
       this.$emit('goBackPreview');
       this.$emit('goBackPreview');
     },
     },
+    /**
+     * 拖拽开始
+     * @param {MouseEvent} event 鼠标事件
+     * @param {string} type 组件类型
+     */
     dragStart(event, type) {
     dragStart(event, type) {
       this.$refs.createCanvas.dragStart(event, type);
       this.$refs.createCanvas.dragStart(event, type);
     },
     },