静态路由菜单生成
从 TanStack Router staticData 生成 Admin Layouts 菜单树、快速索引和首页
静态模式适合菜单由前端路由文件维护的后台应用。路由文件声明 staticData,布局包读取 routeTree 后生成菜单树、快速索引和首页。
启用静态模式
setupAdminLayouts({
defaultHome: '/home',
defaultIcon: 'mdi:menu',
menuCategories: {
admin: {
key: 'admin',
layout: '/(admin)'
}
},
routeMode: 'static',
routeTree,
storage: localStg
});menuCategories 决定哪些 layout route 会参与菜单生成。上面的配置表示:
admin是菜单分类 key。/(admin)是 TanStack Router 的 layout route id。- 该 layout route 下的子路由会被扫描并转换成菜单。
路由声明
每个需要进入菜单或快速索引的 route 都应该声明 staticData。
export const Route = createFileRoute('/(admin)/home/')({
component: Home,
staticData: {
i18nKey: 'route.home',
title: 'home',
menu: {
badge: {
type: 'normal',
valueKey: 'home.updates'
},
icon: 'mdi:monitor-dashboard',
order: 1
}
}
});隐藏详情页也要声明 staticData。区别只是 menu.hide 为 true,这样页面不会显示在菜单树里,但仍会进入快速索引,布局可以根据它找到 tab 信息、父级菜单和激活菜单。
export const Route = createFileRoute('/(admin)/system/user/$id')({
component: UserDetail,
staticData: {
title: 'userDetail',
menu: {
activeMenu: '/system/user',
hide: true
}
}
});生成流程
useMenus().initMenus(userInfo)
└─ menuGenerator.generate({ userInfo })
└─ generateStaticMenus()
├─ createEmptyCategoryMaps()
├─ findLayoutRoute(menuCategories[*].layout)
├─ generateStaticLayoutMenus(layoutRoute)
│ ├─ transformStaticRouteToMenu(route)
│ ├─ generateStaticChildMenus(route)
│ └─ menuNodeCallback(layoutRoute.id)
└─ return { allMenus, quickReferenceMenus, home: defaultHome }initMenus(userInfo) 是静态菜单的入口。认证完成后传入当前用户信息,生成器会在转换 route 时做权限过滤。
分类和 layout route
静态菜单不会扫描整棵 routeTree。它只会找 routeTree.children 中 id 命中 menuCategories[*].layout 的 layout route。
menuCategories: {
admin: {
key: 'admin',
layout: '/(admin)'
},
monitor: {
key: 'monitor',
layout: '/(monitor)'
}
}生成结果会按分类写入:
Map {
'admin' => GeneratedMenu[],
'monitor' => GeneratedMenu[]
}如果某个 layout route 没有配置到 menuCategories,它下面的路由不会参与该菜单系统。
staticData 转换规则
transformStaticRouteToMenu 会先读取 route.options.staticData。没有 staticData 的 route 会直接跳过。
| 输入字段 | 输出到 GeneratedMenu |
|---|---|
staticData.title | title |
staticData.i18nKey | i18nKey |
staticData.menu.icon | icon |
staticData.menu.localIcon | localIcon |
staticData.menu.order | order |
staticData.menu.type | type,默认 item |
staticData.menu.badge | badge |
staticData.menu.extra | extra |
route.fullPath | key 和 path,会先经过 normalizePath |
normalizePath 会去掉非根路径末尾的 /。例如 /home/ 会变成 /home,/ 会保持不变。
权限和隐藏菜单
静态模式会在生成菜单时调用:
hasRoutePermission(staticData, userInfo)如果 route 没有权限,它不会进入菜单,也不会进入快速索引。父 route 没有权限时,它的子路由也不会继续生成。
menu.hide 的处理顺序不同:生成器会先把 route 写入 quickReferenceMenus,再判断是否从菜单树隐藏。因此隐藏页仍可用于:
- 打开对应 tab。
- 根据
activeMenu激活左侧菜单。 - 找到父级菜单链路。
children 和排序
静态菜单会递归读取当前 route 的 children。每一层都会按 menu.order 升序排序,未声明 order 时按 0 处理。
/(admin)
├─ /home order: 1
├─ /system order: 10
│ └─ /user order: 1
└─ /about order: 22生成后同级菜单顺序只由同级 order 决定,父子层级不会互相比较。
menuNodeCallback
静态模式下,menuNodeCallback 会在两个位置执行:
- layout route 级别:给整个分类追加一级菜单或 divider。
- 普通 route 级别:给某个菜单节点追加子菜单或 divider。
它只适合追加路由树之外的补充节点。普通页面菜单仍应放在 route 的 staticData.menu 中。没有补充节点时返回空数组。
import type { MenuNodeCallback } from '@skyroc/web-admin-layouts';
export const menuNodeCallback: MenuNodeCallback = routeId => {
if (routeId !== '/(admin)') return [];
return [
{
id: 'admin-divider',
menu: {
order: 9,
type: 'divider'
}
}
];
};回调返回的节点会走 createExtraMenu,支持 badge、extra、children 和排序。非 divider 节点必须提供 path,否则不会生成菜单。
常见问题
| 问题 | 原因和处理 |
|---|---|
| 路由能访问但菜单不显示 | 检查 route 是否有 staticData.menu,以及父 route 是否被权限过滤。 |
| 隐藏详情页无法激活父菜单 | 详情页需要声明 staticData.menu.hide 和 staticData.menu.activeMenu。 |
| 某个 layout 下的路由都不显示 | 检查 menuCategories[*].layout 是否等于 TanStack Router 的 layout route id。 |
| 菜单顺序不符合预期 | 检查同级 menu.order,未声明时会按 0 排序。 |
| badge 数字不会变化 | 路由只声明 valueKey,数量更新要通过 useAdminMenuBadges 或导出的 badge action 写入。 |