generator-mico-cli 0.2.21 → 0.2.22
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/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/apps/layout/config/config.dev.ts +7 -3
- 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 +5 -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 +107 -48
- 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/menus.ts +89 -144
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +83 -0
- package/generators/micro-react/templates/apps/layout/src/app.tsx +10 -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 +20 -1
- 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/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 +30 -3
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +15 -4
- 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 +2 -2
- package/generators/micro-react/templates/dev.preset.json +1 -1
- package/generators/subapp-react/index.js +160 -2
- package/generators/subapp-react/templates/homepage/.env +2 -1
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +1 -0
- package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +2 -1
- package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +2 -1
- package/generators/subapp-react/templates/homepage/config/config.prod.ts +2 -1
- package/package.json +1 -1
|
@@ -20,8 +20,15 @@ docs/
|
|
|
20
20
|
### 2. Layout 应用文档 (`/apps/layout/docs/`)
|
|
21
21
|
```
|
|
22
22
|
apps/layout/docs/
|
|
23
|
-
├── feature-微前端模式.md
|
|
24
|
-
|
|
23
|
+
├── feature-微前端模式.md # qiankun 微前端架构
|
|
24
|
+
├── feature-路由与菜单解耦.md # PAGES/MENUS 数据源分离、双层权限
|
|
25
|
+
├── feature-菜单权限控制.md # sideMenus 白名单、认证与授权分离
|
|
26
|
+
├── feature-主题色切换.md # 主题系统实现
|
|
27
|
+
├── feature-404页面.md # 404 页面
|
|
28
|
+
├── arch-请求模块.md # HTTP 请求层模块化设计
|
|
29
|
+
├── arch-日志与常量.md # Logger 工具与常量管理
|
|
30
|
+
├── common-intl.md # 国际化公共包
|
|
31
|
+
└── utils-timezone.md # 时区工具
|
|
25
32
|
```
|
|
26
33
|
|
|
27
34
|
### 3. 其他应用文档
|
|
@@ -33,8 +40,9 @@ apps/layout/docs/
|
|
|
33
40
|
|
|
34
41
|
### Step 1: 确认需求涉及的模块
|
|
35
42
|
- 微前端相关 → 阅读 `apps/layout/docs/feature-微前端模式.md`
|
|
43
|
+
- 路由/菜单/权限相关 → 阅读 `apps/layout/docs/feature-路由与菜单解耦.md` 和 `apps/layout/docs/feature-菜单权限控制.md`
|
|
36
44
|
- 主题/样式相关 → 阅读 `apps/layout/docs/feature-主题色切换.md`
|
|
37
|
-
- 请求/认证相关 → 阅读 `
|
|
45
|
+
- 请求/认证相关 → 阅读 `apps/layout/docs/arch-请求模块.md` 和 `src/common/auth/` 源码
|
|
38
46
|
- 菜单相关 → 阅读 `src/common/menu/` 源码
|
|
39
47
|
|
|
40
48
|
### Step 2: 查找相关文档
|
|
@@ -54,9 +62,11 @@ find . -name "*.md" -type f | grep -v node_modules
|
|
|
54
62
|
| 需求类型 | 必读文档 |
|
|
55
63
|
|---------|---------|
|
|
56
64
|
| 新建子应用 | `apps/layout/docs/feature-微前端模式.md` |
|
|
65
|
+
| 路由/页面配置 | `apps/layout/docs/feature-路由与菜单解耦.md` |
|
|
66
|
+
| 权限控制 | `apps/layout/docs/feature-菜单权限控制.md` |
|
|
57
67
|
| 主题适配 | `apps/layout/docs/feature-主题色切换.md` |
|
|
58
68
|
| 提交代码 | `docs/commit-message.md` |
|
|
59
|
-
| API 请求 | `
|
|
69
|
+
| API 请求 | `apps/layout/docs/arch-请求模块.md` |
|
|
60
70
|
| 认证登录 | `src/common/auth/` 目录源码 |
|
|
61
71
|
| 菜单配置 | `src/common/menu/types.ts` 类型定义 |
|
|
62
72
|
|
|
@@ -63,24 +63,27 @@ apps/layout/
|
|
|
63
63
|
const BasicLayout: React.FC = () => {
|
|
64
64
|
const location = useLocation();
|
|
65
65
|
|
|
66
|
-
//
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
return extractRoutes(menus);
|
|
66
|
+
// 所有页面路由(优先 PAGES,降级 MENUS)— 用于路由匹配和渲染
|
|
67
|
+
const allPageRoutes = useMemo(() => {
|
|
68
|
+
return getDynamicRoutes();
|
|
70
69
|
}, []);
|
|
71
70
|
|
|
72
|
-
//
|
|
71
|
+
// 菜单路由(从 MENUS)— 用于权限交叉引用
|
|
72
|
+
const allMenuRoutes = useMemo(() => {
|
|
73
|
+
return extractRoutes(getMenus());
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
// 当前路由配置(从所有页面路由中查找)
|
|
73
77
|
const currentRoute = useMemo(() => {
|
|
74
|
-
return findRouteByPath(
|
|
75
|
-
}, [
|
|
78
|
+
return findRouteByPath(allPageRoutes, location.pathname);
|
|
79
|
+
}, [allPageRoutes, location.pathname]);
|
|
76
80
|
|
|
77
81
|
// 渲染页面内容
|
|
78
82
|
const renderContent = () => {
|
|
79
|
-
// 微应用类型使用 MicroAppLoader
|
|
80
83
|
if (currentRoute?.loadType === 'microapp' && currentRoute.entry) {
|
|
81
|
-
|
|
84
|
+
const appName = getAppNameFromEntry(currentRoute.entry);
|
|
85
|
+
return <MicroAppLoader entry={currentRoute.entry} name={appName} />;
|
|
82
86
|
}
|
|
83
|
-
// 内部路由使用 Outlet
|
|
84
87
|
return <Outlet />;
|
|
85
88
|
};
|
|
86
89
|
|
|
@@ -98,28 +101,36 @@ const BasicLayout: React.FC = () => {
|
|
|
98
101
|
};
|
|
99
102
|
```
|
|
100
103
|
|
|
101
|
-
### 2. 菜单系统 (common/menu/)
|
|
104
|
+
### 2. 全局数据源 (common/portal-data.ts) + 菜单系统 (common/menu/)
|
|
102
105
|
|
|
103
106
|
```typescript
|
|
104
|
-
//
|
|
105
|
-
const
|
|
107
|
+
// 获取页面列表 (window.__MICO_PAGES__)
|
|
108
|
+
const pages = getPages();
|
|
109
|
+
|
|
110
|
+
// 获取菜单树 (window.__MICO_MENUS__)
|
|
111
|
+
const menus = getMenus();
|
|
106
112
|
|
|
107
|
-
//
|
|
108
|
-
const routes =
|
|
113
|
+
// 从页面列表提取动态路由(优先数据源)
|
|
114
|
+
const routes = getDynamicRoutes();
|
|
109
115
|
|
|
110
|
-
//
|
|
116
|
+
// 从菜单树提取路由(用于权限交叉引用)
|
|
117
|
+
const menuRoutes = extractRoutes(menus);
|
|
118
|
+
|
|
119
|
+
// 解析菜单项(用于侧边栏渲染)
|
|
111
120
|
const menuItems = parseMenuItems(menus);
|
|
112
121
|
|
|
113
122
|
// 查找当前路由
|
|
114
123
|
const currentRoute = findRouteByPath(routes, pathname);
|
|
124
|
+
|
|
125
|
+
// 通过 pageId 查找页面(O(1))
|
|
126
|
+
const page = getPageById(pageId);
|
|
115
127
|
```
|
|
116
128
|
|
|
117
129
|
### 3. 动态路由 (app.tsx)
|
|
118
130
|
|
|
119
131
|
```typescript
|
|
120
132
|
export function patchClientRoutes({ routes }: { routes: any[] }) {
|
|
121
|
-
const
|
|
122
|
-
const dynamicRoutes = extractRoutes(menus);
|
|
133
|
+
const dynamicRoutes = getDynamicRoutes(); // 从 __MICO_PAGES__ 获取
|
|
123
134
|
|
|
124
135
|
const rootRoute = routes.find((r) => r.path === '/');
|
|
125
136
|
dynamicRoutes.forEach((route) => {
|
|
@@ -128,9 +139,7 @@ export function patchClientRoutes({ routes }: { routes: any[] }) {
|
|
|
128
139
|
name: route.name,
|
|
129
140
|
meta: {
|
|
130
141
|
loadType: route.loadType,
|
|
131
|
-
|
|
132
|
-
jsUrls: route.jsUrls,
|
|
133
|
-
cssUrls: route.cssUrls,
|
|
142
|
+
entry: route.entry,
|
|
134
143
|
},
|
|
135
144
|
});
|
|
136
145
|
});
|
|
@@ -265,8 +274,9 @@ const { collapsed } = useModel('global');
|
|
|
265
274
|
|
|
266
275
|
## 注意事项
|
|
267
276
|
|
|
268
|
-
1.
|
|
269
|
-
2. **路由动态生成**: 在 `patchClientRoutes`
|
|
270
|
-
3.
|
|
271
|
-
4.
|
|
272
|
-
5.
|
|
277
|
+
1. **数据来源**: 页面列表通过 `window.__MICO_PAGES__` 注入,菜单树通过 `window.__MICO_MENUS__` 注入
|
|
278
|
+
2. **路由动态生成**: 在 `patchClientRoutes` 中优先从 `__MICO_PAGES__` 生成路由
|
|
279
|
+
3. **权限交叉引用**: 菜单路由(`__MICO_MENUS__`)用于权限判断,页面路由(`__MICO_PAGES__`)用于渲染
|
|
280
|
+
4. **微应用判断**: 根据 `htmlUrl` 或 `jsUrls` 判断是否为微应用
|
|
281
|
+
5. **主题初始化**: 在 `app.tsx` 中调用 `initTheme()` 避免闪烁
|
|
282
|
+
6. **认证共享**: 子应用使用主应用传递的 `request` 实例
|
|
@@ -61,15 +61,16 @@ alwaysApply: true
|
|
|
61
61
|
- **关键文件**:
|
|
62
62
|
- `src/layouts/index.tsx` - 主布局组件
|
|
63
63
|
- `src/components/MicroAppLoader/` - 微应用加载器
|
|
64
|
-
- `src/common/menu/parser.ts` -
|
|
64
|
+
- `src/common/menu/parser.ts` - 菜单解析、页面数据提取与路由生成
|
|
65
65
|
- `src/common/request/index.ts` - 统一请求封装
|
|
66
66
|
- `src/hooks/useTheme.ts` - 主题管理 Hook
|
|
67
67
|
|
|
68
68
|
### 2. 微前端架构
|
|
69
|
-
-
|
|
69
|
+
- 路由注册优先使用 `window.__MICO_PAGES__`(页面列表),无数据时降级到 `window.__MICO_MENUS__`(菜单树)
|
|
70
70
|
- 根据 `htmlUrl` 或 `jsUrls` 判断是否为微应用
|
|
71
71
|
- 使用 `loadMicroApp` API 动态挂载子应用
|
|
72
72
|
- 子应用共享主应用的 `request` 实例
|
|
73
|
+
- 权限通过菜单交叉引用 + 页面级兜底的双层策略控制
|
|
73
74
|
|
|
74
75
|
### 3. 主题系统
|
|
75
76
|
- 支持亮色/暗黑主题切换
|
|
@@ -123,6 +124,8 @@ Jenkins 根据环境执行 `CICD/` 下的构建脚本:
|
|
|
123
124
|
## 相关文档
|
|
124
125
|
|
|
125
126
|
- [微前端模式](mdc:apps/layout/docs/feature-微前端模式.md)
|
|
127
|
+
- [路由与菜单解耦](mdc:apps/layout/docs/feature-路由与菜单解耦.md)
|
|
128
|
+
- [菜单权限控制](mdc:apps/layout/docs/feature-菜单权限控制.md)
|
|
126
129
|
- [主题色切换](mdc:apps/layout/docs/feature-主题色切换.md)
|
|
127
130
|
- [提交规范](mdc:docs/commit-message.md)
|
|
128
131
|
- [部署说明](mdc:deployDesc.md)
|
|
@@ -108,8 +108,8 @@ Logger 工具在 `src/common/logger.ts`,常量定义在 `src/constants/index.t
|
|
|
108
108
|
### 认证模块
|
|
109
109
|
认证逻辑在 `src/common/auth/`,核心文件 `auth-manager.ts` 管理 Token 存储和用户信息。SSO 登录在 `getInitialState()` 中完成,确保 UI 渲染前认证已就绪。
|
|
110
110
|
|
|
111
|
-
###
|
|
112
|
-
菜单解析与类型定义在 `src/common/menu
|
|
111
|
+
### 菜单与路由系统
|
|
112
|
+
菜单解析与类型定义在 `src/common/menu/`。路由注册消费 `window.__MICO_PAGES__`(页面列表),菜单栏消费 `window.__MICO_MENUS__`(菜单树),两者解耦运作。权限通过菜单交叉引用 + 页面级兜底的双层策略控制。详见 [路由与菜单解耦](./apps/layout/docs/feature-路由与菜单解耦.md) 和 [菜单权限控制](./apps/layout/docs/feature-菜单权限控制.md)。
|
|
113
113
|
|
|
114
114
|
### 微前端
|
|
115
115
|
layout 应用作为 qiankun 主应用,子应用通过 Umi 配置中的 qiankun 配置注册。详见 [微前端模式](./apps/layout/docs/feature-微前端模式.md)。
|
|
@@ -121,6 +121,8 @@ layout 应用作为 qiankun 主应用,子应用通过 Umi 配置中的 qiankun
|
|
|
121
121
|
| [README.md](./README.md) | 项目入口、技术栈、常用命令 |
|
|
122
122
|
| [提交规范](./docs/commit-message.md) | Git Commit 详细规范 |
|
|
123
123
|
| [微前端模式](./apps/layout/docs/feature-微前端模式.md) | qiankun 架构、MicroAppLoader、子应用配置 |
|
|
124
|
+
| [路由与菜单解耦](./apps/layout/docs/feature-路由与菜单解耦.md) | PAGES/MENUS 数据源分离、双层权限控制 |
|
|
125
|
+
| [菜单权限控制](./apps/layout/docs/feature-菜单权限控制.md) | sideMenus 白名单、认证与授权分离 |
|
|
124
126
|
| [主题色切换](./apps/layout/docs/feature-主题色切换.md) | useTheme Hook、CSS 变量系统 |
|
|
125
127
|
| [请求模块架构](./apps/layout/docs/arch-请求模块.md) | HTTP 请求层模块化设计 |
|
|
126
128
|
| [日志与常量](./apps/layout/docs/arch-日志与常量.md) | Logger 工具与常量管理 |
|
|
@@ -4,6 +4,7 @@ import { defineConfig } from '@umijs/max';
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
import mockMenus from '../mock/menus';
|
|
7
|
+
import mockPages from '../mock/pages';
|
|
7
8
|
|
|
8
9
|
const config: ReturnType<typeof defineConfig> = {
|
|
9
10
|
publicPath: '/',
|
|
@@ -16,18 +17,21 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
16
17
|
{
|
|
17
18
|
content: `
|
|
18
19
|
window.__MICO_MENUS__ = ${JSON.stringify(mockMenus)};
|
|
20
|
+
window.__MICO_PAGES__ = ${JSON.stringify(mockPages)};
|
|
19
21
|
window.__MICO_CONFIG__ = {
|
|
20
22
|
appName: '<%= projectName %>',
|
|
23
|
+
title: '测试样例',
|
|
24
|
+
logo: '',
|
|
21
25
|
apiBaseUrl: '',
|
|
22
26
|
defaultPath: '',
|
|
23
27
|
// 免认证路由(跳过 SSO 登录),支持 /* 前缀匹配
|
|
24
28
|
// noAuthRouteList: ['/*'],
|
|
25
29
|
// 免权限校验路由(跳过菜单权限检查)
|
|
26
|
-
|
|
30
|
+
noPermissionRouteList: [],
|
|
27
31
|
// 不显示布局的路由(全屏页面)
|
|
28
|
-
// noLayoutRouteList: [
|
|
32
|
+
// noLayoutRouteList: [],
|
|
29
33
|
// 关闭权限控制(调试用)
|
|
30
|
-
disableAuth:
|
|
34
|
+
disableAuth: false,
|
|
31
35
|
};
|
|
32
36
|
`,
|
|
33
37
|
},
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
### 核心实现
|
|
20
20
|
|
|
21
|
-
1.
|
|
22
|
-
2.
|
|
21
|
+
1. 主应用优先通过 `window.__MICO_PAGES__` 获取页面配置,无数据时降级到 `window.__MICO_MENUS__`(详见 [路由与菜单解耦](./feature-路由与菜单解耦.md))
|
|
22
|
+
2. 解析页面时根据 `htmlUrl` 或 `jsUrls` 判断是否为微应用
|
|
23
23
|
3. 路由匹配时,微应用类型使用 `MicroAppLoader` 组件加载
|
|
24
24
|
4. `MicroAppLoader` 使用 qiankun 的 `loadMicroApp` API 动态挂载子应用
|
|
25
25
|
5. 组件卸载时自动调用 `unmount()` 清理子应用
|
|
@@ -334,6 +334,7 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
334
334
|
externals: {
|
|
335
335
|
react: 'window.React',
|
|
336
336
|
'react-dom': 'window.ReactDOM',
|
|
337
|
+
'react-dom/client': 'window.ReactDOMClient',
|
|
337
338
|
'@mico-platform/ui': 'window.micoUI',
|
|
338
339
|
},
|
|
339
340
|
};
|
|
@@ -421,6 +422,8 @@ export default function HomePage() {
|
|
|
421
422
|
|
|
422
423
|
## 相关文档
|
|
423
424
|
|
|
425
|
+
- [路由与菜单解耦](./feature-路由与菜单解耦.md) - 路由注册与菜单导航数据源分离
|
|
426
|
+
- [菜单权限控制](./feature-菜单权限控制.md) - sideMenus 白名单权限逻辑
|
|
424
427
|
- [主题色切换](./feature-主题色切换.md) - 主题系统实现与子应用适配
|
|
425
428
|
- [请求模块架构](./arch-请求模块.md) - HTTP 请求层模块化设计
|
|
426
429
|
- [日志与常量](./arch-日志与常量.md) - Logger 工具与常量管理
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
|
|
20
20
|
### 四种路由场景
|
|
21
21
|
|
|
22
|
-
| 场景 | noAuthRouteList | noPermissionRouteList | 示例 |
|
|
23
|
-
| --- | --- | --- | --- |
|
|
24
|
-
| 公开页面 | ✅ | ✅ | 首页、活动页 |
|
|
25
|
-
| 登录后公共页面 | ❌ | ✅ | 个人设置、帮助中心 |
|
|
26
|
-
| 需要权限的页面 | ❌ | ❌ | 后台管理、业务功能 |
|
|
27
|
-
| 登录页等特殊页面 | ✅ | ✅ | /
|
|
22
|
+
| 场景 | noAuthRouteList | noPermissionRouteList | accessControlEnabled | 示例 |
|
|
23
|
+
| --- | --- | --- | --- | --- |
|
|
24
|
+
| 公开页面 | ✅ | ✅ | `false` | 首页、活动页 |
|
|
25
|
+
| 登录后公共页面 | ❌ | ✅ | - | 个人设置、帮助中心 |
|
|
26
|
+
| 需要权限的页面 | ❌ | ❌ | `true` | 后台管理、业务功能 |
|
|
27
|
+
| 登录页等特殊页面 | ✅ | ✅ | `false` | /login、/403 |
|
|
28
28
|
|
|
29
29
|
## 技术方案
|
|
30
30
|
|
|
@@ -40,22 +40,28 @@
|
|
|
40
40
|
用户访问路由
|
|
41
41
|
│
|
|
42
42
|
▼
|
|
43
|
-
|
|
44
|
-
│ 1. SSO 认证检查 (app.tsx)
|
|
45
|
-
│ isNoAuthRoute(pathname)?
|
|
46
|
-
│ ├── 是 → 跳过 SSO
|
|
47
|
-
│
|
|
48
|
-
|
|
43
|
+
┌──────────────────────────────────────────────┐
|
|
44
|
+
│ 1. SSO 认证检查 (app.tsx) │
|
|
45
|
+
│ isNoAuthRoute(pathname)? │
|
|
46
|
+
│ ├── 是 → 跳过 SSO │
|
|
47
|
+
│ page.accessControlEnabled === false? │
|
|
48
|
+
│ ├── 是 → 跳过 SSO(PAGES 数据驱动) │
|
|
49
|
+
│ └── 否 → 执行 ensureSsoSession() │
|
|
50
|
+
└──────────────────────────────────────────────┘
|
|
49
51
|
│
|
|
50
52
|
▼
|
|
51
|
-
|
|
52
|
-
│ 2. 权限校验 (layouts/index.tsx)
|
|
53
|
-
│ isNoPermissionRoute(pathname)?
|
|
54
|
-
│ ├── 是 → 跳过权限校验
|
|
55
|
-
│ └── 否 →
|
|
56
|
-
│
|
|
57
|
-
│
|
|
58
|
-
|
|
53
|
+
┌──────────────────────────────────────────┐
|
|
54
|
+
│ 2. 权限校验 (layouts/index.tsx) │
|
|
55
|
+
│ isNoPermissionRoute(pathname)? │
|
|
56
|
+
│ ├── 是 → 跳过权限校验 │
|
|
57
|
+
│ └── 否 → 双层权限判断 │
|
|
58
|
+
│ Tier 1: 页面在菜单中? │
|
|
59
|
+
│ ├── 是 → 沿用 sideMenus │
|
|
60
|
+
│ │ 白名单逻辑 │
|
|
61
|
+
│ └── 否 → Tier 2: 页面级权限 │
|
|
62
|
+
│ adminOnly / routeKey│
|
|
63
|
+
│ 详见:路由与菜单解耦文档 │
|
|
64
|
+
└──────────────────────────────────────────┘
|
|
59
65
|
│
|
|
60
66
|
▼
|
|
61
67
|
┌─────────────────────────────────┐
|
|
@@ -66,12 +72,16 @@
|
|
|
66
72
|
└─────────────────────────────────┘
|
|
67
73
|
│
|
|
68
74
|
▼
|
|
69
|
-
|
|
70
|
-
│ 4. 子应用加载 (MicroAppLoader)
|
|
71
|
-
│
|
|
72
|
-
│ ├── 是 →
|
|
73
|
-
│
|
|
74
|
-
|
|
75
|
+
┌──────────────────────────────────────────────┐
|
|
76
|
+
│ 4. 子应用加载 (MicroAppLoader) │
|
|
77
|
+
│ page.accessControlEnabled === false? │
|
|
78
|
+
│ ├── 是 → 直接加载(PAGES 数据驱动) │
|
|
79
|
+
│ isNoAuthRoute(pathname)? │
|
|
80
|
+
│ ├── 是 → 直接加载(静态配置兜底) │
|
|
81
|
+
│ isNoPermissionRoute(pathname)? │
|
|
82
|
+
│ ├── 是 → 直接加载 │
|
|
83
|
+
│ └── 否 → 等待 currentUser │
|
|
84
|
+
└──────────────────────────────────────────────┘
|
|
75
85
|
```
|
|
76
86
|
|
|
77
87
|
### 权限判断逻辑(详细)
|
|
@@ -84,22 +94,39 @@
|
|
|
84
94
|
├── 是 → 允许所有访问(调试模式)
|
|
85
95
|
│
|
|
86
96
|
▼
|
|
97
|
+
page.accessControlEnabled === false?
|
|
98
|
+
├── 是 → 跳过 SSO 认证 + 跳过权限校验
|
|
99
|
+
│
|
|
100
|
+
▼
|
|
87
101
|
是否在 noPermissionRouteList 中?
|
|
88
102
|
├── 是 → 允许访问,显示全部菜单
|
|
89
103
|
│
|
|
90
104
|
▼
|
|
105
|
+
是否是非动态路由(不在 PAGES 中)?
|
|
106
|
+
├── 是 → 交给 Umi 处理(404 等静态路由)
|
|
107
|
+
│
|
|
108
|
+
▼
|
|
91
109
|
是否是超级用户?
|
|
92
|
-
├── 是 →
|
|
110
|
+
├── 是 → 允许访问所有页面
|
|
93
111
|
│
|
|
94
112
|
▼
|
|
95
|
-
|
|
96
|
-
├── 是 →
|
|
113
|
+
Tier 1: 页面在菜单中?
|
|
114
|
+
├── 是 → 沿用菜单权限逻辑:
|
|
115
|
+
│ 菜单项 adminOnly === true?
|
|
116
|
+
│ ├── 是 → 禁止访问
|
|
117
|
+
│ 检查 side_menus 白名单
|
|
118
|
+
│ ├── 匹配 → 允许访问
|
|
119
|
+
│ └── 不匹配 → 显示 403
|
|
97
120
|
│
|
|
98
121
|
▼
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
├──
|
|
102
|
-
|
|
122
|
+
Tier 2: 隐藏页面(不在菜单中)
|
|
123
|
+
adminOnly === true?
|
|
124
|
+
├── 是 → 显示 403
|
|
125
|
+
accessControlEnabled === true?
|
|
126
|
+
├── 是 → 检查 routeKey ∈ side_menus
|
|
127
|
+
│ ├── 匹配 → 允许访问
|
|
128
|
+
│ └── 不匹配 → 显示 403
|
|
129
|
+
└── 否 → 允许访问
|
|
103
130
|
```
|
|
104
131
|
|
|
105
132
|
## 文件清单
|
|
@@ -251,18 +278,30 @@ export const NO_PERMISSION_ROUTE_LIST: string[] = ['/403', '/404'];
|
|
|
251
278
|
### Layout 中的权限判断
|
|
252
279
|
|
|
253
280
|
```tsx
|
|
254
|
-
// layouts/index.tsx
|
|
281
|
+
// layouts/index.tsx — 双层权限判断
|
|
255
282
|
const isForbidden = useMemo(() => {
|
|
256
|
-
// 关闭权限控制时,不校验权限
|
|
257
283
|
if (isAuthDisabled()) return false;
|
|
258
|
-
// 免权限校验路由,不检查菜单权限
|
|
259
284
|
if (isNoPermissionRoute(location.pathname)) return false;
|
|
260
|
-
//
|
|
261
|
-
if (
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
285
|
+
if (!currentRoute) return false; // 非动态路由
|
|
286
|
+
if (isSuperuserUser(currentUser?.is_superuser)) return false;
|
|
287
|
+
|
|
288
|
+
// Tier 1: 菜单权限交叉引用
|
|
289
|
+
const inAllMenu = findRouteByPath(allMenuRoutes, location.pathname);
|
|
290
|
+
if (inAllMenu) {
|
|
291
|
+
return !findRouteByPath(allowedMenuRoutes, location.pathname);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Tier 2: 隐藏页面级权限
|
|
295
|
+
if (!hasWindowPages()) return false;
|
|
296
|
+
const page = findPageByPath(getWindowPages(), location.pathname);
|
|
297
|
+
if (!page) return false;
|
|
298
|
+
if (page.adminOnly) return true;
|
|
299
|
+
if (page.accessControlEnabled) {
|
|
300
|
+
const sideMenus = (currentUser?.side_menus || []) as string[];
|
|
301
|
+
return !page.routeKey || !sideMenus.includes(page.routeKey);
|
|
302
|
+
}
|
|
303
|
+
return false;
|
|
304
|
+
}, [...]);
|
|
266
305
|
```
|
|
267
306
|
|
|
268
307
|
### MicroAppLoader 中的认证判断
|
|
@@ -271,6 +310,8 @@ const isForbidden = useMemo(() => {
|
|
|
271
310
|
// components/MicroAppLoader/index.tsx
|
|
272
311
|
const isAuthReady =
|
|
273
312
|
isAuthDisabled() ||
|
|
313
|
+
isPageAuthFree(location.pathname) ||
|
|
314
|
+
isNoAuthRoute(location.pathname) ||
|
|
274
315
|
isNoPermissionRoute(location.pathname) ||
|
|
275
316
|
!!initialState?.currentUser;
|
|
276
317
|
|
|
@@ -280,16 +321,31 @@ if (!isAuthReady) {
|
|
|
280
321
|
}
|
|
281
322
|
```
|
|
282
323
|
|
|
324
|
+
判断优先级:
|
|
325
|
+
1. `isAuthDisabled()` — 全局关闭权限,直接放行
|
|
326
|
+
2. `isPageAuthFree` — **PAGES 数据驱动**,页面 `accessControlEnabled === false` 时跳过认证和权限校验
|
|
327
|
+
3. `isNoAuthRoute()` — 静态配置兜底,免认证路由(PAGES 未注入时的降级保护)
|
|
328
|
+
4. `isNoPermissionRoute()` — 免权限路由(如 403/404),无需等待 currentUser
|
|
329
|
+
5. `!!initialState?.currentUser` — 已登录,有用户信息
|
|
330
|
+
|
|
331
|
+
**注意**:`accessControlEnabled === false` 同时影响三个阶段:
|
|
332
|
+
|
|
333
|
+
- SSO 认证(`app.tsx`):跳过 `ensureSsoSession()` 和 `handleAuthFailureRedirect()`
|
|
334
|
+
- 权限校验(`layouts/index.tsx`):Tier 2 隐藏页面默认放行
|
|
335
|
+
- 子应用加载(`MicroAppLoader`):不等待 currentUser 直接加载
|
|
336
|
+
|
|
283
337
|
## 设计决策
|
|
284
338
|
|
|
285
339
|
| 决策点 | 选择 | 理由 |
|
|
286
340
|
| --- | --- | --- |
|
|
341
|
+
| accessControlEnabled 统一控制 | `false` 同时跳过认证和授权 | PAGES 数据驱动,一个字段即可标记公开页面,无需重复配置 noAuthRouteList |
|
|
287
342
|
| 认证与授权分离 | 两个独立配置项 | 不同场景需要不同组合,如"需要登录但不需要权限"的个人设置页 |
|
|
288
343
|
| 权限模型 | 白名单 (`side_menus`) | 后端返回的 `side_menus` 是允许列表,比黑名单更安全 |
|
|
289
344
|
| 403 处理 | 原地渲染组件 | 保持 URL 不变,用户体验更好,便于分享链接 |
|
|
290
345
|
| 父级菜单显示 | 前缀匹配 | 子菜单有权限时,父级菜单需要作为容器显示 |
|
|
291
346
|
| 超级用户 | 跳过所有检查 | 管理员需要完整访问权限 |
|
|
292
347
|
| 免权限路由的菜单 | 显示全部 | 用户访问公开页面时应能看到所有导航选项 |
|
|
348
|
+
| 免认证路由的子应用 | 直接加载 | 不等待 currentUser,免认证路由本身不需要登录 |
|
|
293
349
|
| 免权限路由的子应用 | 直接加载 | 不等待 currentUser,避免加载卡住 |
|
|
294
350
|
| 静态路由不受权限控制 | 默认允许访问 | 见下方"静态路由与动态路由"说明 |
|
|
295
351
|
| adminOnly 判断 | 读取菜单数据字段 | 替代硬编码菜单名称匹配,由后端数据驱动,支持多语言且无需前端维护 |
|
|
@@ -301,7 +357,7 @@ if (!isAuthReady) {
|
|
|
301
357
|
| 类型 | 来源 | 示例 |
|
|
302
358
|
| --- | --- | --- |
|
|
303
359
|
| **静态路由** | 代码中定义 (`config/routes.ts`) | `/`, `/403`, `/404`, `/user/login` |
|
|
304
|
-
| **动态路由** |
|
|
360
|
+
| **动态路由** | 后端页面配置 (`window.__MICO_PAGES__`,降级 `window.__MICO_MENUS__`) | `/queue-management`, `/quality-check` |
|
|
305
361
|
|
|
306
362
|
### 权限控制范围
|
|
307
363
|
|
|
@@ -311,9 +367,11 @@ if (!isAuthReady) {
|
|
|
311
367
|
权限判断逻辑(isForbidden):
|
|
312
368
|
1. 检查 isAuthDisabled() → 关闭则全部允许
|
|
313
369
|
2. 检查 isNoPermissionRoute() → 在免权限列表中则允许
|
|
314
|
-
3. 检查 currentRoute
|
|
315
|
-
4. 检查
|
|
316
|
-
5.
|
|
370
|
+
3. 检查 currentRoute(PAGES 中的路由)→ 不存在则非动态路由,交给 Umi
|
|
371
|
+
4. 检查 isSuperuserUser() → 超级用户放行
|
|
372
|
+
5. Tier 1: 在菜单中 → 检查 allowedMenuRoutes
|
|
373
|
+
6. Tier 2: 不在菜单(隐藏页面)→ 检查 adminOnly + accessControlEnabled + routeKey
|
|
374
|
+
7. 都不匹配 → 放行
|
|
317
375
|
```
|
|
318
376
|
|
|
319
377
|
### 设计理由
|
|
@@ -351,10 +409,11 @@ if (!isAuthReady) {
|
|
|
351
409
|
- `side_menus` 格式为菜单路径,如 `"列队管理.配置队列"`
|
|
352
410
|
- `miss_permissions` 用于按钮级别权限控制,不影响菜单显示
|
|
353
411
|
- 403 页面在 Layout 内渲染,不会触发路由跳转
|
|
354
|
-
- 调试时可在控制台搜索 `isForbidden check` 查看权限判断日志
|
|
412
|
+
- 调试时可在控制台搜索 `isForbidden (menu check)` 或 `isForbidden (hidden page` 查看权限判断日志
|
|
355
413
|
- **常见问题**:配置了 `noAuthRouteList` 但页面仍显示 403,需同时配置 `noPermissionRouteList`
|
|
356
414
|
|
|
357
415
|
## 相关文档
|
|
358
416
|
|
|
359
|
-
- [
|
|
417
|
+
- [路由与菜单解耦](./feature-路由与菜单解耦.md) - 路由注册与菜单导航数据源分离、双层权限详细说明
|
|
418
|
+
- [微前端模式](./feature-微前端模式.md) - 微应用加载机制
|
|
360
419
|
- [日志与常量](./arch-日志与常量.md) - 常量管理
|