修复菜单折叠问题

This commit is contained in:
zjf committed 2026-08-28 11:22:38 +08:00
1 parent 85aa7f97a7
commit 19ad2a5d99
2 files changed
+88 -38

No files matched your search

+6 -7
View File
@@ -12,7 +12,6 @@
<script setup lang="ts" name="layoutAside">
import {computed, defineAsyncComponent, onBeforeMount, reactive, ref, watch} from 'vue';
import {storeToRefs} from 'pinia';
import pinia from '/@/stores/index';
import {useRoutesList} from '/@/stores/routesList';
import {useThemeConfig} from '/@/stores/themeConfig';
import {useTagsViewRoutes} from '/@/stores/tagsViewRoutes';
@@ -141,8 +140,8 @@ onBeforeMount(() => {
mittBus.on('setSendClassicChildren', (res: MittMenu) => {
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) {
state.menuList = [];
state.menuList = res.children;
// 不要先清空再赋值,避免菜单闪断导致父级折叠
state.menuList = res.children || [];
}
});
mittBus.on('getBreadcrumbIndexSetFilterRoutes', () => {
@@ -159,11 +158,11 @@ watch(themeConfig.value, (val) => {
if (layoutAsideScrollbarRef.value) layoutAsideScrollbarRef.value.update();
}
});
// 监听 pinia 值的变化,动态赋值给菜单中
// 仅在路由表变化时重建侧栏,避免任意 pinia 写入都重置展开状态
watch(
pinia.state,
(val) => {
let { layout, isClassicSplitMenu } = val.themeConfig.themeConfig;
routesList,
() => {
let { layout, isClassicSplitMenu } = themeConfig.value;
if (layout === 'classic' && isClassicSplitMenu) return false;
setFilterRoutes();
},
+82 -31
View File
@@ -1,14 +1,16 @@
<template>
<el-menu
router
:key="state.menuRenderKey"
:default-active="state.defaultActive"
:default-openeds="state.defaultOpeneds"
background-color="transparent"
:collapse="state.isCollapse"
:unique-opened="getThemeConfig.isUniqueOpened"
:collapse-transition="false"
>
<template v-for="val in menuLists">
<!-- 三级菜单 -->
<!-- 三级菜单 -->
<el-sub-menu :index="val.path" v-if="val.children && val.children.length > 0" :key="val.path">
<template #title>
<SvgIcon :name="val.meta.icon" />
@@ -17,7 +19,7 @@
<SubItem :chil="val.children" />
</el-sub-menu>
<template v-else>
<!-- 二级菜单 -->
<!-- 二级菜单 -->
<el-menu-item :index="val.path" :key="val.path">
<SvgIcon :name="val.meta.icon" />
<template #title v-if="!val.meta.isLink || (val.meta.isLink && val.meta.isIframe)">
@@ -33,12 +35,12 @@
</template>
<script setup lang="ts" name="navMenuVertical">
import {RouteRecordRaw} from 'vue-router';
import {useThemeConfig} from '/@/stores/themeConfig';
import other from '/@/utils/other';
import { RouteRecordRaw } from "vue-router"
import { useThemeConfig } from "/@/stores/themeConfig"
import other from "/@/utils/other"
// 引入组件
const SubItem = defineAsyncComponent(() => import('/@/layout/navMenu/subItem.vue'));
const SubItem = defineAsyncComponent(() => import("/@/layout/navMenu/subItem.vue"))
// 定义父组件传过来的值
const props = defineProps({
@@ -47,54 +49,103 @@ const props = defineProps({
type: Array<RouteRecordRaw>,
default: () => [],
},
});
})
// 定义变量内容
const storesThemeConfig = useThemeConfig();
const { themeConfig } = storeToRefs(storesThemeConfig);
const route = useRoute();
const storesThemeConfig = useThemeConfig()
const { themeConfig } = storeToRefs(storesThemeConfig)
const route = useRoute()
const state = reactive({
defaultActive: route.meta.isDynamic ? route.meta.isDynamicPath : route.path,
defaultOpeneds: [] as string[],
menuRenderKey: 0,
isCollapse: false,
});
})
// 获取父级菜单数据
const menuLists = computed(() => {
return <RouteItems>props.menuList;
});
return <RouteItems>props.menuList
})
// 获取布局配置信息
const getThemeConfig = computed(() => {
return themeConfig.value;
});
return themeConfig.value
})
// 菜单高亮(详情时,父级高亮)
const setParentHighlight = (currentRoute: RouteToFrom) => {
const { path, meta } = currentRoute;
const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split('/') : path!.split('/');
if (pathSplit.length >= 4 && meta?.isHide) return pathSplit.splice(0, 3).join('/');
else return path;
};
const { path, meta } = currentRoute
const pathSplit = meta?.isDynamic ? meta.isDynamicPath!.split("/") : path!.split("/")
if (pathSplit.length >= 4 && meta?.isHide) return pathSplit.splice(0, 3).join("/")
else return path
}
/** 找到当前激活项的所有父级 path,保持左侧分组展开 */
const findParentPaths = (menus: RouteItems, active: string, trail: string[] = []): string[] | null => {
for (const m of menus || []) {
if (!m) continue
if (m.path === active) {
return trail
}
if (m.children && m.children.length > 0) {
const found = findParentPaths(m.children as RouteItems, active, [...trail, m.path])
if (found) return found
}
}
// 隐藏子路由 / 详情页:按前缀打开最近父级
for (const m of menus || []) {
if (!m?.children?.length) continue
if (active.startsWith(`${m.path}/`)) {
const found = findParentPaths(m.children as RouteItems, active, [...trail, m.path])
if (found) return found
return [...trail, m.path]
}
}
return null
}
const refreshMenuState = (currentRoute: RouteToFrom = route) => {
const active = setParentHighlight(currentRoute) as string
const openeds = findParentPaths(menuLists.value, active) || []
const sameActive = state.defaultActive === active
const sameOpeneds =
state.defaultOpeneds.length === openeds.length && state.defaultOpeneds.every((p, i) => p === openeds[i])
state.defaultActive = active
state.defaultOpeneds = openeds
// 仅在激活项或父级展开变化时重渲染,避免无意义闪烁
if (!sameActive || !sameOpeneds) {
state.menuRenderKey += 1
}
}
// 打开外部链接
const onALinkClick = (val: RouteItem) => {
other.handleOpenLink(val);
};
other.handleOpenLink(val)
}
// 页面加载时
onMounted(() => {
state.defaultActive = setParentHighlight(route);
});
refreshMenuState(route)
})
// 路由更新时
onBeforeRouteUpdate((to) => {
state.defaultActive = setParentHighlight(to);
const clientWidth = document.body.clientWidth;
if (clientWidth < 1000) themeConfig.value.isCollapse = false;
});
refreshMenuState(to)
const clientWidth = document.body.clientWidth
if (clientWidth < 1000) themeConfig.value.isCollapse = false
})
// 侧栏菜单数据被经典分栏/路由过滤重建后,重新带上父级展开
watch(
menuLists,
() => {
refreshMenuState(route)
},
{ deep: true },
)
// 设置菜单的收起/展开
watch(
themeConfig.value,
() => {
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = themeConfig.value.isCollapse);
document.body.clientWidth <= 1000 ? (state.isCollapse = false) : (state.isCollapse = themeConfig.value.isCollapse)
},
{
immediate: true,
}
);
},
)
</script>