Skyroc Web Kit
Admin Layouts

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';

主要导出:

导出类型说明
AdminLayoutComponent应用级后台布局壳。插槽细节见 布局插槽
setupAdminLayoutsFunction启动阶段注入布局全局配置。
useMenusHook初始化和读取菜单原始数据。
useAdminMenusHook读取当前布局菜单视图和菜单交互方法。详见 状态与页签
useAdminStateHook读取和修改布局运行时状态。详见 状态与页签
useAdminTabHook读取和操作路由页签。详见 状态与页签
useAdminMenuBadgesHook读取和更新菜单 badge 动态值。详见 状态与页签
setMenuBadgeValueFunction非组件环境更新单个 badge 动态值。
setMenuBadgeValuesFunction非组件环境批量更新 badge 动态值。
clearMenuBadgeValuesFunction清理全部或部分 badge 动态值。
hasAnyRoutePermissionFunction校验一组权限是否命中当前用户角色。
hasRoutePermissionFunction校验单个 route meta 权限。
hasMatchedRoutePermissionFunction校验当前 TanStack Router matches 链路权限。
normalizePathFunction规范化路由 path,去掉非根路径末尾斜杠。
useRouteHook读取当前 TanStack Router match,并返回布局包内部使用的标准 route 信息。
cacheTabsFunction将当前 tab 状态写入配置的 storage。通常由认证退出或布局 effect 调用。

核心入口

AdminLayout

AdminLayout 是应用级后台壳。业务应用通常只在后台 layout route 中渲染一次。

<AdminLayout
  footer={<AdminFooter />}
  headerMiddleActions={<NotificationButton />}
  headerRightActions={<UserAvatar />}
  logo={<SystemLogo />}
  logoTitle="Skyroc Admin"
/>

它内部会组合:

  • @skyroc/materials 的底层布局容器。
  • AdminHeaderAdminSiderAdminMenuAdminTabAdminContent
  • ThemeDrawerAdminEffect 和 tab effect。
  • useSettingsThemeuseAdminStateuseAdminMenus 计算出的布局状态。

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菜单模式:staticdynamic
routeTreeTanStack Router 生成的 routeTree。
menuCategories菜单分类配置。
storage布局使用的 local storage 适配器。
loadDynamicRoutes动态菜单加载器。
menuNodeCallback菜单节点扩展回调。
extras自定义菜单 extra 组件注册表。
permissionSuperRole超级角色标识。

AdminLayoutsDynamicRoutes

动态菜单加载器需要返回这个结构:

interface AdminLayoutsDynamicRoutes {
  home?: Router.RoutePath;
  routes: Api.Route.BackendRoute[];
}

home 会覆盖 defaultHomeroutes 会走动态菜单生成链路,最终生成 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/utilscreateStorage 返回值,也可以传自己的适配器。

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行为
staticrouteTreestaticData 生成菜单。详见 静态路由菜单生成
dynamic调用 loadDynamicRoutes,从后端路由结构生成菜单。详见 动态路由菜单生成

动态模式但未配置 loadDynamicRoutes 时会抛错:

Admin layouts routeMode is dynamic, but loadDynamicRoutes is not configured.

useAdminMenus

useAdminMenus 已在 状态与页签 展开。它基于 useMenus 的原始数据,计算当前布局需要的菜单视图:

  • 当前菜单分类下的 Ant Design 菜单项。
  • 一级、二级、子级菜单。
  • 当前路由对应的 selectedKeyopenKeys
  • routerPushByKeychangeActiveFirstLevelMenuKey 等交互方法。

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。
searchTanStack Router search 对象。
searchStr序列化后的 query string。
fullPathpathname + 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;
  };
}
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 可以是数字、字符串、nullundefined。传 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,可使用它手动落盘。

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 项。

On this page