generator-mico-cli 0.2.20 → 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/README.md +29 -0
- package/bin/mico.js +124 -5
- package/generators/micro-react/index.js +76 -17
- 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 +15 -7
- package/generators/micro-react/templates/_gitignore +2 -0
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +7 -3
- package/generators/micro-react/templates/apps/layout/config/config.ts +21 -0
- package/generators/micro-react/templates/apps/layout/config/routes.ts +0 -5
- package/generators/micro-react/templates/apps/layout/docs/common-intl.md +8 -6
- 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 +65 -37
- 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 +112 -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/docs/utils-timezone.md +4 -2
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +89 -139
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +83 -0
- package/generators/micro-react/templates/apps/layout/package.json +3 -2
- package/generators/micro-react/templates/apps/layout/src/app.tsx +10 -8
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +121 -58
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +35 -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 -3
- 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/micro-react/templates/package.json +2 -1
- package/generators/subapp-react/index.js +240 -14
- package/generators/subapp-react/templates/homepage/.env +2 -1
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +9 -1
- 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/generators/subapp-react/templates/homepage/config/config.ts +21 -0
- package/generators/subapp-react/templates/homepage/config/routes.ts +1 -1
- package/generators/subapp-react/templates/homepage/mock/api.mock.ts +2 -2
- package/generators/subapp-react/templates/homepage/package.json +3 -2
- package/generators/subapp-react/templates/homepage/src/app.tsx +1 -1
- package/generators/subapp-react/templates/homepage/src/common/request.ts +2 -2
- package/generators/subapp-react/templates/homepage/src/global.less +2 -1
- package/generators/subapp-react/templates/homepage/src/pages/index.less +1 -1
- package/generators/subapp-react/templates/homepage/src/pages/index.tsx +27 -27
- package/lib/utils.js +200 -2
- package/package.json +1 -1
|
@@ -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
|
},
|
|
@@ -118,6 +118,27 @@ const config: ReturnType<typeof defineConfig> = {
|
|
|
118
118
|
*/
|
|
119
119
|
tailwindcss: {},
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* @name 额外 Babel Presets
|
|
123
|
+
* @description 使用 @mico-platform/ui 的 babel preset,实现组件与图标的按需加载
|
|
124
|
+
*/
|
|
125
|
+
extraBabelPresets: ['@mico-platform/ui/babel-preset'],
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @name MFSU 配置
|
|
129
|
+
* @description
|
|
130
|
+
* - exclude: theme 子路径解析;ui 尽量与主应用一起编译
|
|
131
|
+
* - shared: React 单例,确保 MFSU 预打包里的组件(如 Layout)与主应用共用同一份 React,否则 useContext 报 null
|
|
132
|
+
* @doc https://umijs.org/docs/guides/mfsu
|
|
133
|
+
*/
|
|
134
|
+
mfsu: {
|
|
135
|
+
exclude: ['@mico-platform/theme', '@mico-platform/ui'],
|
|
136
|
+
shared: {
|
|
137
|
+
react: { singleton: true },
|
|
138
|
+
'react-dom': { singleton: true },
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
|
|
121
142
|
/**
|
|
122
143
|
* @name qiankun 微前端配置
|
|
123
144
|
* @description 作为主应用,动态加载子应用
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
- 需要从多语言中台动态获取翻译文案
|
|
11
11
|
- 希望统一管理国际化逻辑,减少重复代码
|
|
12
12
|
|
|
13
|
+
> **注意**:本文档描述的是 `@payment-portal/common-intl` 包的完整使用方式,涵盖了所有使用该包的应用(包括本仓库外的应用如 conversation-v2、session、workorder 等)。当前仓库仅包含 `layout` 和 `basis` 两个应用。
|
|
14
|
+
|
|
13
15
|
## 核心特性
|
|
14
16
|
|
|
15
17
|
| 特性 | 说明 |
|
|
@@ -311,12 +313,12 @@ console.log(SUPPORTED_LOCALES); // ['zh_CN', 'en', 'ar', 'tr']
|
|
|
311
313
|
|
|
312
314
|
目前支持以下语言:
|
|
313
315
|
|
|
314
|
-
| 语言常量
|
|
315
|
-
|
|
|
316
|
-
| `LANG.ZH_CN`
|
|
317
|
-
| `LANG.
|
|
318
|
-
| `LANG.
|
|
319
|
-
| `LANG.
|
|
316
|
+
| 语言常量 | 值 | 说明 |
|
|
317
|
+
| ------------- | ------- | -------- |
|
|
318
|
+
| `LANG.ZH_CN` | `zh_CN` | 简体中文 |
|
|
319
|
+
| `LANG.EN_US` | `en` | 英语 |
|
|
320
|
+
| `LANG.AR_SA` | `ar` | 阿拉伯语 |
|
|
321
|
+
| `LANG.TR_TR` | `tr` | 土耳其语 |
|
|
320
322
|
|
|
321
323
|
### Q: 如何在代码中获取当前语言?
|
|
322
324
|
|
|
@@ -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()` 清理子应用
|
|
@@ -28,16 +28,16 @@
|
|
|
28
28
|
|
|
29
29
|
### 核心文件
|
|
30
30
|
|
|
31
|
-
| 文件路径
|
|
32
|
-
|
|
|
33
|
-
| `src/components/MicroAppLoader/index.tsx`
|
|
34
|
-
| `src/components/MicroAppLoader/
|
|
35
|
-
| `src/components/MicroAppLoader/index.less`
|
|
36
|
-
| `src/common/menu/parser.ts`
|
|
37
|
-
| `src/common/menu/types.ts`
|
|
38
|
-
| `src/layouts/index.tsx`
|
|
39
|
-
| `src/app.tsx`
|
|
40
|
-
| `config/config.ts`
|
|
31
|
+
| 文件路径 | 说明 |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `src/components/MicroAppLoader/index.tsx` | qiankun 微应用加载器组件 |
|
|
34
|
+
| `src/components/MicroAppLoader/micro-app-manager.ts` | 微应用管理器(单例),稳定容器 + 实例缓存 + 操作序列号 |
|
|
35
|
+
| `src/components/MicroAppLoader/index.less` | 加载器样式 |
|
|
36
|
+
| `src/common/menu/parser.ts` | 菜单解析,包含加载类型判断 |
|
|
37
|
+
| `src/common/menu/types.ts` | 类型定义 |
|
|
38
|
+
| `src/layouts/index.tsx` | 主布局,集成微应用渲染 |
|
|
39
|
+
| `src/app.tsx` | qiankun 全局错误处理 |
|
|
40
|
+
| `config/config.ts` | qiankun master 配置 |
|
|
41
41
|
|
|
42
42
|
## API / 组件接口
|
|
43
43
|
|
|
@@ -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 工具与常量管理
|
|
@@ -462,52 +465,77 @@ addGlobalUncaughtErrorHandler((event: Event | string) => {
|
|
|
462
465
|
});
|
|
463
466
|
```
|
|
464
467
|
|
|
465
|
-
###
|
|
468
|
+
### MicroAppManager API (micro-app-manager.ts)
|
|
466
469
|
|
|
467
|
-
|
|
470
|
+
`MicroAppManager` 是单例类,管理微应用的加载、缓存、切换和卸载:
|
|
468
471
|
|
|
469
472
|
```typescript
|
|
470
|
-
|
|
471
|
-
|
|
473
|
+
interface MicroAppConfig {
|
|
474
|
+
name: string;
|
|
475
|
+
entry: string;
|
|
476
|
+
target: HTMLElement; // 目标挂载位置
|
|
477
|
+
props: Record<string, unknown>;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
interface MicroAppState {
|
|
481
|
+
loading: boolean;
|
|
482
|
+
error: string | null;
|
|
483
|
+
mounted: boolean;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const manager = microAppManager; // 单例,导出即用
|
|
472
487
|
|
|
473
|
-
/**
|
|
474
|
-
|
|
488
|
+
/** 切换到指定微应用(已挂载则仅更新 props) */
|
|
489
|
+
manager.switchTo(config: MicroAppConfig): void;
|
|
475
490
|
|
|
476
|
-
/**
|
|
477
|
-
|
|
491
|
+
/** 更新当前已挂载微应用的 props */
|
|
492
|
+
manager.updateProps(props: Record<string, unknown>): void;
|
|
478
493
|
|
|
479
|
-
/**
|
|
480
|
-
|
|
494
|
+
/** 取消待处理的请求 */
|
|
495
|
+
manager.cancel(): void;
|
|
481
496
|
|
|
482
|
-
/**
|
|
483
|
-
|
|
497
|
+
/** 清除所有缓存实例 */
|
|
498
|
+
manager.clearCache(): Promise<void>;
|
|
499
|
+
|
|
500
|
+
/** 获取调试信息 */
|
|
501
|
+
manager.getDebugInfo(): object;
|
|
502
|
+
|
|
503
|
+
/** 设置状态变化回调 */
|
|
504
|
+
manager.setStateCallback(callback: StateChangeCallback | null): void;
|
|
484
505
|
```
|
|
485
506
|
|
|
507
|
+
### 核心设计
|
|
508
|
+
|
|
509
|
+
1. **稳定容器**:容器在 `document.body` 中创建,激活时移到目标元素内,停用时移回 body 隐藏,不受 React 生命周期影响
|
|
510
|
+
2. **实例缓存**:每个 entry 只 `loadMicroApp` 一次,后续切换复用已有实例(mount/unmount)
|
|
511
|
+
3. **操作序列号**:通过递增的 `operationSeq` 检测过期操作,替代旧的会话 ID 机制
|
|
512
|
+
4. **自动路由守卫**:由独立的 `route-guard.ts` 自动检测用户意图,业务代码无需感知
|
|
513
|
+
|
|
486
514
|
### 快速切换时序
|
|
487
515
|
|
|
488
516
|
```
|
|
489
517
|
场景:A → B → A 快速切换
|
|
490
518
|
|
|
491
|
-
|
|
492
|
-
↓
|
|
493
|
-
切换到 B,A1 cleanup 在 queueMicrotask 中执行
|
|
519
|
+
switchTo(A):operationSeq=1,开始 loadMicroApp
|
|
494
520
|
↓
|
|
495
|
-
|
|
521
|
+
switchTo(B):operationSeq=2,A 的 pendingRequest 被替换
|
|
496
522
|
↓
|
|
497
|
-
|
|
523
|
+
A 的 processRequest 在异步步骤中检测 shouldAbort(mySeq=1) → true
|
|
498
524
|
↓
|
|
499
|
-
|
|
525
|
+
A 等待 mountPromise 完成后执行 safeUnmount,容器移回 body
|
|
500
526
|
↓
|
|
501
|
-
|
|
527
|
+
B 开始 processRequest,检查缓存 → 无缓存 → loadMicroApp
|
|
502
528
|
↓
|
|
503
|
-
|
|
529
|
+
B 加载完成后同步最新 Props(locale/timezone/routePath 等)
|
|
504
530
|
```
|
|
505
531
|
|
|
506
532
|
### 设计决策
|
|
507
533
|
|
|
508
534
|
| 决策点 | 选择 | 理由 |
|
|
509
|
-
|
|
510
|
-
|
|
|
511
|
-
| 并发控制 |
|
|
512
|
-
|
|
|
513
|
-
|
|
|
535
|
+
| --- | --- | --- |
|
|
536
|
+
| 容器策略 | 稳定容器(body 中创建,移动挂载) | 不受 React 生命周期影响,避免容器被意外销毁 |
|
|
537
|
+
| 并发控制 | 操作序列号 + pendingRequest 队列 | 简单高效,新请求自动覆盖旧请求 |
|
|
538
|
+
| 实例缓存 | loadPromise 完成后立即缓存 | 即使被 abort 也可复用,避免重复加载 |
|
|
539
|
+
| unmount 安全 | 等待 mountPromise 完成后再 unmount | 避免 single-spa error #32 |
|
|
540
|
+
| 超时保护 | load 30s / mount 30s / unmount 10s | 平衡等待时间与异常检测速度 |
|
|
541
|
+
| Props 同步 | mount 完成后立即 safeUpdate | 确保加载期间的变化不丢失 |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 菜单权限控制
|
|
2
2
|
|
|
3
|
-
> 创建时间:2026-01-24 更新时间:2026-
|
|
3
|
+
> 创建时间:2026-01-24 更新时间:2026-02-08
|
|
4
4
|
|
|
5
5
|
## 功能概述
|
|
6
6
|
|
|
@@ -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,18 +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
|
+
├── 是 → 允许访问所有页面
|
|
111
|
+
│
|
|
112
|
+
▼
|
|
113
|
+
Tier 1: 页面在菜单中?
|
|
114
|
+
├── 是 → 沿用菜单权限逻辑:
|
|
115
|
+
│ 菜单项 adminOnly === true?
|
|
116
|
+
│ ├── 是 → 禁止访问
|
|
117
|
+
│ 检查 side_menus 白名单
|
|
118
|
+
│ ├── 匹配 → 允许访问
|
|
119
|
+
│ └── 不匹配 → 显示 403
|
|
93
120
|
│
|
|
94
121
|
▼
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
├──
|
|
98
|
-
|
|
122
|
+
Tier 2: 隐藏页面(不在菜单中)
|
|
123
|
+
adminOnly === true?
|
|
124
|
+
├── 是 → 显示 403
|
|
125
|
+
accessControlEnabled === true?
|
|
126
|
+
├── 是 → 检查 routeKey ∈ side_menus
|
|
127
|
+
│ ├── 匹配 → 允许访问
|
|
128
|
+
│ └── 不匹配 → 显示 403
|
|
129
|
+
└── 否 → 允许访问
|
|
99
130
|
```
|
|
100
131
|
|
|
101
132
|
## 文件清单
|
|
@@ -241,24 +272,36 @@ export const NO_PERMISSION_ROUTE_LIST: string[] = ['/403', '/404'];
|
|
|
241
272
|
│ └── 配置队列 ✅ 精确匹配 "列队管理.配置队列"
|
|
242
273
|
├── 质量管理 ❌ 不在白名单
|
|
243
274
|
│ └── 抽样检查 ❌ 不在白名单
|
|
244
|
-
└── 权限管理 ❌
|
|
275
|
+
└── 权限管理 ❌ adminOnly=true,非超级用户不可见
|
|
245
276
|
```
|
|
246
277
|
|
|
247
278
|
### Layout 中的权限判断
|
|
248
279
|
|
|
249
280
|
```tsx
|
|
250
|
-
// layouts/index.tsx
|
|
281
|
+
// layouts/index.tsx — 双层权限判断
|
|
251
282
|
const isForbidden = useMemo(() => {
|
|
252
|
-
// 关闭权限控制时,不校验权限
|
|
253
283
|
if (isAuthDisabled()) return false;
|
|
254
|
-
// 免权限校验路由,不检查菜单权限
|
|
255
284
|
if (isNoPermissionRoute(location.pathname)) return false;
|
|
256
|
-
//
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
+
}, [...]);
|
|
262
305
|
```
|
|
263
306
|
|
|
264
307
|
### MicroAppLoader 中的认证判断
|
|
@@ -267,6 +310,8 @@ const isForbidden = useMemo(() => {
|
|
|
267
310
|
// components/MicroAppLoader/index.tsx
|
|
268
311
|
const isAuthReady =
|
|
269
312
|
isAuthDisabled() ||
|
|
313
|
+
isPageAuthFree(location.pathname) ||
|
|
314
|
+
isNoAuthRoute(location.pathname) ||
|
|
270
315
|
isNoPermissionRoute(location.pathname) ||
|
|
271
316
|
!!initialState?.currentUser;
|
|
272
317
|
|
|
@@ -276,18 +321,34 @@ if (!isAuthReady) {
|
|
|
276
321
|
}
|
|
277
322
|
```
|
|
278
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
|
+
|
|
279
337
|
## 设计决策
|
|
280
338
|
|
|
281
339
|
| 决策点 | 选择 | 理由 |
|
|
282
340
|
| --- | --- | --- |
|
|
341
|
+
| accessControlEnabled 统一控制 | `false` 同时跳过认证和授权 | PAGES 数据驱动,一个字段即可标记公开页面,无需重复配置 noAuthRouteList |
|
|
283
342
|
| 认证与授权分离 | 两个独立配置项 | 不同场景需要不同组合,如"需要登录但不需要权限"的个人设置页 |
|
|
284
343
|
| 权限模型 | 白名单 (`side_menus`) | 后端返回的 `side_menus` 是允许列表,比黑名单更安全 |
|
|
285
344
|
| 403 处理 | 原地渲染组件 | 保持 URL 不变,用户体验更好,便于分享链接 |
|
|
286
345
|
| 父级菜单显示 | 前缀匹配 | 子菜单有权限时,父级菜单需要作为容器显示 |
|
|
287
346
|
| 超级用户 | 跳过所有检查 | 管理员需要完整访问权限 |
|
|
288
347
|
| 免权限路由的菜单 | 显示全部 | 用户访问公开页面时应能看到所有导航选项 |
|
|
348
|
+
| 免认证路由的子应用 | 直接加载 | 不等待 currentUser,免认证路由本身不需要登录 |
|
|
289
349
|
| 免权限路由的子应用 | 直接加载 | 不等待 currentUser,避免加载卡住 |
|
|
290
350
|
| 静态路由不受权限控制 | 默认允许访问 | 见下方"静态路由与动态路由"说明 |
|
|
351
|
+
| adminOnly 判断 | 读取菜单数据字段 | 替代硬编码菜单名称匹配,由后端数据驱动,支持多语言且无需前端维护 |
|
|
291
352
|
|
|
292
353
|
## 静态路由与动态路由
|
|
293
354
|
|
|
@@ -296,7 +357,7 @@ if (!isAuthReady) {
|
|
|
296
357
|
| 类型 | 来源 | 示例 |
|
|
297
358
|
| --- | --- | --- |
|
|
298
359
|
| **静态路由** | 代码中定义 (`config/routes.ts`) | `/`, `/403`, `/404`, `/user/login` |
|
|
299
|
-
| **动态路由** |
|
|
360
|
+
| **动态路由** | 后端页面配置 (`window.__MICO_PAGES__`,降级 `window.__MICO_MENUS__`) | `/queue-management`, `/quality-check` |
|
|
300
361
|
|
|
301
362
|
### 权限控制范围
|
|
302
363
|
|
|
@@ -306,9 +367,11 @@ if (!isAuthReady) {
|
|
|
306
367
|
权限判断逻辑(isForbidden):
|
|
307
368
|
1. 检查 isAuthDisabled() → 关闭则全部允许
|
|
308
369
|
2. 检查 isNoPermissionRoute() → 在免权限列表中则允许
|
|
309
|
-
3. 检查 currentRoute
|
|
310
|
-
4. 检查
|
|
311
|
-
5.
|
|
370
|
+
3. 检查 currentRoute(PAGES 中的路由)→ 不存在则非动态路由,交给 Umi
|
|
371
|
+
4. 检查 isSuperuserUser() → 超级用户放行
|
|
372
|
+
5. Tier 1: 在菜单中 → 检查 allowedMenuRoutes
|
|
373
|
+
6. Tier 2: 不在菜单(隐藏页面)→ 检查 adminOnly + accessControlEnabled + routeKey
|
|
374
|
+
7. 都不匹配 → 放行
|
|
312
375
|
```
|
|
313
376
|
|
|
314
377
|
### 设计理由
|
|
@@ -346,10 +409,11 @@ if (!isAuthReady) {
|
|
|
346
409
|
- `side_menus` 格式为菜单路径,如 `"列队管理.配置队列"`
|
|
347
410
|
- `miss_permissions` 用于按钮级别权限控制,不影响菜单显示
|
|
348
411
|
- 403 页面在 Layout 内渲染,不会触发路由跳转
|
|
349
|
-
- 调试时可在控制台搜索 `isForbidden check` 查看权限判断日志
|
|
412
|
+
- 调试时可在控制台搜索 `isForbidden (menu check)` 或 `isForbidden (hidden page` 查看权限判断日志
|
|
350
413
|
- **常见问题**:配置了 `noAuthRouteList` 但页面仍显示 403,需同时配置 `noPermissionRouteList`
|
|
351
414
|
|
|
352
415
|
## 相关文档
|
|
353
416
|
|
|
354
|
-
- [
|
|
417
|
+
- [路由与菜单解耦](./feature-路由与菜单解耦.md) - 路由注册与菜单导航数据源分离、双层权限详细说明
|
|
418
|
+
- [微前端模式](./feature-微前端模式.md) - 微应用加载机制
|
|
355
419
|
- [日志与常量](./arch-日志与常量.md) - 常量管理
|