generator-mico-cli 0.2.21 → 0.2.23

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.
Files changed (68) hide show
  1. package/README.md +33 -0
  2. package/bin/mico.js +15 -2
  3. package/generators/micro-react/index.js +44 -6
  4. package/generators/micro-react/meta.json +2 -1
  5. package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +14 -4
  6. package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +36 -26
  7. package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +5 -2
  8. package/generators/micro-react/templates/CLAUDE.md +4 -2
  9. package/generators/micro-react/templates/_gitignore +3 -1
  10. package/generators/micro-react/templates/_npmrc +1 -0
  11. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +7 -3
  12. package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +5 -0
  13. package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +5 -0
  14. package/generators/micro-react/templates/apps/layout/config/config.prod.ts +12 -0
  15. package/generators/micro-react/templates/apps/layout/config/routes.ts +0 -5
  16. package/generators/micro-react/templates/apps/layout/docs/feature-/345/276/256/345/211/215/347/253/257/346/250/241/345/274/217.md +4 -2
  17. package/generators/micro-react/templates/apps/layout/docs/feature-/350/217/234/345/215/225/346/235/203/351/231/220/346/216/247/345/210/266.md +116 -56
  18. package/generators/micro-react/templates/apps/layout/docs/feature-/350/267/257/347/224/261/344/270/216/350/217/234/345/215/225/350/247/243/350/200/246.md +179 -0
  19. package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +81 -61
  20. package/generators/micro-react/templates/apps/layout/mock/menus.ts +89 -144
  21. package/generators/micro-react/templates/apps/layout/mock/pages.ts +83 -0
  22. package/generators/micro-react/templates/apps/layout/package.json +1 -0
  23. package/generators/micro-react/templates/apps/layout/src/app.tsx +13 -8
  24. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +118 -43
  25. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +31 -4
  26. package/generators/micro-react/templates/apps/layout/src/common/micro-prefetch.ts +3 -2
  27. package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +45 -0
  28. package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +49 -10
  29. package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +1 -1
  30. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +6 -0
  31. package/generators/micro-react/templates/apps/layout/src/common/theme.ts +0 -2
  32. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.less +0 -1
  33. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +4 -4
  34. package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +4 -5
  35. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +21 -6
  36. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +4 -3
  37. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/micro-app-manager.ts +7 -1
  38. package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +12 -16
  39. package/generators/micro-react/templates/apps/layout/src/global.less +15 -2
  40. package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +3 -2
  41. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +32 -4
  42. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +20 -10
  43. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +75 -38
  44. package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +3 -7
  45. package/generators/micro-react/templates/apps/layout/src/services/user.ts +7 -3
  46. package/generators/micro-react/templates/dev.preset.json +1 -1
  47. package/generators/micro-react/templates/package.json +1 -0
  48. package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +45 -0
  49. package/generators/subapp-react/index.js +206 -6
  50. package/generators/subapp-react/templates/homepage/.env +2 -1
  51. package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +1 -0
  52. package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +1 -0
  53. package/generators/subapp-react/templates/homepage/config/config.prod.ts +8 -0
  54. package/generators/subapp-react/templates/homepage/package.json +1 -0
  55. package/generators/subapp-react/templates/homepage/src/app.tsx +6 -0
  56. package/generators/subapp-umd/ignore-list.json +5 -0
  57. package/generators/subapp-umd/index.js +325 -0
  58. package/generators/subapp-umd/meta.json +11 -0
  59. package/generators/subapp-umd/templates/README.md +94 -0
  60. package/generators/subapp-umd/templates/package.json +35 -0
  61. package/generators/subapp-umd/templates/public/index.html +34 -0
  62. package/generators/subapp-umd/templates/src/App.less +15 -0
  63. package/generators/subapp-umd/templates/src/App.tsx +13 -0
  64. package/generators/subapp-umd/templates/src/index.ts +2 -0
  65. package/generators/subapp-umd/templates/tsconfig.json +27 -0
  66. package/generators/subapp-umd/templates/webpack.config.js +68 -0
  67. package/lib/utils.js +2 -1
  68. package/package.json +1 -1
@@ -6,171 +6,116 @@
6
6
  * - id: 菜单唯一标识
7
7
  * - name: 菜单名称
8
8
  * - type: 'page' | 'group' (page: 页面, group: 分组)
9
- * - path: 路由路径 (group 类型为 null)
9
+ * - path: 路由路径 (type=page 时与 PAGES 中的 route 一致, group 类型为 null)
10
10
  * - icon: 图标名称
11
11
  * - enabled: 是否启用
12
12
  * - sortOrder: 排序权重
13
- * - pageId: 关联的页面 ID
14
- * - page: 页面配置 (微前端入口)
15
- * - htmlUrl: 子应用入口地址
16
- * - jsUrls: 额外 JS 资源
17
- * - cssUrls: 额外 CSS 资源
13
+ * - pageId: 关联的页面 ID (通过 pageId 与 __MICO_PAGES__ 关联)
18
14
  * - children: 子菜单数组
19
15
  */
20
16
 
21
- import type { MenuItem, PageConfig } from '@/common/menu/types';
17
+ import type { MenuItem } from '@/common/menu/types';
22
18
 
23
- /** Mock 页面配置 - 只需要核心字段 */
24
- type MockPageConfig = Pick<PageConfig,
25
- | 'id' | 'name' | 'route' | 'enabled' | 'htmlUrl' | 'jsUrls' | 'cssUrls'
26
- | 'adminOnly'
27
- >;
28
-
29
- /** Mock 菜单项 - page 字段使用简化类型,包含多语言字段 */
30
- type MockMenuItem = Omit<MenuItem, 'page' | 'children'> & {
31
- page: MockPageConfig | null;
19
+ /** Mock 菜单项 */
20
+ type MockMenuItem = Omit<MenuItem, 'children'> & {
32
21
  children: MockMenuItem[];
33
- /** 英文名称(用于英文环境权限匹配) */
34
- nameEn?: string;
35
- /** 菜单唯一标识符(用于权限匹配的兜底) */
36
- nameKey?: string;
37
22
  };
38
23
 
39
24
  const mockMenus: MockMenuItem[] = [
40
25
  {
41
- "id": 1,
42
- "name": "首页",
43
- "nameEn": "Home",
44
- "nameKey": "cs_web_menu_home",
45
- "type": "page",
46
- "path": null,
47
- "icon": "Home",
48
- "enabled": true,
49
- "sortOrder": 0,
50
- "pageId": 1,
51
- "page": {
52
- "id": 1,
53
- "name": "home",
54
- "route": "/",
55
- "enabled": true,
56
- "htmlUrl": "",
57
- "jsUrls": [],
58
- "cssUrls": []
59
- },
60
- "children": []
26
+ id: 1,
27
+ name: '首页',
28
+ nameEn: 'Home',
29
+ nameKey: 'cs_web_menu_home',
30
+ type: 'page',
31
+ path: '/',
32
+ icon: 'Home',
33
+ enabled: true,
34
+ sortOrder: 0,
35
+ pageId: 1,
36
+ children: [],
61
37
  },
62
38
  {
63
- "id": 2,
64
- "name": "示例模块",
65
- "nameEn": "Example Module",
66
- "nameKey": "cs_web_menu_example_module",
67
- "type": "group",
68
- "path": null,
69
- "icon": "List",
70
- "enabled": true,
71
- "sortOrder": 1,
72
- "pageId": null,
73
- "page": null,
74
- "children": [
39
+ id: 2,
40
+ name: '示例模块',
41
+ nameEn: 'Example Module',
42
+ nameKey: 'cs_web_menu_example_module',
43
+ type: 'group',
44
+ path: null,
45
+ icon: 'List',
46
+ enabled: true,
47
+ sortOrder: 1,
48
+ pageId: null,
49
+ children: [
75
50
  {
76
- "id": 3,
77
- "name": "示例页面",
78
- "nameEn": "Example Page",
79
- "nameKey": "cs_web_menu_example_page",
80
- "type": "page",
81
- "path": "/example/page",
82
- "icon": "File",
83
- "enabled": true,
84
- "sortOrder": 1,
85
- "pageId": 45,
86
- "page": {
87
- "id": 45,
88
- "name": "example-page",
89
- "route": "/example/page",
90
- "enabled": true,
91
- "htmlUrl": "",
92
- "jsUrls": [],
93
- "cssUrls": []
94
- },
95
- "children": []
96
- }
97
- ]
51
+ id: 3,
52
+ name: '示例页面',
53
+ nameEn: 'Example Page',
54
+ nameKey: 'cs_web_menu_example_page',
55
+ type: 'page',
56
+ path: '/example/page',
57
+ icon: 'File',
58
+ enabled: true,
59
+ sortOrder: 1,
60
+ pageId: 45,
61
+ children: [],
62
+ },
63
+ ],
98
64
  },
99
65
  {
100
- "id": 5,
101
- "name": "微应用示例",
102
- "nameEn": "Microapp Example",
103
- "nameKey": "cs_web_menu_microapp_example",
104
- "type": "group",
105
- "path": null,
106
- "icon": "Apps",
107
- "enabled": true,
108
- "sortOrder": 2,
109
- "pageId": null,
110
- "page": null,
111
- "children": [
66
+ id: 5,
67
+ name: '微应用示例',
68
+ nameEn: 'Microapp Example',
69
+ nameKey: 'cs_web_menu_microapp_example',
70
+ type: 'group',
71
+ path: null,
72
+ icon: 'Apps',
73
+ enabled: true,
74
+ sortOrder: 2,
75
+ pageId: null,
76
+ children: [
112
77
  {
113
- "id": 6,
114
- "name": "子应用页面",
115
- "nameEn": "Subapp Page",
116
- "nameKey": "cs_web_menu_subapp_page",
117
- "type": "page",
118
- "path": null,
119
- "icon": "Desktop",
120
- "enabled": true,
121
- "sortOrder": 1,
122
- "pageId": 55,
123
- "page": {
124
- "id": 55,
125
- "name": "subapp-example",
126
- "route": "/subapp",
127
- "enabled": true,
128
- "htmlUrl": "//localhost:8010",
129
- "jsUrls": [],
130
- "cssUrls": []
131
- },
132
- "children": []
133
- }
134
- ]
78
+ id: 6,
79
+ name: '子应用页面',
80
+ nameEn: 'Subapp Page',
81
+ nameKey: 'cs_web_menu_subapp_page',
82
+ type: 'page',
83
+ path: '/subapp/*',
84
+ icon: 'Desktop',
85
+ enabled: true,
86
+ sortOrder: 1,
87
+ pageId: 55,
88
+ children: [],
89
+ },
90
+ ],
135
91
  },
136
92
  {
137
- "id": 7,
138
- "name": "外部链接",
139
- "nameEn": "External Link",
140
- "nameKey": "cs_web_menu_external_link",
141
- "type": "link",
142
- "path": "https://github.com",
143
- "icon": "Link",
144
- "enabled": true,
145
- "sortOrder": 3,
146
- "pageId": null,
147
- "page": null,
148
- "children": []
93
+ id: 7,
94
+ name: '外部链接',
95
+ nameEn: 'External Link',
96
+ nameKey: 'cs_web_menu_external_link',
97
+ type: 'link',
98
+ path: 'https://github.com',
99
+ icon: 'Link',
100
+ enabled: true,
101
+ sortOrder: 3,
102
+ pageId: null,
103
+ children: [],
149
104
  },
150
105
  {
151
- "id": 8,
152
- "name": "权限管理",
153
- "nameEn": "Permission Management",
154
- "nameKey": "cs_web_menu_permission_management",
155
- "type": "page",
156
- "path": "/permission",
157
- "icon": "Permission",
158
- "enabled": true,
159
- "sortOrder": 3,
160
- "pageId": null,
161
- "adminOnly": true,
162
- "page": {
163
- "id": 8,
164
- "name": "permission",
165
- "route": "/permission",
166
- "enabled": true,
167
- "adminOnly": true,
168
- "htmlUrl": "https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.1/permission/index.html",
169
- "jsUrls": [],
170
- "cssUrls": []
171
- },
172
- "children": []
173
- }
174
- ]
106
+ id: 8,
107
+ name: '权限管理',
108
+ nameEn: 'Permission Management',
109
+ nameKey: 'cs_web_menu_permission_management',
110
+ type: 'page',
111
+ path: '/permission',
112
+ icon: 'Permission',
113
+ enabled: true,
114
+ sortOrder: 3,
115
+ pageId: null,
116
+ adminOnly: true,
117
+ children: [],
118
+ },
119
+ ];
175
120
 
176
121
  export default mockMenus;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Mock 页面数据
3
+ * 用于开发环境测试,模拟 window.__MICO_PAGES__ 数据
4
+ *
5
+ * 页面结构说明:
6
+ * - id: 页面唯一标识
7
+ * - name: 页面名称
8
+ * - route: 路由路径
9
+ * - htmlUrl: 微应用 HTML 入口 URL
10
+ * - jsUrls: 额外 JS 资源
11
+ * - cssUrls: 额外 CSS 资源
12
+ * - prefixPath: 路由前缀路径
13
+ * - routeMode: 路由匹配模式 (prefix | exact)
14
+ * - enabled: 是否启用
15
+ * - accessControlEnabled: 是否开启权限控制
16
+ * - adminOnly: 是否仅超级管理员可见
17
+ * - routeKey: 路由权限标识
18
+ * - mainDocumentId: 关联的主文档 ID
19
+ * - version: 当前版本号
20
+ */
21
+
22
+ import type { PublicPageItem } from '@/common/menu/types';
23
+
24
+ const mockPages: PublicPageItem[] = [
25
+ {
26
+ id: 125,
27
+ name: '登录页',
28
+ nameEn: 'Login',
29
+ nameKey: 'page.user.login',
30
+ route: '/user/login',
31
+ htmlUrl:
32
+ 'https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.4/login/index.html',
33
+ jsUrls: [],
34
+ cssUrls: [],
35
+ prefixPath: '/user',
36
+ routeMode: 'exact',
37
+ enabled: true,
38
+ accessControlEnabled: false,
39
+ adminOnly: false,
40
+ routeKey: null,
41
+ mainDocumentId: 59,
42
+ version: 'v2026.02.26-04.21-000',
43
+ },
44
+ {
45
+ id: 124,
46
+ name: '权限管理',
47
+ nameEn: 'Permission Management',
48
+ nameKey: 'page.permission',
49
+ route: '/permission',
50
+ htmlUrl:
51
+ 'https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.4/permission/index.html',
52
+ jsUrls: [],
53
+ cssUrls: [],
54
+ prefixPath: '',
55
+ routeMode: 'prefix',
56
+ enabled: true,
57
+ accessControlEnabled: true,
58
+ adminOnly: true,
59
+ routeKey: null,
60
+ mainDocumentId: 59,
61
+ version: 'v2026.02.26-04.13-419',
62
+ },
63
+ {
64
+ id: 115,
65
+ name: '兜底',
66
+ nameEn: 'Fallback',
67
+ nameKey: 'page.fallback',
68
+ route: '/*',
69
+ htmlUrl: '',
70
+ jsUrls: [],
71
+ cssUrls: [],
72
+ prefixPath: '',
73
+ routeMode: 'prefix',
74
+ enabled: true,
75
+ accessControlEnabled: true,
76
+ adminOnly: false,
77
+ routeKey: null,
78
+ mainDocumentId: 59,
79
+ version: 'v2026.01.26-14.25-095',
80
+ },
81
+ ];
82
+
83
+ export default mockPages;
@@ -32,6 +32,7 @@
32
32
  "spark-md5": "^3.0.2"
33
33
  },
34
34
  "devDependencies": {
35
+ "@common-web/sentry": "^0.0.3",
35
36
  "@types/react": "^18.0.33",
36
37
  "@types/react-dom": "^18.0.11",
37
38
  "@types/spark-md5": "^3.0.5",
@@ -1,15 +1,16 @@
1
1
  import { history, type RequestConfig } from '@umijs/max';
2
2
  import { addGlobalUncaughtErrorHandler } from 'qiankun';
3
+ import { SentryErrorBoundary } from '@common-web/sentry';
3
4
  import { errorConfig } from './requestErrorConfig';
4
5
  // 将 @mico-platform/ui 暴露到 window,供子应用 externals 使用
5
6
  import * as micoUI from '@mico-platform/ui';
6
7
  import React from 'react';
7
- import ReactDOM from 'react-dom/client';
8
+ import ReactDOM from 'react-dom';
8
9
 
9
10
  import { getStoredAuthToken } from './common/auth/auth-manager';
10
11
  import type { IUserInfo } from './common/auth/type';
11
12
  import { fetchUserInfo } from './services/user';
12
- import { extractRoutes, getWindowMenus } from './common/menu';
13
+ import { getDynamicRoutes, isPageAuthFree } from './common/menu';
13
14
  import {
14
15
  clearMicroAppProps,
15
16
  type IMicroAppProps,
@@ -23,6 +24,7 @@ import { initTheme } from './common/theme';
23
24
  import MicroAppLoader from './components/MicroAppLoader';
24
25
  import { isNoAuthRoute } from '@/constants';
25
26
  import { getCurrentLocale } from '@/common/locale';
27
+ import '@mico-platform/theme/dist/css/theme.css';
26
28
  import './global.less';
27
29
 
28
30
  // ==================== qiankun 全局错误处理 ====================
@@ -127,9 +129,10 @@ export async function getInitialState(): Promise<{
127
129
 
128
130
  const { location } = history;
129
131
  const noAuthRoute = isNoAuthRoute(location.pathname);
132
+ const skipAuth = noAuthRoute || isPageAuthFree(location.pathname);
130
133
 
131
134
  // 非免认证路由:走 SSO 流程
132
- if (!noAuthRoute) {
135
+ if (!skipAuth) {
133
136
  await ensureSsoSession();
134
137
  }
135
138
 
@@ -145,7 +148,7 @@ export async function getInitialState(): Promise<{
145
148
  }
146
149
 
147
150
  // 非免认证路由且没有 token,跳转到 SSO 登录
148
- if (!noAuthRoute) {
151
+ if (!skipAuth) {
149
152
  handleAuthFailureRedirect();
150
153
  // 返回空状态,页面会被重定向
151
154
  return {
@@ -168,6 +171,10 @@ export const request: RequestConfig = {
168
171
  ...errorConfig,
169
172
  };
170
173
 
174
+ export function rootContainer(container: React.ReactNode) {
175
+ return <SentryErrorBoundary>{container}</SentryErrorBoundary>;
176
+ }
177
+
171
178
  /**
172
179
  * Umi 路由类型定义
173
180
  */
@@ -182,15 +189,13 @@ interface UmiRoute {
182
189
 
183
190
  /**
184
191
  * @name 动态路由配置
185
- * window.__MICO_MENUS__ 获取菜单数据,生成动态路由
192
+ * 优先从 window.__MICO_PAGES__ 获取页面数据,无数据时降级到 window.__MICO_MENUS__
186
193
  * @doc https://umijs.org/docs/max/router#%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1
187
194
  */
188
195
  export function patchClientRoutes({ routes }: { routes: UmiRoute[] }) {
189
- const menus = getWindowMenus();
190
- const dynamicRoutes = extractRoutes(menus);
196
+ const dynamicRoutes = getDynamicRoutes();
191
197
 
192
198
  console.log('[app.tsx] patchClientRoutes:', {
193
- menus,
194
199
  dynamicRoutes,
195
200
  routes,
196
201
  });
@@ -1,15 +1,82 @@
1
- import { getIntl } from '@umijs/max';
2
- import { isAuthDisabled, isNoPermissionRoute } from '@/constants';
3
- import type { MenuItem, ParsedMenuItem, ParsedRoute } from './types';
4
1
  import { getCurrentLocale, LOCALE } from '@/common/locale';
2
+ import { getMenuPage, getPages } from '@/common/portal-data';
3
+ import { isAuthDisabled, isNoPermissionRoute } from '@/constants';
4
+ import { getIntl } from '@umijs/max';
5
+ import type {
6
+ MenuItem,
7
+ ParsedMenuItem,
8
+ ParsedRoute,
9
+ PublicPageItem,
10
+ } from './types';
11
+
12
+ /**
13
+ * 从页面数据提取路由配置
14
+ */
15
+ export const extractRoutesFromPages = (
16
+ pages: PublicPageItem[],
17
+ ): ParsedRoute[] => {
18
+ return pages
19
+ .filter((page) => page.enabled)
20
+ .map((page) => {
21
+ const hasEntry = page.htmlUrl || (page.jsUrls && page.jsUrls.length > 0);
22
+ return {
23
+ path: page.route,
24
+ name: page.name,
25
+ icon: '',
26
+ loadType: (hasEntry ? 'microapp' : 'internal') as
27
+ | 'internal'
28
+ | 'microapp',
29
+ entry: page.htmlUrl || page.jsUrls?.[0] || undefined,
30
+ };
31
+ });
32
+ };
5
33
 
6
34
  /**
7
- * 获取 window 上挂载的菜单数据
35
+ * 获取动态路由(从 __MICO_PAGES__)
8
36
  */
9
- export const getWindowMenus = (): MenuItem[] => {
10
- if (typeof window === 'undefined') return [];
11
- const menus = window.__MICO_MENUS__;
12
- return Array.isArray(menus) ? menus : [];
37
+ export const getDynamicRoutes = (): ParsedRoute[] => {
38
+ return extractRoutesFromPages(getPages());
39
+ };
40
+
41
+ /**
42
+ * 根据路径查找对应的页面配置
43
+ * 匹配逻辑与 findRouteByPath 一致(精确匹配 + 通配符匹配)
44
+ */
45
+ /**
46
+ * 判断指定路径的页面是否为免认证页面
47
+ * 当 accessControlEnabled === false 时,跳过 SSO 认证和权限校验
48
+ */
49
+ export const isPageAuthFree = (pathname: string): boolean => {
50
+ const page = findPageByPath(getPages(), pathname);
51
+ return page?.accessControlEnabled === false;
52
+ };
53
+
54
+ export const findPageByPath = (
55
+ pages: PublicPageItem[],
56
+ pathname: string,
57
+ ): PublicPageItem | undefined => {
58
+ let exact: PublicPageItem | undefined;
59
+ let bestWildcard: { page: PublicPageItem; basePath: string } | undefined;
60
+
61
+ for (const page of pages) {
62
+ if (!page.enabled) continue;
63
+
64
+ if (page.route === pathname) {
65
+ exact = page;
66
+ continue;
67
+ }
68
+
69
+ if (page.route.endsWith('/*')) {
70
+ const basePath = page.route.slice(0, -2);
71
+ if (pathname === basePath || pathname.startsWith(`${basePath}/`)) {
72
+ if (!bestWildcard || basePath.length > bestWildcard.basePath.length) {
73
+ bestWildcard = { page, basePath };
74
+ }
75
+ }
76
+ }
77
+ }
78
+
79
+ return exact || bestWildcard?.page;
13
80
  };
14
81
 
15
82
  export interface MenuFilterOptions {
@@ -19,7 +86,7 @@ export interface MenuFilterOptions {
19
86
  sideMenus?: string[];
20
87
  }
21
88
 
22
- const isSuperuserUser = (value?: boolean | number): boolean => {
89
+ export const isSuperuserUser = (value?: boolean | number): boolean => {
23
90
  return value === true || value === 1;
24
91
  };
25
92
 
@@ -67,12 +134,12 @@ const isMenuAllowed = (
67
134
  item: MenuItem,
68
135
  options: MenuFilterOptions,
69
136
  ): boolean => {
70
-
71
137
  // 关闭权限控制时,所有菜单都允许访问
72
138
  if (isAuthDisabled()) return true;
73
139
 
74
140
  // 免权限校验路由,始终允许访问
75
- if (item.page?.route && isNoPermissionRoute(item.page.route)) {
141
+ const itemRoute = item.path ?? getMenuPage(item)?.route;
142
+ if (itemRoute && isNoPermissionRoute(itemRoute)) {
76
143
  return true;
77
144
  }
78
145
 
@@ -155,15 +222,13 @@ export const filterMenuItems = (
155
222
  * 判断页面的加载类型
156
223
  * 有 htmlUrl 或 jsUrls 时使用 qiankun 微应用加载
157
224
  */
158
- const getLoadType = (page: MenuItem['page']): 'internal' | 'microapp' => {
225
+ const getLoadType = (
226
+ page: PublicPageItem | null | undefined,
227
+ ): 'internal' | 'microapp' => {
159
228
  if (!page) return 'internal';
160
-
161
- // 有 htmlUrl 或 jsUrls 使用 qiankun 微应用加载
162
229
  if (page.htmlUrl || (page.jsUrls && page.jsUrls.length > 0)) {
163
230
  return 'microapp';
164
231
  }
165
-
166
- // 否则使用内部路由
167
232
  return 'internal';
168
233
  };
169
234
 
@@ -171,7 +236,9 @@ const getLoadType = (page: MenuItem['page']): 'internal' | 'microapp' => {
171
236
  * 获取微应用入口 URL
172
237
  * 优先使用 htmlUrl,其次使用 jsUrls[0]
173
238
  */
174
- const getEntry = (page: MenuItem['page']): string | undefined => {
239
+ const getEntry = (
240
+ page: PublicPageItem | null | undefined,
241
+ ): string | undefined => {
175
242
  if (!page) return undefined;
176
243
  return page.htmlUrl || page.jsUrls?.[0] || undefined;
177
244
  };
@@ -197,17 +264,19 @@ export const extractRoutes = (
197
264
 
198
265
  const menuPath = buildMenuPath(parentPath, getMenuIdentifierDefault(item));
199
266
 
200
- if (item.type === 'page' && item.page && item.page.enabled) {
201
- const loadType = getLoadType(item.page);
202
- routes.push({
203
- path: item.page.route,
204
- name: item.name,
205
- nameEn: item.nameEn,
206
- icon: item.icon,
207
- loadType,
208
- entry: getEntry(item.page),
209
- pageConfig: item.page,
210
- });
267
+ if (item.type === 'page' && item.pageId) {
268
+ const page = getMenuPage(item);
269
+ if (page && page.enabled) {
270
+ routes.push({
271
+ path: page.route,
272
+ name: item.name,
273
+ nameEn: item.nameEn,
274
+ icon: item.icon,
275
+ loadType: getLoadType(page),
276
+ entry: getEntry(page),
277
+ pageConfig: page,
278
+ });
279
+ }
211
280
  }
212
281
 
213
282
  if (item.children && item.children.length > 0) {
@@ -219,18 +288,25 @@ export const extractRoutes = (
219
288
  };
220
289
 
221
290
  /**
222
- * 获取菜单项的国际化名称
223
- * - 使用 key 作为 intl key 查找翻译
224
- * - 如果 intl 中没有对应的 key,则使用 fallbackName 作为兜底
225
- * - 如果没有传入 fallbackName,则使用 key 本身作为兜底
226
- *
227
- * @param key 国际化 key
228
- * @param fallbackName 保底名称(可选,默认使用 key)
229
- * @returns 翻译后的菜单名称
291
+ * 获取菜单项的显示名称
292
+ * 优先级:name/nameEn > nameKey(走 intl 国际化) > 兜底 name
230
293
  */
231
- export const getMenuLabel = (key: string, fallbackName?: string): string => {
232
- const intl = getIntl();
233
- return intl.formatMessage({ id: key, defaultMessage: fallbackName ?? key });
294
+ export const getMenuLabel = (
295
+ fields: { name?: string; nameEn?: string; nameKey?: string },
296
+ isChinese: boolean,
297
+ ): string => {
298
+ const directName = isChinese ? fields.name : fields.nameEn;
299
+ if (directName) return directName;
300
+
301
+ if (fields.nameKey) {
302
+ const intl = getIntl();
303
+ return intl.formatMessage({
304
+ id: fields.nameKey,
305
+ defaultMessage: fields.nameKey,
306
+ });
307
+ }
308
+
309
+ return fields.name || '';
234
310
  };
235
311
 
236
312
  /**
@@ -244,18 +320,17 @@ export const parseMenuItems = (items: MenuItem[]): ParsedMenuItem[] => {
244
320
  .filter((item) => item.enabled)
245
321
  .sort((a, b) => a.sortOrder - b.sortOrder)
246
322
  .map((item) => {
247
- const name = getMenuIdentifier(item, isChinese);
248
323
  const parsed: ParsedMenuItem = {
249
324
  key: String(item.id),
250
- label: item.nameKey ? getMenuLabel(item.nameKey, name) : name,
325
+ label: getMenuLabel(item, isChinese),
251
326
  icon: item.icon,
252
327
  type: item.type,
253
328
  };
254
329
 
255
330
  if (item.type === 'link' && item.path) {
256
331
  parsed.path = item.path;
257
- } else if (item.type === 'page' && item.page) {
258
- parsed.path = item.page.route;
332
+ } else if (item.type === 'page') {
333
+ parsed.path = item.path ?? getMenuPage(item)?.route;
259
334
  }
260
335
 
261
336
  if (item.children && item.children.length > 0) {