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.
- package/README.md +33 -0
- package/bin/mico.js +15 -2
- package/generators/micro-react/index.js +44 -6
- package/generators/micro-react/meta.json +2 -1
- package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +14 -4
- package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +36 -26
- package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +5 -2
- package/generators/micro-react/templates/CLAUDE.md +4 -2
- package/generators/micro-react/templates/_gitignore +3 -1
- package/generators/micro-react/templates/_npmrc +1 -0
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +7 -3
- package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +5 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +5 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.ts +12 -0
- package/generators/micro-react/templates/apps/layout/config/routes.ts +0 -5
- 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
- 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
- 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
- package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +81 -61
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +89 -144
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +83 -0
- package/generators/micro-react/templates/apps/layout/package.json +1 -0
- package/generators/micro-react/templates/apps/layout/src/app.tsx +13 -8
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +118 -43
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +31 -4
- package/generators/micro-react/templates/apps/layout/src/common/micro-prefetch.ts +3 -2
- package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +45 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +49 -10
- package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +6 -0
- package/generators/micro-react/templates/apps/layout/src/common/theme.ts +0 -2
- package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.less +0 -1
- package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +4 -4
- package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +4 -5
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +21 -6
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +4 -3
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/micro-app-manager.ts +7 -1
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +12 -16
- package/generators/micro-react/templates/apps/layout/src/global.less +15 -2
- package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +3 -2
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +32 -4
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +20 -10
- package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +75 -38
- package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +3 -7
- package/generators/micro-react/templates/apps/layout/src/services/user.ts +7 -3
- package/generators/micro-react/templates/dev.preset.json +1 -1
- package/generators/micro-react/templates/package.json +1 -0
- package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +45 -0
- package/generators/subapp-react/index.js +206 -6
- package/generators/subapp-react/templates/homepage/.env +2 -1
- package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +1 -0
- package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +1 -0
- package/generators/subapp-react/templates/homepage/config/config.prod.ts +8 -0
- package/generators/subapp-react/templates/homepage/package.json +1 -0
- package/generators/subapp-react/templates/homepage/src/app.tsx +6 -0
- package/generators/subapp-umd/ignore-list.json +5 -0
- package/generators/subapp-umd/index.js +325 -0
- package/generators/subapp-umd/meta.json +11 -0
- package/generators/subapp-umd/templates/README.md +94 -0
- package/generators/subapp-umd/templates/package.json +35 -0
- package/generators/subapp-umd/templates/public/index.html +34 -0
- package/generators/subapp-umd/templates/src/App.less +15 -0
- package/generators/subapp-umd/templates/src/App.tsx +13 -0
- package/generators/subapp-umd/templates/src/index.ts +2 -0
- package/generators/subapp-umd/templates/tsconfig.json +27 -0
- package/generators/subapp-umd/templates/webpack.config.js +68 -0
- package/lib/utils.js +2 -1
- 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
|
|
17
|
+
import type { MenuItem } from '@/common/menu/types';
|
|
22
18
|
|
|
23
|
-
/** Mock
|
|
24
|
-
type
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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;
|
|
@@ -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
|
|
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 {
|
|
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 (!
|
|
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 (!
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
35
|
+
* 获取动态路由(从 __MICO_PAGES__)
|
|
8
36
|
*/
|
|
9
|
-
export const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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 = (
|
|
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 = (
|
|
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.
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
*
|
|
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 = (
|
|
232
|
-
|
|
233
|
-
|
|
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:
|
|
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'
|
|
258
|
-
parsed.path = item.
|
|
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) {
|