@oj-module/runtime 0.2.2 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import type { RbacSettingItem } from "./types";
1
2
  /** 展示名:title 缺省回落机器名。 */
2
3
  export declare function roleLabel(role: {
3
4
  name: string;
@@ -26,3 +27,20 @@ export declare function groupByModule<T extends {
26
27
  module: string;
27
28
  items: T[];
28
29
  }>;
30
+ /** 设置卡片(系统设置页三层嵌套渲染单元,R20):框架卡 module=null,方案卡带所属方案名。 */
31
+ export interface RbacSettingCard {
32
+ /** 方案模块名;框架卡为 null(标题走 system:setting.group.*,与 C4 现状一致) */
33
+ module: string | null;
34
+ /** settings 组 id(sys_setting.group 列) */
35
+ group: string;
36
+ /** 卡片标题 i18n key:方案卡 = `<module>:settings.group.<group>`(C2-22 组标题契约);框架卡 null */
37
+ label_key: string | null;
38
+ items: RbacSettingItem[];
39
+ }
40
+ /**
41
+ * 设置行按模块三层嵌套分组(R20/R24,纯函数):框架卡在前(按 group 列聚合、保持服务端
42
+ * `order by group, key` 次序),方案卡在后(module 名升序、组内按 group 列聚合)。
43
+ * 不按 key 首段切组——框架 seed 存在无点号键(ldap_base_dn),首段不是其模块名;
44
+ * module 列是落库的所有权真源。空行降级返回空数组。
45
+ */
46
+ export declare function groupSettingsByModule(rows: RbacSettingItem[]): RbacSettingCard[];
@@ -1,4 +1,4 @@
1
- import type { AuthSessionPayload, LoginMethods, RbacContact, RbacOplogPage, RbacPermission, RbacRole, RbacSettingGroup, RbacUser } from "./types";
1
+ import type { AuthSessionPayload, LoginMethods, RbacContact, RbacOplogPage, RbacOplogWrite, RbacPermission, RbacRole, RbacSettingGroup, RbacUser, SolutionRouteRow } from "./types";
2
2
  export * from "./types";
3
3
  export declare const fetchRbacRoleList: () => Promise<RbacRole[]>;
4
4
  export declare const fetchRbacPermissionList: () => Promise<RbacPermission[]>;
@@ -25,8 +25,12 @@ export declare function fetchRbacOplog(query: {
25
25
  module?: string;
26
26
  limit?: number;
27
27
  }): Promise<RbacOplogPage>;
28
+ export declare const fetchRbacOplogPost: (body: RbacOplogWrite) => Promise<{
29
+ ok: boolean;
30
+ }>;
28
31
  export declare const fetchRbacSettings: () => Promise<RbacSettingGroup[]>;
29
32
  export declare const fetchRbacUpdateSetting: (key: string, value: string) => Promise<unknown>;
33
+ export declare const fetchSolutionRoutes: () => Promise<SolutionRouteRow[]>;
30
34
  export declare function fetchRbacContacts(keyword: string, limit?: number): Promise<RbacContact[]>;
31
35
  export declare const fetchRbacContactDetail: (id: number) => Promise<RbacContact & {
32
36
  email: string | null;
@@ -61,9 +61,30 @@ export interface RbacSettingGroup {
61
61
  group: string;
62
62
  items: RbacSettingItem[];
63
63
  }
64
+ /** 方案路由行(GET /api/_platform/solutions/routes;P1-14/R21 前端归并数据源,登录即可只读端点) */
65
+ export interface SolutionRouteRow {
66
+ path: string;
67
+ module: string;
68
+ category: "main" | "admin" | "hidden";
69
+ order: number;
70
+ title_key: string | null;
71
+ /** 仅供深检对比,前端不做权限级菜单过滤(R22:后端 403 硬执行) */
72
+ required_permission: string | null;
73
+ /** false = 禁用:仅菜单层过滤(hideInMenu),路由可达性归装载层(D12) */
74
+ enabled: boolean;
75
+ }
64
76
  export interface LoginMethods {
65
77
  methods: Array<"local" | "ldap" | "sso">;
66
78
  }
79
+ /** 操作日志写端入参(POST /api/system/oplog;module 为调用方自身模块名)。脱敏红线:detail 不得含口令/密钥/token。 */
80
+ export interface RbacOplogWrite {
81
+ action: string;
82
+ module: string;
83
+ version?: string;
84
+ target?: string;
85
+ detail?: unknown;
86
+ level?: number;
87
+ }
67
88
  /** 登录响应(auth/login、auth/refresh、auth/password 同形) */
68
89
  export interface AuthSessionPayload {
69
90
  access_token: string;
@@ -6,6 +6,11 @@ export interface AuthType {
6
6
  export interface LoginInfo {
7
7
  username: string;
8
8
  password: string;
9
+ /**
10
+ * 登录方式:local=本地口令(默认),ldap=目录登录;sso 走重定向不参与本载荷。
11
+ * 仅用于把 POST 路由到正确后端端点(auth/login vs auth/ldap-login),默认 local。
12
+ */
13
+ method?: "local" | "ldap" | "sso";
9
14
  }
10
15
  export interface UserInfoType {
11
16
  id: string;
package/dist/index.d.ts CHANGED
@@ -42,12 +42,14 @@ export type { NotificationsApiProvider, SystemApiProvider, UploadApiProvider, }
42
42
  export { useAuthStore } from "./store/auth";
43
43
  export type { AuthProvider } from "./store/auth-provider";
44
44
  export type { HeaderProvider } from "./store/header-provider";
45
+ export { useSolutionRoutes } from "./store/solution-routes";
45
46
  export { useTabsStore } from "./store/tabs";
46
47
  export { useUserStore } from "./store/user";
47
48
  export type { AppInfo } from "./types/app-info";
48
49
  export { getAllExpandedKeys } from "./utils/get-all-expanded-keys";
49
50
  export { getAppInfo } from "./utils/get-app-info";
50
51
  export { getRedirectPath } from "./utils/get-redirect-path";
52
+ export { request } from "./utils/request";
51
53
  export * from "./utils/tree";
52
54
  export type { NoticeCategory, NoticeItem, PushBroadcastInput, PushInput, } from "@oj-module/runtime/notify";
53
55
  export { ErrorBoundary } from "react-error-boundary";
@@ -23,6 +23,7 @@ declare const baseRoutes: ({
23
23
  login?: boolean;
24
24
  internal?: boolean;
25
25
  hideInMenu?: boolean;
26
+ category?: "main" | "admin" | "hidden";
26
27
  iframeLink?: string;
27
28
  externalLink?: string;
28
29
  ignoreAccess?: boolean;
@@ -71,6 +72,7 @@ declare const baseRoutes: ({
71
72
  login?: boolean;
72
73
  internal?: boolean;
73
74
  hideInMenu?: boolean;
75
+ category?: "main" | "admin" | "hidden";
74
76
  iframeLink?: string;
75
77
  externalLink?: string;
76
78
  ignoreAccess?: boolean;
@@ -61,6 +61,12 @@ export interface RouteMeta {
61
61
  * 是否在菜单中隐藏,用于控制某些路由不在侧边栏菜单中显示
62
62
  */
63
63
  hideInMenu?: boolean;
64
+ /**
65
+ * 方案路由分类(P1-14/R21 三值归并,源自 sys_solution_route.category,经
66
+ * mergeSolutionCategories 注写):main=常规组、admin=管理组、hidden=不进菜单。
67
+ * 框架/后端路由未归并时缺省。
68
+ */
69
+ category?: "main" | "admin" | "hidden";
64
70
  /**
65
71
  * iframe链接,如果路由需要在iframe中加载外部页面时使用
66
72
  */
@@ -12,6 +12,7 @@ export declare function ascending(arr: AppRouteRecordRaw[]): ({
12
12
  login?: boolean;
13
13
  internal?: boolean;
14
14
  hideInMenu?: boolean;
15
+ category?: "main" | "admin" | "hidden";
15
16
  iframeLink?: string;
16
17
  externalLink?: string;
17
18
  ignoreAccess?: boolean;
@@ -60,6 +61,7 @@ export declare function ascending(arr: AppRouteRecordRaw[]): ({
60
61
  login?: boolean;
61
62
  internal?: boolean;
62
63
  hideInMenu?: boolean;
64
+ category?: "main" | "admin" | "hidden";
63
65
  iframeLink?: string;
64
66
  externalLink?: string;
65
67
  ignoreAccess?: boolean;
@@ -0,0 +1,21 @@
1
+ import type { SolutionRouteRow } from "../../api/rbac/types";
2
+ import type { AppRouteRecordRaw } from "../types";
3
+ /**
4
+ * 归并产物:路由 + 方案分类注记(P1-14/R21 三值归并)。
5
+ * 形状与 AppRouteRecordRaw 相同,handle 上多出 category(及 hideInMenu/order 覆写)。
6
+ */
7
+ export type AnnotatedRoute = AppRouteRecordRaw;
8
+ /**
9
+ * 方案路由归并(纯函数,R21):按 path 把 sys_solution_route 行注记到已注册路由上。
10
+ *
11
+ * - 三值 category 原样落 handle.category(main=常规组、admin=管理组、hidden=不进菜单);
12
+ * - hidden 与 enabled=0(禁用)复用既有 hideInMenu 通路——只过滤菜单层,路由仍可达
13
+ * (禁用的路由摘除归装载层三处同写,D12);
14
+ * - order 覆写为声明序:框架项保留 100 间隔带、方案项从 200 起编,共用一条序轴;
15
+ * - 未匹配路由(框架/后端)原样保留(同一对象引用),入参不突变;
16
+ * - solRoutes 为空(端点失败/未登录降级空表)时原样返回——降级不阻断,路由照常。
17
+ *
18
+ * title 不在此覆写:entry.ts 是模块元数据唯一来源(module-loader 既有惯例),
19
+ * title_key 随行下发仅供深检对比。
20
+ */
21
+ export declare function mergeSolutionCategories(routes: AppRouteRecordRaw[], solRoutes: SolutionRouteRow[]): AnnotatedRoute[];
package/dist/runtime.js CHANGED
@@ -201,7 +201,8 @@ function mapAuthPayload(data) {
201
201
  };
202
202
  }
203
203
  function fetchLogin(data) {
204
- return request.post("auth/login", { json: data }).json().then((env) => mapAuthPayload(env.data));
204
+ const path = data.method === "ldap" ? "auth/ldap-login" : "auth/login";
205
+ return request.post(path, { json: data }).json().then((env) => mapAuthPayload(env.data));
205
206
  }
206
207
  function fetchLogout(data) {
207
208
  return request.post("auth/logout", { json: { refresh_token: data?.refreshToken ?? "" } }).json().then(() => {});
@@ -255,10 +256,10 @@ function getAppInfo() {
255
256
  return {
256
257
  "pkg": {
257
258
  "name": "@oj-module/runtime",
258
- "version": "0.2.2",
259
+ "version": "0.2.5",
259
260
  "license": "MIT"
260
261
  },
261
- "lastBuildTime": "2026-10-16 22:08:56"
262
+ "lastBuildTime": "2026-10-19 10:55:08"
262
263
  };
263
264
  }
264
265
  var init_get_app_info = __esmMin((() => {}));
@@ -8321,6 +8322,39 @@ function generateMenuItemsFromRoutes(routeList, parentPath = "/") {
8321
8322
  }
8322
8323
  var init_generate_menu_items_from_routes = __esmMin((() => {}));
8323
8324
  //#endregion
8325
+ //#region src/router/utils/merge-solution-categories.ts
8326
+ /**
8327
+ * 方案路由归并(纯函数,R21):按 path 把 sys_solution_route 行注记到已注册路由上。
8328
+ *
8329
+ * - 三值 category 原样落 handle.category(main=常规组、admin=管理组、hidden=不进菜单);
8330
+ * - hidden 与 enabled=0(禁用)复用既有 hideInMenu 通路——只过滤菜单层,路由仍可达
8331
+ * (禁用的路由摘除归装载层三处同写,D12);
8332
+ * - order 覆写为声明序:框架项保留 100 间隔带、方案项从 200 起编,共用一条序轴;
8333
+ * - 未匹配路由(框架/后端)原样保留(同一对象引用),入参不突变;
8334
+ * - solRoutes 为空(端点失败/未登录降级空表)时原样返回——降级不阻断,路由照常。
8335
+ *
8336
+ * title 不在此覆写:entry.ts 是模块元数据唯一来源(module-loader 既有惯例),
8337
+ * title_key 随行下发仅供深检对比。
8338
+ */
8339
+ function mergeSolutionCategories(routes, solRoutes) {
8340
+ if (solRoutes.length === 0) return routes;
8341
+ const byPath = new Map(solRoutes.map((r) => [r.path, r]));
8342
+ return routes.map((route) => {
8343
+ const row = route.path ? byPath.get(route.path) : void 0;
8344
+ if (!row) return route;
8345
+ return {
8346
+ ...route,
8347
+ handle: {
8348
+ ...route.handle,
8349
+ category: row.category,
8350
+ order: row.order,
8351
+ hideInMenu: row.category === "hidden" || !row.enabled || (route.handle.hideInMenu ?? false)
8352
+ }
8353
+ };
8354
+ });
8355
+ }
8356
+ var init_merge_solution_categories = __esmMin((() => {}));
8357
+ //#endregion
8324
8358
  //#region src/router/utils/resolve-login-route.ts
8325
8359
  /**
8326
8360
  * 内外 login 路由去重(P3,login 模块化):唯一合成入口(SRP/DRY),
@@ -8380,18 +8414,20 @@ var init_access = __esmMin((() => {
8380
8414
  init_ascending();
8381
8415
  init_flatten_routes();
8382
8416
  init_generate_menu_items_from_routes();
8417
+ init_merge_solution_categories();
8383
8418
  init_resolve_login_route();
8384
8419
  initialState$1 = {
8385
8420
  wholeMenus: generateMenuItemsFromRoutes(baseRoutes),
8386
8421
  routeList: baseRoutes,
8387
8422
  flatRouteList: flattenRoutes(baseRoutes),
8388
- isAccessChecked: false
8423
+ isAccessChecked: false,
8424
+ solutionRoutes: []
8389
8425
  };
8390
8426
  lastRegisteredRoutes = [];
8391
- useAccessStore = create((set) => ({
8427
+ useAccessStore = create((set, get) => ({
8392
8428
  ...initialState$1,
8393
8429
  setAccessStore: (routes) => {
8394
- const newRoutes = ascending([...resolveLoginRoute(baseRoutes, routes, router, rootRoute), ...routes]);
8430
+ const newRoutes = ascending([...resolveLoginRoute(baseRoutes, routes, router, rootRoute), ...mergeSolutionCategories(routes, get().solutionRoutes)]);
8395
8431
  router.patchRoutes(ROOT_ROUTE_ID, routes);
8396
8432
  const flatRouteList = flattenRoutes(newRoutes);
8397
8433
  const newState = {
@@ -8404,6 +8440,10 @@ var init_access = __esmMin((() => {
8404
8440
  set(() => newState);
8405
8441
  return newState;
8406
8442
  },
8443
+ setSolutionRoutes: (rows) => {
8444
+ set({ solutionRoutes: rows });
8445
+ if (lastRegisteredRoutes.length > 0) get().setAccessStore(lastRegisteredRoutes);
8446
+ },
8407
8447
  reset: () => {
8408
8448
  router._internalSetRoutes(rootRoute);
8409
8449
  set(initialState$1);
@@ -8739,7 +8779,10 @@ var init_request = __esmMin((() => {
8739
8779
  }
8740
8780
  }
8741
8781
  return refreshTokenAndRetry(request, options, refreshToken);
8742
- } else return handleErrorResponse(response);
8782
+ } else {
8783
+ if (options.silentError) return response;
8784
+ return handleErrorResponse(response);
8785
+ }
8743
8786
  }
8744
8787
  return response;
8745
8788
  }]
@@ -8777,11 +8820,13 @@ function fetchRbacOplog(query) {
8777
8820
  for (const [k, v] of Object.entries(query)) if (v != null && v !== "") sp.set(k, String(v));
8778
8821
  return get(`system/oplog?${sp.toString()}`);
8779
8822
  }
8823
+ var fetchRbacOplogPost = (body) => post("system/oplog", body);
8780
8824
  var fetchRbacSettings = () => get("system/settings");
8781
8825
  var fetchRbacUpdateSetting = (key, value) => put("system/settings", {
8782
8826
  key,
8783
8827
  value
8784
8828
  });
8829
+ var fetchSolutionRoutes = () => get("_platform/solutions/routes");
8785
8830
  function fetchRbacContacts(keyword, limit) {
8786
8831
  return get(`_platform/contacts?keyword=${encodeURIComponent(keyword)}${limit ? `&limit=${limit}` : ""}`);
8787
8832
  }
@@ -8827,6 +8872,46 @@ function groupByModule(items) {
8827
8872
  items
8828
8873
  }));
8829
8874
  }
8875
+ /**
8876
+ * 框架自有设置模块(seed.sql 现行集合;新增框架设置模块须同步)。
8877
+ * 方案设置经安装预检强制 `moduleOf(key)===name`(R24),落库 module 列即方案名——
8878
+ * 据此把 sys_setting 行二分为框架区(维持按 group 聚合的现状渲染)与方案区(按模块三层嵌套)。
8879
+ */
8880
+ var FRAMEWORK_SETTING_MODULES = /* @__PURE__ */ new Set([
8881
+ "framework",
8882
+ "market",
8883
+ "ldap",
8884
+ "sso"
8885
+ ]);
8886
+ /**
8887
+ * 设置行按模块三层嵌套分组(R20/R24,纯函数):框架卡在前(按 group 列聚合、保持服务端
8888
+ * `order by group, key` 次序),方案卡在后(module 名升序、组内按 group 列聚合)。
8889
+ * 不按 key 首段切组——框架 seed 存在无点号键(ldap_base_dn),首段不是其模块名;
8890
+ * module 列是落库的所有权真源。空行降级返回空数组。
8891
+ */
8892
+ function groupSettingsByModule(rows) {
8893
+ const framework = /* @__PURE__ */ new Map();
8894
+ const solutions = /* @__PURE__ */ new Map();
8895
+ for (const item of rows) if (FRAMEWORK_SETTING_MODULES.has(item.module)) (framework.get(item.group) ?? framework.set(item.group, []).get(item.group)).push(item);
8896
+ else {
8897
+ const groups = solutions.get(item.module) ?? solutions.set(item.module, /* @__PURE__ */ new Map()).get(item.module);
8898
+ (groups.get(item.group) ?? groups.set(item.group, []).get(item.group)).push(item);
8899
+ }
8900
+ const cards = [];
8901
+ for (const [group, items] of framework) cards.push({
8902
+ module: null,
8903
+ group,
8904
+ label_key: null,
8905
+ items
8906
+ });
8907
+ for (const module of [...solutions.keys()].sort()) for (const [group, items] of solutions.get(module)) cards.push({
8908
+ module,
8909
+ group,
8910
+ label_key: `${module}:settings.group.${group}`,
8911
+ items
8912
+ });
8913
+ return cards;
8914
+ }
8830
8915
  //#endregion
8831
8916
  //#region src/api/system/menu/index.ts
8832
8917
  init_api_provider();
@@ -9545,6 +9630,55 @@ function defineModule(definition) {
9545
9630
  return definition;
9546
9631
  }
9547
9632
  //#endregion
9633
+ //#region src/store/solution-routes.ts
9634
+ init_access();
9635
+ init_auth();
9636
+ var useSolutionRoutes = create((set) => ({
9637
+ routes: [],
9638
+ loaded: false,
9639
+ refresh: async () => {
9640
+ try {
9641
+ const routes = await fetchSolutionRoutes();
9642
+ set({
9643
+ routes,
9644
+ loaded: true
9645
+ });
9646
+ useAccessStore.getState().setSolutionRoutes(routes);
9647
+ } catch {
9648
+ set({
9649
+ routes: [],
9650
+ loaded: true
9651
+ });
9652
+ useAccessStore.getState().setSolutionRoutes([]);
9653
+ }
9654
+ },
9655
+ reset: () => {
9656
+ set({
9657
+ routes: [],
9658
+ loaded: false
9659
+ });
9660
+ useAccessStore.getState().setSolutionRoutes([]);
9661
+ }
9662
+ }));
9663
+ /**
9664
+ * token 就绪监视(R21):登录成功(token 翻转)强制重拉一次;登出清空行并解除归并。
9665
+ * 幂等(armed 防重),模块导入即武装——宿主链(shell 无 AuthGuard)同样生效。
9666
+ */
9667
+ var armed = false;
9668
+ function watchTokenReady() {
9669
+ if (armed) return;
9670
+ armed = true;
9671
+ let lastToken = useAuthStore.getState().token;
9672
+ if (lastToken) useSolutionRoutes.getState().refresh();
9673
+ useAuthStore.subscribe((state) => {
9674
+ if (state.token === lastToken) return;
9675
+ lastToken = state.token;
9676
+ if (state.token) useSolutionRoutes.getState().refresh();
9677
+ else useSolutionRoutes.getState().reset();
9678
+ });
9679
+ }
9680
+ watchTokenReady();
9681
+ //#endregion
9548
9682
  //#region src/utils/get-all-expanded-keys/index.ts
9549
9683
  function getAllExpandedKeys(data, fieldName = "key") {
9550
9684
  return data.flatMap((item) => [item[fieldName], ...item.children?.length ? getAllExpandedKeys(item.children, fieldName) : []]);
@@ -27594,10 +27728,11 @@ init_tabs();
27594
27728
  init_user();
27595
27729
  init_get_app_info();
27596
27730
  init_get_redirect_path();
27731
+ init_request();
27597
27732
  init_user$1();
27598
27733
  init_local_icons();
27599
27734
  init_ri();
27600
27735
  init_menu_icons();
27601
27736
  init_tree();
27602
27737
  //#endregion
27603
- export { AccessControl, AccessControlRoles, AntdApp, BasicButton, BasicContent, BasicTable, EmbeddedIcon, ErrorBoundary, ExternalIcon, FormAvatarItem, FormTreeItem, Iframe, LayoutCenterIcon, LayoutEffects, LayoutLeftIcon, LayoutRightIcon, MixedNavigationIcon, OutsidePageIcon, PageError, ProfileCardIcon, RiAccountCircleLine, RiContrastFill, RiFullscreenExitLine, RiFullscreenLine, RiMailCheckLine, RiMoonIcon, RiReactjsLine, RiSunIcon, RiUserSettingsLine, ServerErrorIcon, SideNavigationIcon, TopNavigationIcon, TwoColumnNavigationIcon, accessControlCodes, bindingDiff, canAssignRole, canDeleteRole, canEditRoleBindings, defineModule, fetchAddMenuItem, fetchAddRoleItem, fetchAsyncRoutes, fetchChangePassword, fetchDeleteMenuItem, fetchDeleteRoleItem, fetchLdapLogin, fetchLogin, fetchLoginMethods, fetchLogout, fetchMenuByRoleId, fetchMenuList, fetchRbacAdminResetPassword, fetchRbacContactDetail, fetchRbacContacts, fetchRbacCreateRole, fetchRbacCreateUser, fetchRbacDeleteRole, fetchRbacOplog, fetchRbacPermissionList, fetchRbacRoleList, fetchRbacSetForbidden, fetchRbacSettings, fetchRbacUpdateBindings, fetchRbacUpdateSetting, fetchRbacUpdateUserRoles, fetchRbacUserList, fetchRefreshToken, fetchRoleList, fetchRoleMenu, fetchUpdateMenuItem, fetchUpdateRoleItem, fetchUserInfo, filterTree, getAllExpandedKeys, getAppInfo, getBooleanOptions, getModule, getModules, getRedirectPath, getRegisteredApiPrefix, getRegisteredStore, getRoutes, getYesNoOptions, groupByModule, handleTree, loadAll, mapTree, menuIcons, notify, permissionPrefix, roleLabel, setupI18n, traverseTreeValues, unloadModule, useAccess, useAuthStore, usePreferences, useSlotNodes, useTabKey, useTabsStore, useUserStore, external_exports as z };
27738
+ export { AccessControl, AccessControlRoles, AntdApp, BasicButton, BasicContent, BasicTable, EmbeddedIcon, ErrorBoundary, ExternalIcon, FormAvatarItem, FormTreeItem, Iframe, LayoutCenterIcon, LayoutEffects, LayoutLeftIcon, LayoutRightIcon, MixedNavigationIcon, OutsidePageIcon, PageError, ProfileCardIcon, RiAccountCircleLine, RiContrastFill, RiFullscreenExitLine, RiFullscreenLine, RiMailCheckLine, RiMoonIcon, RiReactjsLine, RiSunIcon, RiUserSettingsLine, ServerErrorIcon, SideNavigationIcon, TopNavigationIcon, TwoColumnNavigationIcon, accessControlCodes, bindingDiff, canAssignRole, canDeleteRole, canEditRoleBindings, defineModule, fetchAddMenuItem, fetchAddRoleItem, fetchAsyncRoutes, fetchChangePassword, fetchDeleteMenuItem, fetchDeleteRoleItem, fetchLdapLogin, fetchLogin, fetchLoginMethods, fetchLogout, fetchMenuByRoleId, fetchMenuList, fetchRbacAdminResetPassword, fetchRbacContactDetail, fetchRbacContacts, fetchRbacCreateRole, fetchRbacCreateUser, fetchRbacDeleteRole, fetchRbacOplog, fetchRbacOplogPost, fetchRbacPermissionList, fetchRbacRoleList, fetchRbacSetForbidden, fetchRbacSettings, fetchRbacUpdateBindings, fetchRbacUpdateSetting, fetchRbacUpdateUserRoles, fetchRbacUserList, fetchRefreshToken, fetchRoleList, fetchRoleMenu, fetchSolutionRoutes, fetchUpdateMenuItem, fetchUpdateRoleItem, fetchUserInfo, filterTree, getAllExpandedKeys, getAppInfo, getBooleanOptions, getModule, getModules, getRedirectPath, getRegisteredApiPrefix, getRegisteredStore, getRoutes, getYesNoOptions, groupByModule, groupSettingsByModule, handleTree, loadAll, mapTree, menuIcons, notify, permissionPrefix, request, roleLabel, setupI18n, traverseTreeValues, unloadModule, useAccess, useAuthStore, usePreferences, useSlotNodes, useSolutionRoutes, useTabKey, useTabsStore, useUserStore, external_exports as z };
@@ -1,3 +1,4 @@
1
+ import type { SolutionRouteRow } from "../api/rbac/types";
1
2
  import type { MenuItemType } from "../layout/layout-menu/types";
2
3
  import type { AppRouteRecordRaw } from "../router/types";
3
4
  interface AccessState {
@@ -5,9 +6,12 @@ interface AccessState {
5
6
  routeList: AppRouteRecordRaw[];
6
7
  flatRouteList: Record<string, AppRouteRecordRaw>;
7
8
  isAccessChecked: boolean;
9
+ solutionRoutes: SolutionRouteRow[];
8
10
  }
9
11
  interface AccessAction {
10
- setAccessStore: (routes: AppRouteRecordRaw[]) => AccessState;
12
+ setAccessStore: (routes: AppRouteRecordRaw[]) => Omit<AccessState, "solutionRoutes">;
13
+ /** 方案路由行推送(useSolutionRoutes 就绪/清空时调用),推送后按快照重归并(R21) */
14
+ setSolutionRoutes: (rows: SolutionRouteRow[]) => void;
11
15
  reset: () => void;
12
16
  }
13
17
  export declare const useAccessStore: import("zustand").UseBoundStore<import("zustand").StoreApi<AccessState & AccessAction>>;
@@ -0,0 +1,21 @@
1
+ import type { SolutionRouteRow } from "../api/rbac/types";
2
+ /**
3
+ * 方案路由归并 store(P1-14,R21 独立缓存 store)。
4
+ *
5
+ * 职责:登录即可只读端点(GET /api/_platform/solutions/routes)的唯一前端消费口——
6
+ * - token 就绪强制重拉一次(首登会话归并补齐;现状菜单登记在登录前且 alreadyRegistered
7
+ * 跳过,无此补拉则首登永久降级为无分类,R21);
8
+ * - 端点失败/未登录降级空表不炸(按无分类渲染,路由照常,R21 降级不阻断);
9
+ * - 行就绪即推送 access store(setSolutionRoutes)触发按快照重归并。
10
+ */
11
+ interface SolutionRoutesState {
12
+ /** 未 deprecated 方案路由行(降级时为空表) */
13
+ routes: SolutionRouteRow[];
14
+ /** 已完成过一次拉取(成功或失败) */
15
+ loaded: boolean;
16
+ refresh: () => Promise<void>;
17
+ reset: () => void;
18
+ }
19
+ export declare const useSolutionRoutes: import("zustand").UseBoundStore<import("zustand").StoreApi<SolutionRoutesState>>;
20
+ export declare function watchTokenReady(): void;
21
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@oj-module/runtime",
3
3
  "type": "module",
4
- "version": "0.2.2",
4
+ "version": "0.2.5",
5
5
  "description": "React Antd Admin 框架运行时,供模块工程消费",
6
6
  "license": "MIT",
7
7
  "imports": {