API 参考
@skyroc/web-admin-layouts 的导出项、初始化配置、布局 props、菜单 meta 和 hook 速查
公共导出
import {
AdminLayout,
cacheTabs,
clearMenuBadgeValues,
hasAnyRoutePermission,
hasMatchedRoutePermission,
hasRoutePermission,
normalizePath,
setMenuBadgeValue,
setMenuBadgeValues,
setupAdminLayouts,
useAdminMenuBadges,
useAdminMenus,
useAdminState,
useAdminTab,
useMenus,
useRoute
} from '@skyroc/web-admin-layouts';
import type { AdminLayoutProps, AdminLayoutsOptions } from '@skyroc/web-admin-layouts';主要导出:
| 导出 | 类型 | 说明 |
|---|---|---|
AdminLayout | Component | 应用级后台布局壳。插槽细节见 布局插槽。 |
setupAdminLayouts | Function | 启动阶段注入布局全局配置。 |
useMenus | Hook | 初始化和读取菜单原始数据。 |
useAdminMenus | Hook | 读取当前布局菜单视图和菜单交互方法。详见 状态与页签。 |
useAdminState | Hook | 读取和修改布局运行时状态。详见 状态与页签。 |
useAdminTab | Hook | 读取和操作路由页签。详见 状态与页签。 |
useAdminMenuBadges | Hook | 读取和更新菜单 badge 动态值。详见 状态与页签。 |
setMenuBadgeValue | Function | 非组件环境更新单个 badge 动态值。 |
setMenuBadgeValues | Function | 非组件环境批量更新 badge 动态值。 |
clearMenuBadgeValues | Function | 清理全部或部分 badge 动态值。 |
hasAnyRoutePermission | Function | 校验一组权限是否命中当前用户角色。 |
hasRoutePermission | Function | 校验单个 route meta 权限。 |
hasMatchedRoutePermission | Function | 校验当前 TanStack Router matches 链路权限。 |
normalizePath | Function | 规范化路由 path,去掉非根路径末尾斜杠。 |
useRoute | Hook | 读取当前 TanStack Router match,并返回布局包内部使用的标准 route 信息。 |
cacheTabs | Function | 将当前 tab 状态写入配置的 storage。通常由认证退出或布局 effect 调用。 |
核心入口
AdminLayout
AdminLayout 是应用级后台壳。业务应用通常只在后台 layout route 中渲染一次。
<AdminLayout
footer={<AdminFooter />}
headerMiddleActions={<NotificationButton />}
headerRightActions={<UserAvatar />}
logo={<SystemLogo />}
logoTitle="Skyroc Admin"
/>它内部会组合:
@skyroc/materials的底层布局容器。AdminHeader、AdminSider、AdminMenu、AdminTab、AdminContent。ThemeDrawer、AdminEffect和 tab effect。useSettingsTheme、useAdminState、useAdminMenus计算出的布局状态。
AdminLayout 的详细插槽说明见 布局插槽,这里不重复展开。
setupAdminLayouts
setupAdminLayouts 必须在渲染 AdminLayout 之前调用。它只做配置注入,不渲染组件。
setupAdminLayouts({
defaultHome: '/home',
defaultIcon: 'mdi:menu',
loadDynamicRoutes: loadAdminDynamicRoutes,
menuCategories: {
admin: {
key: 'admin',
layout: '/(admin)'
}
},
routeMode: 'static',
routeTree,
storage: localStg
});如果未调用就渲染布局或调用依赖配置的 helper,布局包会抛出初始化错误。
Admin layouts are not initialized. Call setupAdminLayouts before rendering AdminLayout.适合放在应用 bootstrap 中,并且应该早于 createRoot(...).render(...)。
AdminLayoutsOptions
interface AdminLayoutsOptions {
defaultHome: Router.RoutePath;
defaultIcon: string;
extras?: Record<string, ComponentType<any>>;
loadDynamicRoutes?: () => Promise<AdminLayoutsDynamicRoutes>;
menuCategories: Record<string, AdminLayoutMenuCategory>;
menuNodeCallback?: MenuNodeCallback;
permissionSuperRole?: string;
routeMode: 'dynamic' | 'static';
routeTree: AnyRoute;
storage: AdminLayoutsStorage;
}| 字段 | 必填 | 说明 |
|---|---|---|
defaultHome | 是 | 默认首页路由。 |
defaultIcon | 是 | 菜单默认图标。 |
routeMode | 是 | 菜单模式:static 或 dynamic。 |
routeTree | 是 | TanStack Router 生成的 routeTree。 |
menuCategories | 是 | 菜单分类配置。 |
storage | 是 | 布局使用的 local storage 适配器。 |
loadDynamicRoutes | 否 | 动态菜单加载器。 |
menuNodeCallback | 否 | 菜单节点扩展回调。 |
extras | 否 | 自定义菜单 extra 组件注册表。 |
permissionSuperRole | 否 | 超级角色标识。 |
AdminLayoutsDynamicRoutes
动态菜单加载器需要返回这个结构:
interface AdminLayoutsDynamicRoutes {
home?: Router.RoutePath;
routes: Api.Route.BackendRoute[];
}home 会覆盖 defaultHome。routes 会走动态菜单生成链路,最终生成 GeneratedMenu[]。
AdminLayoutMenuCategory
interface AdminLayoutMenuCategory {
key: string;
layout: Router.RouteId;
}layout 指向 TanStack Router 的 layout route id。布局包会从这些 layout route 下查找可生成菜单的子路由。
AdminLayoutsStorage
interface AdminLayoutsStorage {
get<K extends keyof StorageType.Local>(key: K): StorageType.Local[K] | null;
remove(key: keyof StorageType.Local): void;
set<K extends keyof StorageType.Local>(key: K, value: StorageType.Local[K]): void;
}布局包不绑定具体 storage 实现。应用可以传 @skyroc/utils 的 createStorage 返回值,也可以传自己的适配器。
AdminLayoutProps
interface AdminLayoutProps extends AdminLayoutSlots {
categoryKey?: string;
}interface AdminLayoutSlots {
content?: ReactNode;
footer?: ReactNode;
headerLeftActions?: ReactNode;
headerMiddleActions?: ReactNode;
headerRightActions?: ReactNode;
logo?: ReactNode;
logoComponent?: AdminLayoutLogoComponent;
logoTitle?: ReactNode;
logoTo?: Router.RoutePath;
}插槽行为见 布局插槽。
菜单数据 API
useMenus
useMenus 管理菜单原始数据,是认证初始化和动态菜单加载的核心 hook。
const {
clearMenus,
getHomeRoute,
home,
initMenus,
menus,
quickReferenceMenus
} = useMenus();返回值:
| 返回值 | 说明 |
|---|---|
home | 当前用户首页路由。静态模式默认来自 defaultHome,动态模式可来自后端。 |
menus | 原始菜单树,类型是 Map<categoryKey, GeneratedMenu[]>。 |
quickReferenceMenus | 快速索引,类型是 Map<categoryKey, Map<path, QuickReferenceMenu>>。 |
initMenus(userInfo) | 根据 routeMode 初始化菜单,并按用户权限过滤。 |
clearMenus() | 清空菜单状态,并恢复默认首页。 |
getHomeRoute() | 从全局 store 同步读取当前首页路由。适合路由重定向等场景。 |
典型用法是在认证初始化成功后调用:
async function initAuth() {
const userInfo = await queryClient.ensureQueryData(queryUserInfoOptions());
await initMenus(userInfo);
return userInfo;
}initMenus 的行为取决于 routeMode:
routeMode | 行为 |
|---|---|
static | 从 routeTree 的 staticData 生成菜单。详见 静态路由菜单生成。 |
dynamic | 调用 loadDynamicRoutes,从后端路由结构生成菜单。详见 动态路由菜单生成。 |
动态模式但未配置 loadDynamicRoutes 时会抛错:
Admin layouts routeMode is dynamic, but loadDynamicRoutes is not configured.useAdminMenus
useAdminMenus 已在 状态与页签 展开。它基于 useMenus 的原始数据,计算当前布局需要的菜单视图:
- 当前菜单分类下的 Ant Design 菜单项。
- 一级、二级、子级菜单。
- 当前路由对应的
selectedKey和openKeys。 routerPushByKey、changeActiveFirstLevelMenuKey等交互方法。
useRoute
useRoute 是布局包对 TanStack Router 当前 match 的标准化封装。
const route = useRoute();返回结构:
interface AdminRouteInfo {
fullPath: string;
originPath: Router.RoutePath;
params: Record<string, unknown>;
pathname: string;
routeId: Router.RouteId;
search: Record<string, unknown>;
searchStr: string;
staticData?: Router.Meta;
}字段说明:
| 字段 | 说明 |
|---|---|
pathname | 当前 URL pathname。 |
search | TanStack Router search 对象。 |
searchStr | 序列化后的 query string。 |
fullPath | pathname + searchStr,用于 tab 激活和跳转。 |
originPath | 去掉末尾斜杠后的 route fullPath,用于菜单 key 匹配。 |
routeId | 当前 TanStack Router route id。 |
staticData | 当前 route 的 Router.Meta。 |
一般业务页面不需要直接使用它;布局内部用它连接菜单、tab 和权限状态。
Router.Meta
@skyroc/web-admin-layouts 依赖共享的 Router.Meta 类型描述路由菜单、权限和 tab 行为。
interface Meta {
i18nKey?: I18n.I18nKey | null;
keepAlive?: boolean | null;
permissions?: string[];
title?: string;
menu?: {
activeMenu?: Router.RoutePath | null;
badge?: Router.MenuBadge | null;
extra?: Router.Extra | null;
hide?: boolean | null;
icon?: string;
localIcon?: string;
meta?: { key: string; value: string }[] | null;
order?: number | null;
type?: string | null;
};
tab?: {
fixedIndex?: number | null;
hide?: boolean | null;
icon?: string;
localIcon?: string;
};
}MenuBadge
interface MenuBadge {
showZero?: boolean;
type?: 'dot' | 'normal';
value?: number | string | null;
valueKey?: string;
variant?: 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info';
}valueKey 优先读取动态值表。如果动态值表里没有对应 key,则回退到 value。
const { setMenuBadgeValue } = useAdminMenuBadges();
setMenuBadgeValue('todo.count', 7);Badge 动态值 Action
这组 action 和 useAdminMenuBadges 使用同一份全局值表。区别是:hook 适合 React 组件内使用,action 适合请求拦截器、消息通道、store action 或其他非组件代码。
setMenuBadgeValue
更新一个 badge 值。
setMenuBadgeValue('message.unread', 8);value 可以是数字、字符串、null 或 undefined。传 undefined 会保留 key,但渲染层会把它当作无值处理;如果要删除 key,使用 clearMenuBadgeValues(['message.unread'])。
setMenuBadgeValues
批量合并多个 badge 值。
setMenuBadgeValues({
'audit.pending': 12,
'message.unread': 8,
'todo.count': 3
});它是浅合并,不会清空未传入的 key。
clearMenuBadgeValues
不传参数时清空所有动态值:
clearMenuBadgeValues();传 key 列表时只清理指定项:
clearMenuBadgeValues(['message.unread', 'todo.count']);权限函数
权限函数都读取 setupAdminLayouts 中配置的 permissionSuperRole。如果用户角色包含超级角色,则直接通过权限校验。
hasAnyRoutePermission
校验一组权限是否被当前用户角色命中。
const visible = hasAnyRoutePermission(['R_ADMIN', 'R_EDITOR'], userInfo);规则:
permissions为空或未传时返回true。userInfo.roles包含permissionSuperRole时返回true。- 其他情况只要命中任意一个 permission 就返回
true。
hasRoutePermission
校验单个 route meta。
const visible = hasRoutePermission(route.staticData, userInfo);它等价于读取 routeMeta?.permissions 后调用 hasAnyRoutePermission。
hasMatchedRoutePermission
校验当前 TanStack Router matches 链路。适合在后台 layout route 的 beforeLoad 中使用。
export const Route = createFileRoute('/(admin)')({
beforeLoad: async ({ context, matches }) => {
const userInfo = context.isAuthInitialized ? context.userInfo : await context.initAuth();
if (!hasMatchedRoutePermission(matches, userInfo)) {
throw redirect({ to: '/403' });
}
}
});它要求匹配链路中的每个 route 都通过 hasRoutePermission,因此父级 layout 和子页面权限都会生效。
工具函数
normalizePath
规范化路由路径:非根路径如果以 / 结尾,则移除末尾斜杠。
normalizePath('/home/'); // '/home'
normalizePath('/'); // '/'菜单生成、当前路由匹配和 quick reference 索引都会使用相同规则,避免 /home 和 /home/ 被当成两个菜单 key。
cacheTabs
把当前 tab 状态写入配置的 storage.globalTabs。
cacheTabs();通常不需要页面主动调用。认证退出、用户切换或应用卸载前如果需要保留 tabs,可使用它手动落盘。
MenuNodeCallback
type MenuNodeConfig = Partial<Omit<Api.Route.BackendRoute, 'layout' | 'parentId'>>;
type MenuNodeCallback = (
routeId: Router.RouteId
) => MenuNodeConfig[];menuNodeCallback 用于在菜单生成阶段给指定 route 节点追加补充菜单。普通页面菜单优先写在 route staticData.menu 或后端 BackendRoute.menu;只有 divider、固定入口、聚合入口等“路由树之外的节点”才放在这里。
示例:
export const menuNodeCallback: MenuNodeCallback = routeId => {
if (routeId !== '/(admin)') return [];
return [
{
id: 'admin-divider',
menu: {
order: 9,
type: 'divider'
}
}
];
};GeneratedMenu
GeneratedMenu 是菜单生成器内部和测试中常用的标准菜单结构。
interface GeneratedMenu {
badge?: Router.MenuBadge | null;
children?: GeneratedMenu[];
extra?: Router.Extra | null;
i18nKey?: I18n.I18nKey | null;
icon?: string;
key: string;
localIcon?: string;
order?: number;
path?: Router.RoutePath;
title?: string;
type?: string;
}静态路由、动态路由和 menuNodeCallback 添加的额外菜单都会先转成 GeneratedMenu,再由 renderCommonMenus 转成 Ant Design Menu 项。