|
1 | 1 | <template>
|
2 |
| - <div class="menu_bottom" v-for="(parent, x) of menuList" :key="x"> |
3 |
| - <!-- 一级菜单 --> |
4 |
| - <div |
5 |
| - @click="menuClicked(parent, x)" |
6 |
| - class="menu_item" |
7 |
| - draggable="true" |
8 |
| - @dragstart="onDragStart(DragType.Parent, x)" |
9 |
| - @dragenter.prevent="onDragEnter(DragType.Parent, x)" |
10 |
| - :class="{ active: props.activeIndex === `${x}` }" |
11 |
| - > |
12 |
| - <Icon icon="ep:fold" color="black" />{{ parent.name }} |
13 |
| - </div> |
14 |
| - <!-- 以下为二级菜单--> |
15 |
| - <div class="submenu" v-if="parentIndex === x && parent.children"> |
16 |
| - <div class="subtitle menu_bottom" v-for="(child, y) in parent.children" :key="y"> |
| 2 | + <draggable |
| 3 | + v-model="menuList" |
| 4 | + item-key="id" |
| 5 | + ghost-class="draggable-ghost" |
| 6 | + :animation="400" |
| 7 | + @end="onDragEnd" |
| 8 | + > |
| 9 | + <template #item="{ element: parent, index: x }"> |
| 10 | + <div class="menu_bottom"> |
| 11 | + <!-- 一级菜单 --> |
17 | 12 | <div
|
18 |
| - class="menu_subItem" |
19 |
| - draggable="true" |
20 |
| - @dragstart="onDragStart(DragType.Child, y)" |
21 |
| - @dragenter.prevent="onDragEnter(DragType.Child, x, y)" |
22 |
| - v-if="parent.children" |
23 |
| - :class="{ active: props.activeIndex === `${x}-${y}` }" |
24 |
| - @click="subMenuClicked(child, x, y)" |
| 13 | + @click="menuClicked(parent, x)" |
| 14 | + class="menu_item" |
| 15 | + :class="{ active: props.activeIndex === `${x}` }" |
25 | 16 | >
|
26 |
| - {{ child.name }} |
| 17 | + <Icon icon="ep:fold" color="black" />{{ parent.name }} |
| 18 | + </div> |
| 19 | + <!-- 以下为二级菜单--> |
| 20 | + <div class="submenu" v-if="props.parentIndex === x && parent.children"> |
| 21 | + <draggable |
| 22 | + v-model="parent.children" |
| 23 | + item-key="id" |
| 24 | + ghost-class="draggable-ghost" |
| 25 | + :animation="400" |
| 26 | + > |
| 27 | + <template #item="{ element: child, index: y }"> |
| 28 | + <div class="subtitle menu_bottom"> |
| 29 | + <div |
| 30 | + class="menu_subItem" |
| 31 | + v-if="parent.children" |
| 32 | + :class="{ active: props.activeIndex === `${x}-${y}` }" |
| 33 | + @click="subMenuClicked(child, x, y)" |
| 34 | + > |
| 35 | + {{ child.name }} |
| 36 | + </div> |
| 37 | + </div> |
| 38 | + </template> |
| 39 | + </draggable> |
| 40 | + <!-- 二级菜单加号, 当长度 小于 5 才显示二级菜单的加号 --> |
| 41 | + <div |
| 42 | + class="menu_bottom menu_addicon" |
| 43 | + v-if="!parent.children || parent.children.length < 5" |
| 44 | + @click="addSubMenu(x, parent)" |
| 45 | + > |
| 46 | + <Icon icon="ep:plus" class="plus" /> |
| 47 | + </div> |
27 | 48 | </div>
|
28 | 49 | </div>
|
29 |
| - <!-- 二级菜单加号, 当长度 小于 5 才显示二级菜单的加号 --> |
30 |
| - <div |
31 |
| - class="menu_bottom menu_addicon" |
32 |
| - v-if="!parent.children || parent.children.length < 5" |
33 |
| - @click="addSubMenu(x, parent)" |
34 |
| - > |
35 |
| - <Icon icon="ep:plus" class="plus" /> |
36 |
| - </div> |
37 |
| - </div> |
38 |
| - </div> |
| 50 | + </template> |
| 51 | + </draggable> |
| 52 | + |
39 | 53 | <!-- 一级菜单加号 -->
|
40 | 54 | <div class="menu_bottom menu_addicon" v-if="menuList.length < 3" @click="addMenu">
|
41 | 55 | <Icon icon="ep:plus" class="plus" />
|
|
44 | 58 |
|
45 | 59 | <script setup lang="ts">
|
46 | 60 | import { Menu } from './types'
|
| 61 | +import draggable from 'vuedraggable' |
47 | 62 |
|
48 | 63 | const props = defineProps<{
|
49 | 64 | modelValue: Menu[]
|
@@ -97,46 +112,41 @@ const addSubMenu = (i: number, parent: any) => {
|
97 | 112 | const menuClicked = (parent: Menu, x: number) => {
|
98 | 113 | emit('menu-clicked', parent, x)
|
99 | 114 | }
|
| 115 | +
|
100 | 116 | const subMenuClicked = (child: Menu, x: number, y: number) => {
|
101 | 117 | emit('submenu-clicked', child, x, y)
|
102 | 118 | }
|
103 | 119 |
|
104 |
| -// ======================== 菜单排序 ======================== |
105 |
| -const dragIndex = ref<number>(0) |
106 |
| -enum DragType { |
107 |
| - Parent = 'parent', |
108 |
| - Child = 'child' |
109 |
| -} |
110 |
| -const dragType = ref<DragType>() |
111 |
| -
|
112 | 120 | /**
|
113 |
| - * 菜单开始拖动回调,记录被拖动菜单的信息(类型,下标) |
| 121 | + * 处理一级菜单展开后被拖动 |
114 | 122 | *
|
115 |
| - * @param type DragType, 拖动类型,父节点、子节点 |
116 |
| - * @param index number, 被拖动的菜单下标 |
| 123 | + * @param oldIndex: 一级菜单拖动前的位置 |
| 124 | + * @param newIndex: 一级菜单拖动后的位置 |
117 | 125 | */
|
118 |
| -const onDragStart = (type: DragType, index: number) => { |
119 |
| - dragIndex.value = index |
120 |
| - dragType.value = type |
121 |
| -} |
| 126 | +const onDragEnd = ({ oldIndex, newIndex }) => { |
| 127 | + // 二级菜单没有展开,直接返回 |
| 128 | + if (props.activeIndex === '__MENU_NOT_SELECTED__') { |
| 129 | + return |
| 130 | + } |
122 | 131 |
|
123 |
| -/** |
124 |
| - * 拖动其他菜单位置回调, 判断【被拖动】及【被替换位置】的两个菜单是否同个类型,同类型才会进行插入 |
125 |
| - * |
126 |
| - * @param type: DragType, 拖动类型,父节点、子节点 |
127 |
| - * @param x number, 准备替换父节点位置的下标 |
128 |
| - * @param y number, 准备替换子节点位置的下标, 父节点拖动时可选 |
129 |
| - */ |
130 |
| -const onDragEnter = (type: DragType, x: number, y = -1) => { |
131 |
| - if (dragIndex.value !== x && dragType.value === type) { |
132 |
| - if (type === DragType.Parent) { |
133 |
| - const source = menuList.value.splice(dragIndex.value, 1) |
134 |
| - menuList.value.splice(x, 0, ...source) |
135 |
| - } else { |
136 |
| - const source = menuList.value[x].children?.splice(dragIndex.value, 1) |
137 |
| - menuList.value[x].children?.splice(y, 0, ...(source as any)) |
138 |
| - } |
| 132 | + let newParent = props.parentIndex |
| 133 | + if (props.parentIndex === oldIndex) { |
| 134 | + newParent = newIndex |
| 135 | + } else if (props.parentIndex === newIndex) { |
| 136 | + newParent = oldIndex |
| 137 | + } else { |
| 138 | + // 如果展开的二级菜单下标`props.parentIndex`不是被移动的菜单的前后下标。 |
| 139 | + // 那么使用一个辅助素组来模拟菜单移动,然后找到展开的二级菜单的新下标`newParent` |
| 140 | + let positions = new Array<boolean>(menuList.value.length).fill(false) |
| 141 | + positions[props.parentIndex] = true |
| 142 | + positions.splice(oldIndex, 1) |
| 143 | + positions.splice(newIndex, 0, true) |
| 144 | + newParent = positions.indexOf(true) |
139 | 145 | }
|
| 146 | +
|
| 147 | + // 找到菜单元素,触发一级菜单点击 |
| 148 | + const parent = menuList.value[newParent] |
| 149 | + emit('menu-clicked', parent, newParent) |
140 | 150 | }
|
141 | 151 | </script>
|
142 | 152 |
|
@@ -199,4 +209,10 @@ const onDragEnter = (type: DragType, x: number, y = -1) => {
|
199 | 209 | box-sizing: border-box;
|
200 | 210 | }
|
201 | 211 | }
|
| 212 | +
|
| 213 | +.draggable-ghost { |
| 214 | + opacity: 0.5; |
| 215 | + background: #f7fafc; |
| 216 | + border: 1px solid #4299e1; |
| 217 | +} |
202 | 218 | </style>
|
0 commit comments