MenuV2.vue 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. <template>
  2. <div class="floating">
  3. <div>
  4. <Logo :show-logo="!isCollapse"/>
  5. <div style="height: 30px; padding: 0 8px 8px 8px" @click="expandMenu" id="tutorial_search_menu">
  6. <el-input
  7. ref="searchRef"
  8. v-model="menuText"
  9. prefix-icon="Search"
  10. clearable
  11. @input="searchInput"
  12. placeholder="菜单太难找?在这里搜索。"/>
  13. </div>
  14. </div>
  15. <div class="layout_flex_1-y">
  16. <el-scrollbar>
  17. <el-menu
  18. ref="menuRef"
  19. :collapse-transition="false"
  20. :collapse="isCollapse"
  21. :default-active="isSearch ? '' : activeMenu"
  22. :unique-opened="isSearch ? false : expandOneMenu">
  23. <template v-for="item in isSearch ? searchData : menuData">
  24. <menu-item-v2
  25. :data="item"
  26. v-if="item?.metaShowMenu"
  27. :menuText="menuText"/>
  28. </template>
  29. </el-menu>
  30. </el-scrollbar>
  31. </div>
  32. </div>
  33. </template>
  34. <script setup lang="ts">
  35. import MenuItemV2 from "./MenuItemV2.vue";
  36. import {computed, nextTick, onMounted, Ref, ref, watch} from "vue";
  37. import {useRoute} from "vue-router";
  38. import Logo from '../HeaderV2/Logo.vue'
  39. import XEUtils from "xe-utils";
  40. import router, {routerMenus} from '@/router'
  41. import sleep from "@/utils/sleep";
  42. import {treeSearch} from "@/utils/array-utils";
  43. import {useCompRef} from "@/utils/useCompRef";
  44. import {ElMenu} from "element-plus";
  45. import {useSystemStore} from "@/pinia/system-store";
  46. import {stringNotBlank} from "@/utils/blank-utils";
  47. const menuText: Ref<string> = ref('')
  48. const menuRef = useCompRef(ElMenu)
  49. const menuData = computed(() => {
  50. return routerMenus.value
  51. })
  52. const isSearch = ref<boolean>(false)
  53. const searchData = ref([])
  54. const searchActives = new Set()
  55. const searchInput: (val: string) => void = XEUtils.debounce(async (val: string) => {
  56. menuText.value = val;
  57. searchActives.clear();
  58. if (val) {
  59. searchData.value = treeSearch<{ metaTitle: string; }>(menuData.value, (item) => {
  60. const metaTitle: string = item.metaTitle
  61. if (stringNotBlank(metaTitle)) {
  62. return metaTitle.includes(val);
  63. }
  64. return false
  65. });
  66. isSearch.value = true
  67. await nextTick()
  68. expandNodes(searchData.value)
  69. } else {
  70. searchData.value = []
  71. await nextTick()
  72. isSearch.value = false
  73. smoothScrolling()
  74. }
  75. }, 500)
  76. const expandNodes = (treeData) => {
  77. const traverse = tempData => {
  78. XEUtils.arrayEach(tempData, (item) => {
  79. if (item.children !== null && item.children.length > 0) {
  80. menuLaunch(item.name)
  81. traverse(item.children);
  82. }
  83. })
  84. }
  85. traverse(treeData)
  86. }
  87. const menuLaunch = async path => {
  88. await nextTick();
  89. const li = document.getElementById(path);
  90. const div = li.children[0];
  91. const icon = div.getElementsByClassName('el-icon el-sub-menu__icon-arrow');
  92. // @ts-ignore
  93. if (icon.item(0).style.transform === 'none') {
  94. try {
  95. // @ts-ignore
  96. div!.click();
  97. } catch (e) {
  98. }
  99. }
  100. }
  101. const systemStore = useSystemStore()
  102. const expandOneMenu = computed<boolean>(() => systemStore.expandOneMenu)
  103. const route = useRoute()
  104. const isCollapse = computed<boolean>(() => systemStore.getCollapse)
  105. const expandMenu = () => {
  106. if (isCollapse.value) {
  107. systemStore.setCollapse(false)
  108. setTimeout(() => {
  109. searchRef.value!.focus()
  110. }, 100)
  111. }
  112. }
  113. const searchRef: Ref<HTMLHeadElement | null> = ref(null)
  114. const activeMenu = computed<string>(() => {
  115. const {meta, path} = route
  116. if (meta['activeMenu']) {
  117. return meta['activeMenu'] as string
  118. }
  119. return path
  120. })
  121. const smoothScrolling = () => {
  122. const routerPath = document.getElementById(<string>router.currentRoute.value.name)
  123. if (!routerPath) return
  124. routerPath.scrollIntoView({
  125. block: 'center',
  126. inline: 'nearest',
  127. behavior: 'smooth'
  128. })
  129. }
  130. watch(() => router.currentRoute.value, async () => {
  131. menuText.value = ''
  132. isSearch.value = false
  133. await nextTick()
  134. await sleep(200)
  135. smoothScrolling()
  136. })
  137. onMounted(async () => {
  138. await nextTick()
  139. await sleep(500)
  140. smoothScrolling()
  141. })
  142. </script>
  143. <style scoped lang="scss">
  144. .floating {
  145. height: 100%;
  146. width: 100%;
  147. background-color: var(--el-menu-bg-color);
  148. display: flex;
  149. flex-direction: column;
  150. .el-menu {
  151. border-right: 0 !important;
  152. position: relative;
  153. }
  154. .menu {
  155. overflow: auto;
  156. }
  157. }
  158. :deep(.el-sub-menu__title) {
  159. padding: 0 16px;
  160. }
  161. </style>