|
@@ -3,17 +3,25 @@
|
|
|
<div ref="logoRef">
|
|
|
<Logo/>
|
|
|
</div>
|
|
|
- <div style="height: 30px; padding: 0 8px 8px 8px" @click="expandMenu">
|
|
|
- <el-input ref="searchRef" v-model="menuText" prefix-icon="Search" clearable
|
|
|
+ <div style="height: 30px; padding: 0 8px 8px 8px" @click="expandMenu" id="tutorial_search_menu">
|
|
|
+ <el-input ref="searchRef"
|
|
|
+ v-model="menuText"
|
|
|
+ prefix-icon="Search"
|
|
|
+ clearable
|
|
|
+ @input="searchInput"
|
|
|
placeholder="菜单太难找?在这里搜索。"></el-input>
|
|
|
</div>
|
|
|
<el-scrollbar :style="barHeight">
|
|
|
- <el-menu router
|
|
|
- :collapse-transition="false"
|
|
|
- :collapse="isCollapse"
|
|
|
- :default-active="activeMenu"
|
|
|
- :unique-opened="expandOneMenu">
|
|
|
- <menu-item-v2 v-for="item in cptMenu" :data="item"/>
|
|
|
+ <el-menu
|
|
|
+ ref="menuRef"
|
|
|
+ router
|
|
|
+ :collapse-transition="false"
|
|
|
+ :collapse="isCollapse"
|
|
|
+ :default-active="isSearch ? '' : activeMenu"
|
|
|
+ :unique-opened="isSearch ? false : expandOneMenu">
|
|
|
+ <menu-item-v2 v-for="item in isSearch ? searchData : data"
|
|
|
+ :data="item"
|
|
|
+ :menuText="menuText"/>
|
|
|
</el-menu>
|
|
|
</el-scrollbar>
|
|
|
</div>
|
|
@@ -22,23 +30,95 @@
|
|
|
<script setup name='MenuV2' lang="ts">
|
|
|
import store from '@/store'
|
|
|
import MenuItemV2 from "./MenuItemV2.vue";
|
|
|
-import {computed, nextTick, onMounted, reactive, Ref, ref} from "vue";
|
|
|
+import {computed, nextTick, onMounted, reactive, Ref, ref, watch} from "vue";
|
|
|
import {useRoute} from "vue-router";
|
|
|
import Logo from '../HeaderV2/Logo.vue'
|
|
|
+import XEUtils from "xe-utils";
|
|
|
+import router from '@/router'
|
|
|
|
|
|
-const menuText: Ref<string> = ref(null)
|
|
|
+const menuText: Ref<string> = ref('')
|
|
|
+const menuRef = ref()
|
|
|
|
|
|
const data = store.state.user.routes
|
|
|
-const flatMenu = store.state.user.flatRoutes
|
|
|
|
|
|
-const cptMenu = computed(() => {
|
|
|
- if (menuText.value) {
|
|
|
- return flatMenu.filter(item => item['metaTitle'].indexOf(menuText.value) !== -1)
|
|
|
+const isSearch = ref<boolean>(false)
|
|
|
+const searchData = ref([])
|
|
|
+
|
|
|
+function filterMenu<D>(data: D[], iterator: (item: D) => boolean) {
|
|
|
+ data = JSON.parse(JSON.stringify(data))
|
|
|
+
|
|
|
+ function filterDataByVisible(tempData: D) {
|
|
|
+ return tempData.filter(item => {
|
|
|
+ if (item.children) {
|
|
|
+ item.children = filterDataByVisible(item.children);
|
|
|
+ }
|
|
|
+ if (item.$visible) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ const traverse = (tempData: D) => {
|
|
|
+ tempData.forEach(child => {
|
|
|
+ child.$visible = iterator(child)
|
|
|
+ if (child.children) traverse(child.children);
|
|
|
+ if (!child.$visible && child.children?.length) {
|
|
|
+ let $visible = !child.children.some(child => child.$visible);
|
|
|
+ child.$visible = $visible === false;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ traverse(data);
|
|
|
+ return filterDataByVisible(data);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+let searchActives = new Set()
|
|
|
+const searchInput = XEUtils.debounce(async (val) => {
|
|
|
+ menuText.value = val
|
|
|
+ searchActives.clear()
|
|
|
+
|
|
|
+ searchData.value = filterMenu<{
|
|
|
+ metaTitle: string
|
|
|
+ }>(data, (item) => {
|
|
|
+ const metaTitle: string = item.metaTitle
|
|
|
+ return metaTitle.includes(val);
|
|
|
+ })
|
|
|
+
|
|
|
+ isSearch.value = !!val
|
|
|
+
|
|
|
+ await nextTick()
|
|
|
+ expandNodes(searchData.value)
|
|
|
+
|
|
|
+}, 500)
|
|
|
+
|
|
|
+const expandNodes = (treeData) => {
|
|
|
+ const traverse = tempData => {
|
|
|
+ XEUtils.arrayEach(tempData, (item) => {
|
|
|
+ if (item.children !== null && item.children.length > 0) {
|
|
|
+ menuLaunch(item.completeRoute)
|
|
|
+ traverse(item.children);
|
|
|
+ }
|
|
|
+ })
|
|
|
}
|
|
|
- return data.filter(() => {
|
|
|
- return true
|
|
|
- });
|
|
|
-})
|
|
|
+
|
|
|
+ traverse(treeData)
|
|
|
+}
|
|
|
+
|
|
|
+const menuLaunch = async path => {
|
|
|
+ try {
|
|
|
+ await nextTick()
|
|
|
+ const li = document.getElementById(path)
|
|
|
+ const div = li.children[0]
|
|
|
+ const icon = div.getElementsByClassName('el-icon el-sub-menu__icon-arrow')
|
|
|
+ if (icon[0].style.transform === 'none') {
|
|
|
+ div.click()
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
|
|
|
const expandOneMenu = computed(() => store.state.app.expandOneMenu)
|
|
|
const route = useRoute()
|
|
@@ -69,10 +149,29 @@ const activeMenu = computed(() => {
|
|
|
return path
|
|
|
})
|
|
|
|
|
|
-onMounted(() => {
|
|
|
- nextTick(() => {
|
|
|
- barHeight.height = (floatingRef.value.clientHeight - logoRef.value.clientHeight - 36) + 'px'
|
|
|
+const smoothScrolling = () => {
|
|
|
+ const routerPath = document.getElementById(router.currentRoute.value.fullPath)
|
|
|
+ if (!routerPath) return
|
|
|
+ routerPath.scrollIntoView({
|
|
|
+ block: 'start',
|
|
|
+ inline: 'nearest',
|
|
|
+ behavior: 'smooth'
|
|
|
})
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+watch(() => router.currentRoute.value, async () => {
|
|
|
+ menuText.value = ''
|
|
|
+ isSearch.value = false
|
|
|
+ await menuRef.value.handleResize()
|
|
|
+ await nextTick()
|
|
|
+ smoothScrolling()
|
|
|
+})
|
|
|
+
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ await nextTick()
|
|
|
+ barHeight.height = (floatingRef.value.clientHeight - logoRef.value.clientHeight - 36) + 'px'
|
|
|
})
|
|
|
|
|
|
</script>
|