MenuPreviewer.vue 5.7 KB

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