MenuPreviewer.vue 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <template>
  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. <!-- 一级菜单 -->
  12. <div
  13. @click="menuClicked(parent, x)"
  14. class="menu_item"
  15. :class="{ active: props.activeIndex === `${x}` }"
  16. >
  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>
  48. </div>
  49. </div>
  50. </template>
  51. </draggable>
  52. <!-- 一级菜单加号 -->
  53. <div class="menu_bottom menu_addicon" v-if="menuList.length < 3" @click="addMenu">
  54. <Icon icon="ep:plus" class="plus" />
  55. </div>
  56. </template>
  57. <script setup lang="ts">
  58. import { Menu } from './types'
  59. import draggable from 'vuedraggable'
  60. const props = defineProps<{
  61. modelValue: Menu[]
  62. activeIndex: string
  63. parentIndex: number
  64. accountId: number
  65. }>()
  66. const emit = defineEmits<{
  67. (e: 'update:modelValue', v: Menu[])
  68. (e: 'menu-clicked', parent: Menu, x: number)
  69. (e: 'submenu-clicked', child: Menu, x: number, y: number)
  70. }>()
  71. const menuList = computed<Menu[]>({
  72. get: () => props.modelValue,
  73. set: (val) => emit('update:modelValue', val)
  74. })
  75. // 添加横向一级菜单
  76. const addMenu = () => {
  77. const index = menuList.value.length
  78. const menu = {
  79. name: '菜单名称',
  80. children: [],
  81. reply: {
  82. // 用于存储回复内容
  83. type: 'text',
  84. accountId: props.accountId // 保证组件里,可以使用到对应的公众号
  85. }
  86. }
  87. menuList.value[index] = menu
  88. menuClicked(menu, index - 1)
  89. }
  90. // 添加横向二级菜单;parent 表示要操作的父菜单
  91. const addSubMenu = (i: number, parent: any) => {
  92. const subMenuKeyLength = parent.children.length // 获取二级菜单key长度
  93. const addButton = {
  94. name: '子菜单名称',
  95. reply: {
  96. // 用于存储回复内容
  97. type: 'text',
  98. accountId: props.accountId // 保证组件里,可以使用到对应的公众号
  99. }
  100. }
  101. parent.children[subMenuKeyLength] = addButton
  102. subMenuClicked(parent.children[subMenuKeyLength], i, subMenuKeyLength)
  103. }
  104. const menuClicked = (parent: Menu, x: number) => {
  105. emit('menu-clicked', parent, x)
  106. }
  107. const subMenuClicked = (child: Menu, x: number, y: number) => {
  108. emit('submenu-clicked', child, x, y)
  109. }
  110. /**
  111. * 处理一级菜单展开后被拖动
  112. *
  113. * @param oldIndex: 一级菜单拖动前的位置
  114. * @param newIndex: 一级菜单拖动后的位置
  115. */
  116. const onDragEnd = ({ oldIndex, newIndex }) => {
  117. // 二级菜单没有展开,直接返回
  118. if (props.activeIndex === '__MENU_NOT_SELECTED__') {
  119. return
  120. }
  121. let newParent = props.parentIndex
  122. if (props.parentIndex === oldIndex) {
  123. newParent = newIndex
  124. } else if (props.parentIndex === newIndex) {
  125. newParent = oldIndex
  126. } else {
  127. // 如果展开的二级菜单下标`props.parentIndex`不是被移动的菜单的前后下标。
  128. // 那么使用一个辅助素组来模拟菜单移动,然后找到展开的二级菜单的新下标`newParent`
  129. let positions = new Array<boolean>(menuList.value.length).fill(false)
  130. positions[props.parentIndex] = true
  131. positions.splice(oldIndex, 1)
  132. positions.splice(newIndex, 0, true)
  133. newParent = positions.indexOf(true)
  134. }
  135. // 找到菜单元素,触发一级菜单点击
  136. const parent = menuList.value[newParent]
  137. emit('menu-clicked', parent, newParent)
  138. }
  139. </script>
  140. <style lang="scss" scoped>
  141. .menu_bottom {
  142. position: relative;
  143. display: inline-block;
  144. float: left;
  145. width: 85.5px;
  146. text-align: center;
  147. cursor: pointer;
  148. background-color: #fff;
  149. border: 1px solid #ebedee;
  150. box-sizing: border-box;
  151. &.menu_addicon {
  152. height: 46px;
  153. line-height: 46px;
  154. .plus {
  155. color: #2bb673;
  156. }
  157. }
  158. .menu_item {
  159. display: flex;
  160. width: 100%;
  161. height: 44px;
  162. line-height: 44px;
  163. // text-align: center;
  164. box-sizing: border-box;
  165. align-items: center;
  166. justify-content: center;
  167. &.active {
  168. border: 1px solid #2bb673;
  169. }
  170. }
  171. .menu_subItem {
  172. height: 44px;
  173. line-height: 44px;
  174. text-align: center;
  175. box-sizing: border-box;
  176. &.active {
  177. border: 1px solid #2bb673;
  178. }
  179. }
  180. }
  181. /* 第二级菜单 */
  182. .submenu {
  183. position: absolute;
  184. bottom: 45px;
  185. width: 85.5px;
  186. .subtitle {
  187. background-color: #fff;
  188. box-sizing: border-box;
  189. }
  190. }
  191. .draggable-ghost {
  192. opacity: 0.5;
  193. background: #f7fafc;
  194. border: 1px solid #4299e1;
  195. }
  196. </style>