多级菜单:自动展开功能(嵌套&递归通用写法)
当用户刷新页面或通过 URL 直接访问某个深层级页面时,左侧菜单应该自动展开包含当前页面的父级菜单。Element Plus 的 el-menu 提供 default-openeds 属性来控制初始展开的子菜单,但需要手动计算当前路由对应的所有祖先菜单的 index。
自动展开的原理
用户访问 /components/icon
→ 对应菜单层级:组件示例 > 图标列表
→ 需要展开的子菜单 index:["1"](组件示例的 index)
→ default-openeds 应为 ["1"]
text
对于三级菜单:
用户访问 /system/user/list
→ 菜单层级:系统管理 > 用户管理 > 用户列表
→ 需要展开的子菜单 index:["2", "2-1"]
text
递归计算展开路径
在 useMenu.ts 中新增一个递归方法,根据当前路由路径查找所有祖先菜单的 index:
function getOpenKeys(
menus: AppRouteMenuItem[],
currentPath: string
): string[] {
const openKeys: string[] = []
function findPath(
items: AppRouteMenuItem[],
target: string,
parents: string[] = []
): boolean {
for (const item of items) {
const currentKey = item.meta?.key ?? ''
if (item.path === target) {
openKeys.push(...parents)
return true
}
if (
Array.isArray(item.children) &&
item.children.length > 0
) {
if (findPath(item.children, target, [...parents, currentKey])) {
return true
}
}
}
return false
}
findPath(menus, currentPath)
return openKeys
}
typescript
在 Layout 中使用
// default.vue
const route = useRoute()
const defaultOpenKeys = computed(() => {
return getOpenKeys(filteredMenus.value, route.path)
})
typescript
传递给 Menu 组件:
<Menu
:data="menus"
:default-open-keys="defaultOpenKeys"
/>
vue
Menu 组件内部将 defaultOpenKeys 映射为 el-menu 的 default-openeds:
<el-menu :default-openeds="defaultOpenKeys">
vue
路由切换时保持展开
当用户通过菜单点击跳转到同组的其他页面时,需要保持父级菜单的展开状态。使用 watch 监听路由变化:
watch(
() => route.path,
(newPath) => {
defaultOpenKeys.value = getOpenKeys(filteredMenus.value, newPath)
}
)
typescript
通用递归模板
这种"递归查找 + 收集路径"的模式在树形结构处理中非常通用。核心思路是:
function findInTree(tree, target, path = []):
for each node in tree:
if node matches target:
return path
if node has children:
result = findInTree(children, target, path + node)
if result found:
return result
return not found
text
这个模式可以复用于:面包屑路径计算、权限树查找、文件目录搜索等场景。
本节小结
- 自动展开:根据当前路由路径,递归查找所有祖先菜单的 index,设置为
default-openeds。 - 递归模式:
findPath函数遍历树形结构,收集从根到目标的路径。 - 路由监听:
watch(route.path)确保路由切换时展开状态同步更新。 - 通用模板:递归查找 + 路径收集的模式可复用于其他树形结构场景。
↑