Browse Source

perf: mp/menu使用vuedraggable替换拖动的原生实现

(cherry picked from commit ebacbbb9cf69bd4358367bef3d9de9bcdfaad754)
dhb52 2 years ago
parent
commit
4601bd3428
1 changed files with 80 additions and 64 deletions
  1. 80 64
      src/views/mp/menu/components/MenuPreviewer.vue

+ 80 - 64
src/views/mp/menu/components/MenuPreviewer.vue

@@ -1,41 +1,55 @@
 <template>
-  <div class="menu_bottom" v-for="(parent, x) of menuList" :key="x">
-    <!-- 一级菜单 -->
-    <div
-      @click="menuClicked(parent, x)"
-      class="menu_item"
-      draggable="true"
-      @dragstart="onDragStart(DragType.Parent, x)"
-      @dragenter.prevent="onDragEnter(DragType.Parent, x)"
-      :class="{ active: props.activeIndex === `${x}` }"
-    >
-      <Icon icon="ep:fold" color="black" />{{ parent.name }}
-    </div>
-    <!-- 以下为二级菜单-->
-    <div class="submenu" v-if="parentIndex === x && parent.children">
-      <div class="subtitle menu_bottom" v-for="(child, y) in parent.children" :key="y">
+  <draggable
+    v-model="menuList"
+    item-key="id"
+    ghost-class="draggable-ghost"
+    :animation="400"
+    @end="onDragEnd"
+  >
+    <template #item="{ element: parent, index: x }">
+      <div class="menu_bottom">
+        <!-- 一级菜单 -->
         <div
-          class="menu_subItem"
-          draggable="true"
-          @dragstart="onDragStart(DragType.Child, y)"
-          @dragenter.prevent="onDragEnter(DragType.Child, x, y)"
-          v-if="parent.children"
-          :class="{ active: props.activeIndex === `${x}-${y}` }"
-          @click="subMenuClicked(child, x, y)"
+          @click="menuClicked(parent, x)"
+          class="menu_item"
+          :class="{ active: props.activeIndex === `${x}` }"
         >
-          {{ child.name }}
+          <Icon icon="ep:fold" color="black" />{{ parent.name }}
+        </div>
+        <!-- 以下为二级菜单-->
+        <div class="submenu" v-if="props.parentIndex === x && parent.children">
+          <draggable
+            v-model="parent.children"
+            item-key="id"
+            ghost-class="draggable-ghost"
+            :animation="400"
+          >
+            <template #item="{ element: child, index: y }">
+              <div class="subtitle menu_bottom">
+                <div
+                  class="menu_subItem"
+                  v-if="parent.children"
+                  :class="{ active: props.activeIndex === `${x}-${y}` }"
+                  @click="subMenuClicked(child, x, y)"
+                >
+                  {{ child.name }}
+                </div>
+              </div>
+            </template>
+          </draggable>
+          <!-- 二级菜单加号, 当长度 小于 5 才显示二级菜单的加号  -->
+          <div
+            class="menu_bottom menu_addicon"
+            v-if="!parent.children || parent.children.length < 5"
+            @click="addSubMenu(x, parent)"
+          >
+            <Icon icon="ep:plus" class="plus" />
+          </div>
         </div>
       </div>
-      <!-- 二级菜单加号, 当长度 小于 5 才显示二级菜单的加号  -->
-      <div
-        class="menu_bottom menu_addicon"
-        v-if="!parent.children || parent.children.length < 5"
-        @click="addSubMenu(x, parent)"
-      >
-        <Icon icon="ep:plus" class="plus" />
-      </div>
-    </div>
-  </div>
+    </template>
+  </draggable>
+
   <!-- 一级菜单加号 -->
   <div class="menu_bottom menu_addicon" v-if="menuList.length < 3" @click="addMenu">
     <Icon icon="ep:plus" class="plus" />
@@ -44,6 +58,7 @@
 
 <script setup lang="ts">
 import { Menu } from './types'
+import draggable from 'vuedraggable'
 
 const props = defineProps<{
   modelValue: Menu[]
@@ -97,46 +112,41 @@ const addSubMenu = (i: number, parent: any) => {
 const menuClicked = (parent: Menu, x: number) => {
   emit('menu-clicked', parent, x)
 }
+
 const subMenuClicked = (child: Menu, x: number, y: number) => {
   emit('submenu-clicked', child, x, y)
 }
 
-// ======================== 菜单排序 ========================
-const dragIndex = ref<number>(0)
-enum DragType {
-  Parent = 'parent',
-  Child = 'child'
-}
-const dragType = ref<DragType>()
-
 /**
- * 菜单开始拖动回调,记录被拖动菜单的信息(类型,下标)
+ * 处理一级菜单展开后被拖动
  *
- * @param type DragType, 拖动类型,父节点、子节点
- * @param index number, 被拖动的菜单下标
+ * @param oldIndex: 一级菜单拖动前的位置
+ * @param newIndex: 一级菜单拖动后的位置
  */
-const onDragStart = (type: DragType, index: number) => {
-  dragIndex.value = index
-  dragType.value = type
-}
+const onDragEnd = ({ oldIndex, newIndex }) => {
+  // 二级菜单没有展开,直接返回
+  if (props.activeIndex === '__MENU_NOT_SELECTED__') {
+    return
+  }
 
-/**
- * 拖动其他菜单位置回调, 判断【被拖动】及【被替换位置】的两个菜单是否同个类型,同类型才会进行插入
- *
- * @param type: DragType, 拖动类型,父节点、子节点
- * @param x number, 准备替换父节点位置的下标
- * @param y number, 准备替换子节点位置的下标, 父节点拖动时可选
- */
-const onDragEnter = (type: DragType, x: number, y = -1) => {
-  if (dragIndex.value !== x && dragType.value === type) {
-    if (type === DragType.Parent) {
-      const source = menuList.value.splice(dragIndex.value, 1)
-      menuList.value.splice(x, 0, ...source)
-    } else {
-      const source = menuList.value[x].children?.splice(dragIndex.value, 1)
-      menuList.value[x].children?.splice(y, 0, ...(source as any))
-    }
+  let newParent = props.parentIndex
+  if (props.parentIndex === oldIndex) {
+    newParent = newIndex
+  } else if (props.parentIndex === newIndex) {
+    newParent = oldIndex
+  } else {
+    // 如果展开的二级菜单下标`props.parentIndex`不是被移动的菜单的前后下标。
+    // 那么使用一个辅助素组来模拟菜单移动,然后找到展开的二级菜单的新下标`newParent`
+    let positions = new Array<boolean>(menuList.value.length).fill(false)
+    positions[props.parentIndex] = true
+    positions.splice(oldIndex, 1)
+    positions.splice(newIndex, 0, true)
+    newParent = positions.indexOf(true)
   }
+
+  // 找到菜单元素,触发一级菜单点击
+  const parent = menuList.value[newParent]
+  emit('menu-clicked', parent, newParent)
 }
 </script>
 
@@ -199,4 +209,10 @@ const onDragEnter = (type: DragType, x: number, y = -1) => {
     box-sizing: border-box;
   }
 }
+
+.draggable-ghost {
+  opacity: 0.5;
+  background: #f7fafc;
+  border: 1px solid #4299e1;
+}
 </style>