generator-mico-cli 0.2.28 → 0.2.30
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 +7 -20
- package/bin/mico.js +27 -62
- package/generators/micro-react/index.js +25 -1
- package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +3 -0
- package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +1 -0
- package/generators/micro-react/templates/CICD/start_dev.sh +11 -0
- package/generators/micro-react/templates/CICD/start_local.sh +9 -0
- package/generators/micro-react/templates/CICD/start_prod.sh +13 -0
- package/generators/micro-react/templates/CICD/start_test.sh +11 -0
- package/generators/micro-react/templates/CLAUDE.md +1 -0
- package/generators/micro-react/templates/README.md +1 -1
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +13 -5
- package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +12 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +12 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.ts +14 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-PermissionFilter/346/214/211/351/222/256/346/235/203/351/231/220.md +116 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-/345/233/275/351/231/205/345/214/226.md +121 -0
- 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 +8 -0
- 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 +83 -77
- 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 +50 -35
- package/generators/micro-react/templates/apps/layout/docs/feature-/350/267/257/347/224/261/346/235/203/351/231/220/346/227/245/345/277/227.md +162 -0
- package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +23 -31
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +14 -0
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +27 -8
- package/generators/micro-react/templates/apps/layout/package.json +2 -0
- package/generators/micro-react/templates/apps/layout/src/app.tsx +85 -4
- package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +3 -0
- package/generators/micro-react/templates/apps/layout/src/common/auth/tenant.ts +25 -0
- package/generators/micro-react/templates/apps/layout/src/common/auth/type.ts +41 -27
- package/generators/micro-react/templates/apps/layout/src/common/intl/formatLayoutMessage.ts +30 -0
- package/generators/micro-react/templates/apps/layout/src/common/intl/index.ts +6 -0
- package/generators/micro-react/templates/apps/layout/src/common/intl/intlRuntime.ts +14 -0
- package/generators/micro-react/templates/apps/layout/src/common/intl/localeMapping.ts +30 -0
- package/generators/micro-react/templates/apps/layout/src/common/intl/types.ts +14 -0
- package/generators/micro-react/templates/apps/layout/src/common/intl/useLayoutIntl.ts +40 -0
- package/generators/micro-react/templates/apps/layout/src/common/logger.ts +3 -4
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +148 -85
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +29 -6
- package/generators/micro-react/templates/apps/layout/src/common/micro/types.ts +23 -0
- package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +46 -2
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +74 -15
- package/generators/micro-react/templates/apps/layout/src/common/request/token-refresh.ts +2 -0
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +32 -6
- package/generators/micro-react/templates/apps/layout/src/components/PermissionFilter/index.tsx +51 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +10 -1
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/TenantDropdown.tsx +76 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/index.ts +1 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/tenant-dropdown.less +48 -0
- package/generators/micro-react/templates/apps/layout/src/constants/index.ts +1 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/index.ts +1 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/useMenuState.ts +18 -0
- package/generators/micro-react/templates/apps/layout/src/hooks/useTenant.ts +41 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +4 -1
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +21 -9
- package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +105 -60
- package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +28 -0
- package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +26 -0
- package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +7 -3
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +32 -0
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +148 -4
- package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +2 -1
- package/generators/micro-react/templates/apps/layout/src/services/user.ts +79 -21
- package/generators/micro-react/templates/apps/layout/typings.d.ts +16 -0
- package/generators/micro-react/templates/docs/package-shared.md +189 -0
- package/generators/micro-react/templates/package.json +1 -1
- package/generators/micro-react/templates/packages/common-intl/README.md +78 -368
- package/generators/micro-react/templates/packages/common-intl/package.json +3 -13
- package/generators/micro-react/templates/packages/common-intl/src/index.ts +5 -6
- package/generators/micro-react/templates/packages/common-intl/src/intl.ts +115 -28
- package/generators/micro-react/templates/packages/common-intl/src/umiLocaleBridge.ts +101 -0
- package/generators/micro-react/templates/packages/common-intl/tsconfig.json +2 -4
- package/generators/micro-react/templates/packages/shared/README.md +120 -0
- package/generators/micro-react/templates/packages/shared/package.json +26 -0
- package/generators/micro-react/templates/packages/shared/services/common/index.ts +43 -0
- package/generators/micro-react/templates/packages/shared/services/index.ts +21 -0
- package/generators/micro-react/templates/packages/shared/services/request.ts +43 -0
- package/generators/micro-react/templates/packages/shared/timezone/index.ts +228 -0
- package/generators/micro-react/templates/packages/shared/tsconfig.json +20 -0
- package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +6 -1
- package/generators/micro-react/templates/turbo.json +9 -1
- package/generators/subapp-react/index.js +28 -22
- package/generators/subapp-react/templates/homepage/README.md +1 -0
- 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 +1 -0
- package/generators/subapp-react/templates/homepage/config/config.ts +10 -0
- package/generators/subapp-react/templates/homepage/docs/feature-PermissionFilter/346/214/211/351/222/256/346/235/203/351/231/220.md +35 -0
- package/generators/subapp-react/templates/homepage/docs/feature-/345/233/275/351/231/205/345/214/226.md +124 -0
- package/generators/subapp-react/templates/homepage/package.json +3 -1
- package/generators/subapp-react/templates/homepage/src/app.tsx +104 -2
- package/generators/subapp-react/templates/homepage/src/common/intl/index.ts +15 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/intlRuntime.ts +14 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/localeMapping.ts +24 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/subappIntlConfig.ts +28 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/subappLocale.ts +18 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/subappOwnIntl.ts +63 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/types.ts +14 -0
- package/generators/subapp-react/templates/homepage/src/common/intl/useSubappIntl.ts +61 -0
- package/generators/subapp-react/templates/homepage/src/common/locale.ts +80 -0
- package/generators/subapp-react/templates/homepage/src/common/mainApp.ts +41 -2
- package/generators/subapp-react/templates/homepage/src/components/PermissionFilter/index.tsx +48 -0
- package/generators/subapp-react/templates/homepage/src/locales/en-US.ts +6 -0
- package/generators/subapp-react/templates/homepage/src/locales/zh-CN.ts +6 -0
- package/generators/subapp-react/templates/homepage/src/pages/index.less +10 -0
- package/generators/subapp-react/templates/homepage/src/pages/index.tsx +86 -1
- package/generators/subapp-react/templates/homepage/typings.d.ts +12 -0
- package/lib/utils.js +0 -1
- package/package.json +2 -2
- package/generators/micro-react/templates/apps/layout/docs/common-intl.md +0 -372
- package/generators/micro-react/templates/packages/common-intl/src/indexedDBUtils.ts +0 -51
- package/generators/micro-react/templates/packages/common-intl/src/utils.ts +0 -482
- package/generators/micro-react/templates/packages/common-intl/vite.config.ts +0 -25
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# PermissionFilter 按钮权限
|
|
2
|
+
|
|
3
|
+
> 创建时间:2025-03-27
|
|
4
|
+
|
|
5
|
+
## 功能概述
|
|
6
|
+
|
|
7
|
+
`PermissionFilter` 用于按**按钮级权限**控制 UI 是否渲染:传入权限标识 `permissionKey`,仅当当前用户具备该权限(或为超级用户)时渲染 `children`,否则渲染可选的 `fallback`。
|
|
8
|
+
|
|
9
|
+
- **主应用(layout)**:权限数据来自 `fetchUserInfo` 返回的用户信息字段 `button_perms`、`is_superuser`(与 `initialState.currentUser` 一致)。
|
|
10
|
+
- **子应用**:不直接请求用户接口,权限列表由主应用通过 qiankun 注入到子应用 props,与主应用同一套 `button_perms` 语义。
|
|
11
|
+
|
|
12
|
+
与侧栏菜单使用的 `menu_perms` / `routeKey` 不同,`button_perms` 面向页面内按钮、操作入口等细粒度控制。详见 [菜单权限控制](./feature-菜单权限控制.md) 中对 `button_perms` 的说明。
|
|
13
|
+
|
|
14
|
+
## 技术方案
|
|
15
|
+
|
|
16
|
+
### 技术栈
|
|
17
|
+
|
|
18
|
+
- React 18 + TypeScript
|
|
19
|
+
- 主应用:`useModel('@@initialState')` 读取 `currentUser`
|
|
20
|
+
- 子应用:`@/common/mainApp` 的 `useMainAppProps()`(内部 `useSyncExternalStore`,随 qiankun `update` 刷新)
|
|
21
|
+
|
|
22
|
+
### 主应用数据流
|
|
23
|
+
|
|
24
|
+
1. `GET /user/info/`(`services/user.ts` → `fetchUserInfo`)返回 `button_perms: string[]`、`is_superuser`。
|
|
25
|
+
2. `getInitialState`(`app.tsx`)在有 **token** 时拉取用户信息;免认证页同样需要 `currentUser` 时,逻辑为「有 token 即请求」,避免免认证页面无 `currentUser` 导致按钮权限恒为无权限。
|
|
26
|
+
3. `MicroAppLoader` 将 `button_perms`、`is_superuser` 放入传给子应用的 qiankun props,子应用 `setMainAppProps` 后可供 `PermissionFilter` 使用。
|
|
27
|
+
|
|
28
|
+
### 子应用数据流
|
|
29
|
+
|
|
30
|
+
1. 主应用 `MicroAppLoader` 构建 props 时写入 `button_perms`、`is_superuser`。
|
|
31
|
+
2. 子应用 `app.tsx` 中 qiankun `mount` / `update` 调用 `setMainAppProps(props)`。
|
|
32
|
+
3. `PermissionFilter` 通过 `useMainAppProps()` 订阅 props 变化;独立运行子应用时无主应用 props,默认视为无对应按钮权限(走 `fallback`)。
|
|
33
|
+
|
|
34
|
+
### 权限判定规则
|
|
35
|
+
|
|
36
|
+
1. `permissionKey` 为空或仅空白:视为**无权限**。
|
|
37
|
+
2. `is_superuser` 为真(含与现有 parser 一致的 `1`):视为**有全部按钮权限**(与 `isSuperuserUser` 行为一致)。
|
|
38
|
+
3. 否则:`button_perms.includes(permissionKey)` 为真则有权限。
|
|
39
|
+
|
|
40
|
+
## 文件清单
|
|
41
|
+
|
|
42
|
+
| 文件路径 | 说明 |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `src/components/PermissionFilter/index.tsx` | 主应用组件 |
|
|
45
|
+
| `src/components/MicroAppLoader/index.tsx` | 向子应用注入 `button_perms`、`is_superuser` |
|
|
46
|
+
| `src/services/user.ts` | `fetchUserInfo` 规范化 `button_perms` |
|
|
47
|
+
| `src/app.tsx` | `getInitialState` 拉取 `currentUser` |
|
|
48
|
+
|
|
49
|
+
子应用:
|
|
50
|
+
|
|
51
|
+
| 文件路径 | 说明 |
|
|
52
|
+
| --- | --- |
|
|
53
|
+
| `src/components/PermissionFilter/index.tsx` | 子应用组件(基于 `useMainAppProps`) |
|
|
54
|
+
| `src/common/mainApp.ts` | qiankun props、`useMainAppProps`、`subscribeMainAppProps` |
|
|
55
|
+
|
|
56
|
+
## API / 组件接口
|
|
57
|
+
|
|
58
|
+
### Props(主应用与子应用一致)
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
interface IPermissionFilterProps {
|
|
62
|
+
/** 按钮/操作权限标识,需在用户 button_perms 中命中 */
|
|
63
|
+
permissionKey: string;
|
|
64
|
+
children?: React.ReactNode;
|
|
65
|
+
/** 无权限时渲染;默认 null */
|
|
66
|
+
fallback?: React.ReactNode;
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 使用示例
|
|
71
|
+
|
|
72
|
+
### 主应用
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import PermissionFilter from '@/components/PermissionFilter';
|
|
76
|
+
|
|
77
|
+
<PermissionFilter
|
|
78
|
+
permissionKey="cs_web_btn_export"
|
|
79
|
+
fallback={<span>无权限</span>}
|
|
80
|
+
>
|
|
81
|
+
<Button type="primary">导出</Button>
|
|
82
|
+
</PermissionFilter>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
首页示例见 `src/pages/Home/index.tsx`;本地 Mock 可在 `mock/api.mock.ts` 的 `GET /user/info/` 响应 `data.button_perms` 中加入对应字符串。
|
|
86
|
+
|
|
87
|
+
### 子应用
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
import PermissionFilter from '@/components/PermissionFilter';
|
|
91
|
+
|
|
92
|
+
<PermissionFilter
|
|
93
|
+
permissionKey="cs_web_btn_subapp_demo"
|
|
94
|
+
fallback={<Alert type="warning" content="无权限" />}
|
|
95
|
+
>
|
|
96
|
+
<Button type="primary">操作</Button>
|
|
97
|
+
</PermissionFilter>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
示例见子应用 `src/pages/index.tsx`。嵌入主应用时,主应用用户信息中的 `button_perms` 需包含该 key;**独立运行**子应用时通常始终为无权限。
|
|
101
|
+
|
|
102
|
+
## Mock / 联调说明
|
|
103
|
+
|
|
104
|
+
- 主应用 Mock:`mock/api.mock.ts` 中 `button_perms` 数组需包含你在页面中使用的 `permissionKey`。
|
|
105
|
+
- 若页面在配置中为**免认证**(`accessControlEnabled: false`),仍依赖「有 token 时拉取用户信息」才能在主应用侧得到 `button_perms`;请确认 `app.tsx` 中 `getInitialState` 与当前模板一致。
|
|
106
|
+
|
|
107
|
+
## 注意事项
|
|
108
|
+
|
|
109
|
+
- 权限标识由后端/配置约定,需与 `button_perms` 中字符串完全一致(区分大小写)。
|
|
110
|
+
- 子应用请勿自行缓存过期的 `button_perms`;以 qiankun 传入的最新 props 为准(已用 `useMainAppProps` 订阅 `update`)。
|
|
111
|
+
|
|
112
|
+
## 相关文档
|
|
113
|
+
|
|
114
|
+
- [微前端模式](./feature-微前端模式.md)(MicroAppLoader、子应用加载)
|
|
115
|
+
- [菜单权限控制](./feature-菜单权限控制.md)(`menu_perms` 与 `button_perms` 分工)
|
|
116
|
+
- [路由与菜单解耦](./feature-路由与菜单解耦.md)
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# 国际化(common-intl 与 Umi 兜底)
|
|
2
|
+
|
|
3
|
+
> 创建时间:2026-04-07
|
|
4
|
+
|
|
5
|
+
## 功能概述
|
|
6
|
+
|
|
7
|
+
layout 主应用通过 **`<%= packageScope %>/common-intl`**(`packages/common-intl` 中转包)绑定中台 **`tag`**,在 **`app.tsx`** 中 **`configureLocale`** 与 Umi **`umi_locale`** 对齐;当 **`window.__MICO_CONFIG__.intl` 或 `commonIntl`** 含有效 **`tag`、`app_name`** 时视为启用中台,首屏在 **`render`** 中 **`fetchMultilingualData`**。业务侧统一使用 **`useLayoutIntl()`**:**`i18n` 优先**,与 `defaultMessage` 比较后未命中则 **Umi `formatMessage`**(`src/locales/*`)。未启用中台时 **仅 Umi**。
|
|
8
|
+
|
|
9
|
+
## 技术方案
|
|
10
|
+
|
|
11
|
+
### 技术栈
|
|
12
|
+
|
|
13
|
+
- Umi 4 `@umijs/max`:`locale` 运行时、`export const locale`、`useIntl`
|
|
14
|
+
- `<%= packageScope %>/common-intl`:`configureLocale`、`fetchMultilingualData`、`i18n`、`intl`(包内 `initIntl`)
|
|
15
|
+
- 门控与类型:`src/common/intl/intlRuntime.ts`、`types.ts`
|
|
16
|
+
|
|
17
|
+
### 核心实现
|
|
18
|
+
|
|
19
|
+
1. **`app.tsx`**:最早 **`configureLocale({ getLocale, setLocale })`**,与 **`getCurrentLocale`** / **`setLocaleToStorage`**(`src/common/locale.ts`)及 **`localeMapping`** 一致;**`export const locale`** 供 Umi 插件;**`render`** 仅在 **`isCommonIntlEnabled()`** 为真时调用 **`fetchMultilingualData`**(`request`、`Message`、`lang`、`LOCALE_REQUEST_URL`)。
|
|
20
|
+
2. **`useLayoutIntl.ts`**:读取 **`isCommonIntlEnabled()`**;启用时 **`i18n({ key, defaultMessage })`**,否则直接 **`useIntl().formatMessage`**;兜底规则与 **`defaultMessage`**、Umi 结果比较(同子应用 **`useSubappIntl`** 策略)。
|
|
21
|
+
3. **全局暴露**:**`window.CommonIntl`** 指向包模块,供子应用 **externals** 复用。
|
|
22
|
+
4. **包内实现**:**`packages/common-intl/src/intl.ts`** 中 **`initIntl`** 合并 **`__MICO_CONFIG__`** 与默认 tag,详见包 README。
|
|
23
|
+
|
|
24
|
+
## 文件清单(相对 `apps/layout`)
|
|
25
|
+
|
|
26
|
+
| 文件路径 | 说明 |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| `src/common/intl/useLayoutIntl.ts` | 页面统一 Hook |
|
|
29
|
+
| `src/common/intl/intlRuntime.ts` | `isCommonIntlEnabled`、`getCommonIntlConfig` |
|
|
30
|
+
| `src/common/intl/localeMapping.ts` | Umi locale ↔ `ILang` |
|
|
31
|
+
| `src/common/intl/types.ts` | `IMicoConfigCommonIntl` |
|
|
32
|
+
| `src/common/intl/index.ts` | 导出 `useLayoutIntl`、`intl`、`isCommonIntlEnabled` 等 |
|
|
33
|
+
| `src/common/locale.ts` | URL / storage / 浏览器语言(与 Umi 一致) |
|
|
34
|
+
| `src/app.tsx` | `configureLocale`、`locale`、`render`、**`window.CommonIntl`** |
|
|
35
|
+
| `config/config.ts` | `locale` 插件(`default`、`antd: false`、`baseNavigator: false`) |
|
|
36
|
+
| `src/locales/zh-CN.ts`、`en-US.ts` | Umi 兜底文案 |
|
|
37
|
+
| `src/pages/Home/index.tsx` | 国际化用法示例(可选参考) |
|
|
38
|
+
|
|
39
|
+
## API / 组件接口
|
|
40
|
+
|
|
41
|
+
### `useLayoutIntl`
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const {
|
|
45
|
+
formatMessage, // 与 useIntl 相同 descriptor / values 签名
|
|
46
|
+
commonIntlEnabled, // 是否启用中台(有效 tag + app_name)
|
|
47
|
+
locale, // 当前 Umi 风格 locale(getCurrentLocale)
|
|
48
|
+
intl, // 包内便捷对象,仅 commonIntlEnabled 为 true 时非 undefined
|
|
49
|
+
} = useLayoutIntl();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### `window.__MICO_CONFIG__.intl` / `commonIntl`
|
|
53
|
+
|
|
54
|
+
二者取其一(见 `intlRuntime`),字段含义一致:
|
|
55
|
+
|
|
56
|
+
| 字段 | 说明 |
|
|
57
|
+
| --- | --- |
|
|
58
|
+
| `tag` | 多语言中台 tag(必填) |
|
|
59
|
+
| `app_name` | 中台应用名(必填) |
|
|
60
|
+
| `indexedDBParams` | 可选本地缓存参数 |
|
|
61
|
+
|
|
62
|
+
## 使用示例
|
|
63
|
+
|
|
64
|
+
**运行时注入(示例):**
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
window.__MICO_CONFIG__ = {
|
|
68
|
+
commonIntl: {
|
|
69
|
+
tag: 'cs_fe_pc',
|
|
70
|
+
app_name: 'middle',
|
|
71
|
+
indexedDBParams: { dbName: 'mico_cs_web_i18n_db' },
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**页面:**
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import { useLayoutIntl } from '@/common/intl';
|
|
80
|
+
|
|
81
|
+
const { formatMessage, commonIntlEnabled, locale } = useLayoutIntl();
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<span>
|
|
85
|
+
{formatMessage({ id: 'page.home.title', defaultMessage: '首页' })}
|
|
86
|
+
</span>
|
|
87
|
+
);
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**包内预置文案(中台 key 亦可走 `i18n`):**
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { intl } from '@/common/intl';
|
|
94
|
+
|
|
95
|
+
intl.common_hello();
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## 设计决策
|
|
99
|
+
|
|
100
|
+
| 决策点 | 选择 | 理由 |
|
|
101
|
+
| --- | --- | --- |
|
|
102
|
+
| 门控 | `intl` 与 `commonIntl` 字段兼容 | 与注入脚本、历史配置一致 |
|
|
103
|
+
| 首屏拉取 | 仅 `isCommonIntlEnabled()` 时 `render` 内 fetch | 未配置中台时不阻塞首屏 |
|
|
104
|
+
| 合并策略 | `i18n` 与 Umi 二选一兜底 | 与微前端子应用 `useSubappIntl` 对齐,便于维护 |
|
|
105
|
+
|
|
106
|
+
## 已知限制与待改进
|
|
107
|
+
|
|
108
|
+
- **`LOCALE_REQUEST_URL`** 未配置时,行为依赖上游 **`fetchMultilingualData`** 实现。
|
|
109
|
+
- 中台文案与 **`src/locales`** 的 key 需自行约定,避免重复 id 语义冲突。
|
|
110
|
+
|
|
111
|
+
## 注意事项
|
|
112
|
+
|
|
113
|
+
- 修改语言时请走与 **`getCurrentLocale`** / **`setLocaleToStorage`** 一致的入口,避免与 **`configureLocale`** 脱节。
|
|
114
|
+
- 子应用不复制上述 **`render`** 逻辑时,嵌入主应用后通常仍依赖主应用已加载的 **`CommonIntl`**;独立运行子应用需自行 **`render` + fetch**(见子应用模板文档)。
|
|
115
|
+
|
|
116
|
+
## 相关文档
|
|
117
|
+
|
|
118
|
+
- [packages/common-intl README](../../../../packages/common-intl/README.md)
|
|
119
|
+
- [Layout 国际化(CLI 仓库总览)](../../../../../../docs/feat-layout-国际化翻译.md)
|
|
120
|
+
- [子应用国际化(模板内说明)](../../../../../subapp-react/templates/homepage/docs/feature-国际化.md)
|
|
121
|
+
- [微前端模式](./feature-微前端模式.md)(主应用暴露 `CommonIntl`)
|
|
@@ -136,6 +136,14 @@ interface MicroAppProps {
|
|
|
136
136
|
|
|
137
137
|
**说明**:子应用在 `mount` 生命周期中通过 `props` 接收这些数据。
|
|
138
138
|
|
|
139
|
+
### 国际化(common-intl)
|
|
140
|
+
|
|
141
|
+
- **共享文案(默认)**:主应用在 `render` 中拉取中台后,子应用 `import { intl } from '<%= packageScope %>/common-intl'` 即可(与主应用同一默认 tag),详见 `packages/common-intl/README.md`。
|
|
142
|
+
- **不同 tag**:上游支持多次 `initIntl`,子应用可对同一依赖再绑定一套 tag 并在子应用 `render` 中拉取;或直连 `@common-web/common-intl`,见 README「不同 tag」。
|
|
143
|
+
- **可选模板层**:若子应用需 `formatMessage` 式 API 与 Umi 兜底,见仓库 `docs/feat-subapp-国际化翻译.md`(生成项目根目录文档以实际为准)。
|
|
144
|
+
|
|
145
|
+
主应用 **不向** 子应用 props 传递 `commonIntl`;子应用 **locale** 以 props 与主应用对齐(见上表 `locale`)。
|
|
146
|
+
|
|
139
147
|
### 子应用 Props 更新机制
|
|
140
148
|
|
|
141
149
|
主应用通过 qiankun 的 `update()` 方法实时通知子应用状态变化,无需重新加载:
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# 菜单权限控制
|
|
2
2
|
|
|
3
|
-
> 创建时间:2026-01-24 更新时间:2026-
|
|
3
|
+
> 创建时间:2026-01-24 更新时间:2026-03-27(同步 `getMenuPage`:pageId 优先,path 兜底)
|
|
4
4
|
|
|
5
5
|
## 功能概述
|
|
6
6
|
|
|
7
|
-
基于用户信息中的 `
|
|
7
|
+
基于用户信息中的 **`menu_perms`**(菜单权限 code 数组,与 OpenAPI 8 `GET /user/info/` 一致)实现菜单和路由权限。非超级用户仅当 **`menu_perms` 包含对应 code** 时可见菜单项、可访问受控动态路由(`accessControlEnabled && routeKey` 时要求 `routeKey ∈ menu_perms`)。访问无权限路由时显示 403 页面。
|
|
8
8
|
|
|
9
9
|
**v2 更新**:支持认证(Authentication)与授权(Authorization)分离配置,可独立控制"跳过 SSO 登录"和"跳过菜单权限校验"。
|
|
10
10
|
|
|
@@ -54,22 +54,21 @@
|
|
|
54
54
|
│ 2. 权限校验 (layouts/index.tsx) │
|
|
55
55
|
│ isNoPermissionRoute(pathname)? │
|
|
56
56
|
│ ├── 是 → 跳过权限校验 │
|
|
57
|
-
│ └── 否 →
|
|
58
|
-
│
|
|
59
|
-
│
|
|
60
|
-
│ │
|
|
61
|
-
│ └── 否 → Tier 2: 页面级权限 │
|
|
62
|
-
│ adminOnly / routeKey│
|
|
57
|
+
│ └── 否 → PAGES 页面:adminOnly / │
|
|
58
|
+
│ accessControlEnabled+routeKey│
|
|
59
|
+
│ 与 menu_perms;无 page 时 │
|
|
60
|
+
│ 按菜单过滤结果兜底 │
|
|
63
61
|
│ 详见:路由与菜单解耦文档 │
|
|
64
62
|
└──────────────────────────────────────────┘
|
|
65
63
|
│
|
|
66
64
|
▼
|
|
67
65
|
┌──────────────────────────────────────────┐
|
|
68
66
|
│ 3. 菜单渲染 (menu/index.tsx) │
|
|
69
|
-
│ 按
|
|
67
|
+
│ 按 menu_perms 过滤(菜单项 code │
|
|
68
|
+
│ 与关联页 routeKey / nameKey 一致) │
|
|
70
69
|
│ 每个菜单项独立检查 isNoPermissionRoute│
|
|
71
70
|
│ ├── 匹配 → 该项始终显示 │
|
|
72
|
-
│ └── 不匹配 →
|
|
71
|
+
│ └── 不匹配 → code ∈ menu_perms │
|
|
73
72
|
└──────────────────────────────────────────┘
|
|
74
73
|
│
|
|
75
74
|
▼
|
|
@@ -111,23 +110,20 @@ page.accessControlEnabled === false?
|
|
|
111
110
|
├── 是 → 允许访问所有页面
|
|
112
111
|
│
|
|
113
112
|
▼
|
|
114
|
-
|
|
115
|
-
├── 是 →
|
|
116
|
-
│
|
|
117
|
-
│
|
|
118
|
-
│
|
|
119
|
-
│ ├──
|
|
120
|
-
│ └──
|
|
113
|
+
能关联到 PAGES 页面配置?
|
|
114
|
+
├── 是 → adminOnly === true?
|
|
115
|
+
│ ├── 是 → 403
|
|
116
|
+
│ accessControlEnabled === true?
|
|
117
|
+
│ ├── 是 → routeKey ∈ menu_perms?
|
|
118
|
+
│ │ ├── 是 → 允许
|
|
119
|
+
│ │ └── 否 → 403
|
|
120
|
+
│ └── 否 → 允许
|
|
121
121
|
│
|
|
122
122
|
▼
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
├── 是 → 检查 routeKey ∈ side_menus
|
|
128
|
-
│ ├── 匹配 → 允许访问
|
|
129
|
-
│ └── 不匹配 → 显示 403
|
|
130
|
-
└── 否 → 允许访问
|
|
123
|
+
路径仅在菜单路由中、无 page 元数据
|
|
124
|
+
└── 在 filterMenuItems 后的路由集中?
|
|
125
|
+
├── 是 → 允许
|
|
126
|
+
└── 否 → 403
|
|
131
127
|
```
|
|
132
128
|
|
|
133
129
|
## 文件清单
|
|
@@ -144,7 +140,8 @@ Tier 2: 隐藏页面(不在菜单中)
|
|
|
144
140
|
| --- | --- |
|
|
145
141
|
| `src/common/menu/types.ts` | 新增 `noPermissionRouteList` 类型定义 |
|
|
146
142
|
| `src/constants/index.ts` | 新增 `isNoPermissionRoute`、`getNoPermissionRouteList` 函数 |
|
|
147
|
-
| `src/common/
|
|
143
|
+
| `src/common/portal-data.ts` | `getMenuPage`:pageId 优先,无匹配再用 path 与页面 route |
|
|
144
|
+
| `src/common/menu/parser.ts` | `filterMenuItems`、`getMenuItemPermCode` 等,消费 `getMenuPage` |
|
|
148
145
|
| `src/layouts/index.tsx` | 新增 `isForbidden` 判断,集成 `isNoPermissionRoute` |
|
|
149
146
|
| `src/layouts/components/menu/index.tsx` | 集成菜单过滤,支持免权限校验路由 |
|
|
150
147
|
| `src/components/MicroAppLoader/index.tsx` | 集成 `isNoPermissionRoute`,免权限路由直接加载 |
|
|
@@ -194,33 +191,43 @@ function isAuthDisabled(): boolean;
|
|
|
194
191
|
interface MenuFilterOptions {
|
|
195
192
|
/** 是否是超级用户 */
|
|
196
193
|
isSuperuser?: boolean | number;
|
|
197
|
-
/**
|
|
198
|
-
|
|
194
|
+
/** 用户拥有的菜单权限 code(与页面 routeKey、菜单项一致) */
|
|
195
|
+
menuPerms?: string[];
|
|
199
196
|
}
|
|
200
197
|
```
|
|
201
198
|
|
|
199
|
+
### 菜单项如何关联到页面(与 `routeKey` / `menu_perms`)
|
|
200
|
+
|
|
201
|
+
`page` 类型菜单项的权限 code 来自关联页的 **`routeKey`**(当该页 `accessControlEnabled && routeKey` 时)。关联页由 **`getMenuPage(item)`**(`src/common/portal-data.ts`)解析:
|
|
202
|
+
|
|
203
|
+
1. **`pageId`**:在 `__MICO_PAGES__` 中按 `id` 查找,**命中则使用该页面**(与菜单 `path` 是否一致无关)。
|
|
204
|
+
2. **`path` + 页面 `route`**:上一步无结果时,用 `findPageByPath` 将菜单 `path` 与页面 `route` 匹配(精确 + `/*` 通配)。
|
|
205
|
+
|
|
206
|
+
详见 [路由与菜单解耦](./feature-路由与菜单解耦.md) 文档内「菜单项关联页面(getMenuPage)」小节。
|
|
207
|
+
|
|
202
208
|
### filterMenuItems
|
|
203
209
|
|
|
204
210
|
```typescript
|
|
205
211
|
/**
|
|
206
|
-
*
|
|
212
|
+
* 根据 menu_perms 过滤菜单项
|
|
213
|
+
* - page:关联页由 getMenuPage → routeKey(需校验时);link:nameKey
|
|
207
214
|
*/
|
|
208
|
-
function filterMenuItems(
|
|
209
|
-
items: MenuItem[],
|
|
210
|
-
options?: MenuFilterOptions,
|
|
211
|
-
parentPath?: string,
|
|
212
|
-
): MenuItem[];
|
|
215
|
+
function filterMenuItems(items: MenuItem[], options?: MenuFilterOptions): MenuItem[];
|
|
213
216
|
```
|
|
214
217
|
|
|
215
218
|
### 用户信息相关字段
|
|
216
219
|
|
|
217
220
|
```typescript
|
|
218
221
|
interface IUserInfo {
|
|
219
|
-
is_superuser: boolean
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
222
|
+
is_superuser: boolean;
|
|
223
|
+
menu_perms: string[];
|
|
224
|
+
button_perms: string[];
|
|
225
|
+
app_perms: string[];
|
|
226
|
+
region_perms: string[];
|
|
227
|
+
name: string;
|
|
228
|
+
user_name: string;
|
|
229
|
+
username: string;
|
|
230
|
+
// …见 src/common/auth/type.ts IUserInfo / IUserInfoApiData
|
|
224
231
|
}
|
|
225
232
|
```
|
|
226
233
|
|
|
@@ -264,43 +271,41 @@ export const NO_AUTH_ROUTE_LIST: string[] = [
|
|
|
264
271
|
export const NO_PERMISSION_ROUTE_LIST: string[] = ['/403', '/404'];
|
|
265
272
|
```
|
|
266
273
|
|
|
267
|
-
###
|
|
274
|
+
### 菜单过滤结果(示意)
|
|
268
275
|
|
|
269
276
|
```
|
|
270
|
-
|
|
271
|
-
├──
|
|
272
|
-
├──
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
│ └── 抽样检查 ❌ 不在白名单
|
|
276
|
-
└── 权限管理 ❌ adminOnly=true,非超级用户不可见
|
|
277
|
+
menu_perms 含 code "a"、"b.c"
|
|
278
|
+
├── 页面菜单(routeKey=a) ✅
|
|
279
|
+
├── 页面菜单(routeKey 不在列表) ❌
|
|
280
|
+
├── 分组(仅容器,有可见子项) ✅ 作为父级保留
|
|
281
|
+
└── adminOnly 菜单 ❌ 非超管
|
|
277
282
|
```
|
|
278
283
|
|
|
279
284
|
### Layout 中的权限判断
|
|
280
285
|
|
|
281
286
|
```tsx
|
|
282
|
-
// layouts/index.tsx —
|
|
287
|
+
// layouts/index.tsx — 以 PAGES + menu_perms 为主,无 page 时菜单路由兜底
|
|
283
288
|
const isForbidden = useMemo(() => {
|
|
284
289
|
if (isAuthDisabled()) return false;
|
|
285
290
|
if (isNoPermissionRoute(location.pathname)) return false;
|
|
286
|
-
if (!currentRoute) return false;
|
|
291
|
+
if (!currentRoute) return false;
|
|
287
292
|
if (isSuperuserUser(currentUser?.is_superuser)) return false;
|
|
288
293
|
|
|
289
|
-
|
|
294
|
+
const page =
|
|
295
|
+
currentRoute.pageConfig ??
|
|
296
|
+
(hasPages() ? findPageByPath(getPages(), location.pathname) : undefined);
|
|
297
|
+
|
|
298
|
+
if (page) {
|
|
299
|
+
if (page.adminOnly) return true;
|
|
300
|
+
if (!page.accessControlEnabled) return false;
|
|
301
|
+
const menuPerms = currentUser?.menu_perms || [];
|
|
302
|
+
return !page.routeKey || !menuPerms.includes(page.routeKey);
|
|
303
|
+
}
|
|
304
|
+
|
|
290
305
|
const inAllMenu = findRouteByPath(allMenuRoutes, location.pathname);
|
|
291
306
|
if (inAllMenu) {
|
|
292
307
|
return !findRouteByPath(allowedMenuRoutes, location.pathname);
|
|
293
308
|
}
|
|
294
|
-
|
|
295
|
-
// Tier 2: 隐藏页面级权限
|
|
296
|
-
if (!hasWindowPages()) return false;
|
|
297
|
-
const page = findPageByPath(getWindowPages(), location.pathname);
|
|
298
|
-
if (!page) return false;
|
|
299
|
-
if (page.adminOnly) return true;
|
|
300
|
-
if (page.accessControlEnabled) {
|
|
301
|
-
const sideMenus = (currentUser?.side_menus || []) as string[];
|
|
302
|
-
return !page.routeKey || !sideMenus.includes(page.routeKey);
|
|
303
|
-
}
|
|
304
309
|
return false;
|
|
305
310
|
}, [...]);
|
|
306
311
|
```
|
|
@@ -323,6 +328,7 @@ if (!isAuthReady) {
|
|
|
323
328
|
```
|
|
324
329
|
|
|
325
330
|
判断优先级:
|
|
331
|
+
|
|
326
332
|
1. `isAuthDisabled()` — 全局关闭权限,直接放行
|
|
327
333
|
2. `isPageAuthFree` — **PAGES 数据驱动**,页面 `accessControlEnabled === false` 时跳过认证和权限校验
|
|
328
334
|
3. `isNoAuthRoute()` — 静态配置兜底,免认证路由(PAGES 未注入时的降级保护)
|
|
@@ -341,9 +347,9 @@ if (!isAuthReady) {
|
|
|
341
347
|
| --- | --- | --- |
|
|
342
348
|
| accessControlEnabled 统一控制 | `false` 同时跳过认证和授权 | PAGES 数据驱动,一个字段即可标记公开页面,无需重复配置 noAuthRouteList |
|
|
343
349
|
| 认证与授权分离 | 两个独立配置项 | 不同场景需要不同组合,如"需要登录但不需要权限"的个人设置页 |
|
|
344
|
-
| 权限模型 | 白名单 (`
|
|
350
|
+
| 权限模型 | 白名单 (`menu_perms`) | 后端返回的 code 列表为允许集合;与 `routeKey` / 菜单项一一对应 |
|
|
345
351
|
| 403 处理 | 原地渲染组件 | 保持 URL 不变,用户体验更好,便于分享链接 |
|
|
346
|
-
| 父级菜单显示 |
|
|
352
|
+
| 父级菜单显示 | 子项驱动 | `group` 无独立 code,有可见子项时保留为容器 |
|
|
347
353
|
| 超级用户 | 跳过所有检查 | 管理员需要完整访问权限 |
|
|
348
354
|
| 免权限路由的菜单 | 按菜单项独立判断 | 每个菜单项根据自身路由是否匹配 noPermissionRouteList 独立决定显示,避免菜单随当前页面变化 |
|
|
349
355
|
| 免认证路由的子应用 | 直接加载 | 不等待 currentUser,免认证路由本身不需要登录 |
|
|
@@ -370,9 +376,9 @@ if (!isAuthReady) {
|
|
|
370
376
|
2. 检查 isNoPermissionRoute() → 在免权限列表中则允许
|
|
371
377
|
3. 检查 currentRoute(PAGES 中的路由)→ 不存在则非动态路由,交给 Umi
|
|
372
378
|
4. 检查 isSuperuserUser() → 超级用户放行
|
|
373
|
-
5.
|
|
374
|
-
6.
|
|
375
|
-
7.
|
|
379
|
+
5. 有 PAGES 页面元数据 → adminOnly / accessControlEnabled + routeKey ∈ menu_perms
|
|
380
|
+
6. 无页面元数据但在菜单路由中 → 检查 allowedMenuRoutes(已按 menu_perms 过滤)
|
|
381
|
+
7. 其他 → 放行
|
|
376
382
|
```
|
|
377
383
|
|
|
378
384
|
### 设计理由
|
|
@@ -384,15 +390,15 @@ if (!isAuthReady) {
|
|
|
384
390
|
|
|
385
391
|
### 影响
|
|
386
392
|
|
|
387
|
-
当用户 `is_superuser=false` 且 `
|
|
393
|
+
当用户 `is_superuser=false` 且 `menu_perms=[]` 时:
|
|
388
394
|
|
|
389
|
-
| 路由
|
|
390
|
-
|
|
|
391
|
-
| `/` (Home)
|
|
392
|
-
| `/403`
|
|
393
|
-
| `/404`
|
|
394
|
-
| `/user/login`
|
|
395
|
-
| `/queue-management` | ❌ 显示 403 | 动态路由,无权限
|
|
395
|
+
| 路由 | 结果 | 原因 |
|
|
396
|
+
| ------------------- | ----------- | ---------------------------------- |
|
|
397
|
+
| `/` (Home) | ✅ 正常显示 | 静态路由,不受权限控制 |
|
|
398
|
+
| `/403` | ✅ 正常显示 | 在 `noPermissionRouteList` 中 |
|
|
399
|
+
| `/404` | ✅ 正常显示 | 在 `noPermissionRouteList` 中 |
|
|
400
|
+
| `/user/login` | ✅ 正常显示 | 静态路由 + 在 `noAuthRouteList` 中 |
|
|
401
|
+
| `/queue-management` | ❌ 显示 403 | 动态路由,无权限 |
|
|
396
402
|
|
|
397
403
|
### 如需控制静态路由
|
|
398
404
|
|
|
@@ -406,11 +412,11 @@ if (!isAuthReady) {
|
|
|
406
412
|
|
|
407
413
|
- `noAuthRouteList` 和 `noPermissionRouteList` 是独立的,需要根据场景分别配置
|
|
408
414
|
- 两个列表都支持 `/*` 后缀进行前缀匹配
|
|
409
|
-
- `
|
|
410
|
-
- `
|
|
411
|
-
- `
|
|
415
|
+
- `menu_perms` 为空时,非超级用户没有任何菜单权限(`page`/`link` 项)
|
|
416
|
+
- 菜单项权限 code 与页面 `routeKey` 一致;`page` 类型先通过 **`getMenuPage`** 得到关联页再取 `routeKey`,否则回退 `nameKey`(link)
|
|
417
|
+
- `button_perms` 可用于按钮级权限(layout 侧栏不直接消费)
|
|
412
418
|
- 403 页面在 Layout 内渲染,不会触发路由跳转
|
|
413
|
-
-
|
|
419
|
+
- 调试时可在控制台过滤 `routePermission` 查看结构化权限日志(详见 [路由权限日志](./feature-路由权限日志.md))
|
|
414
420
|
- **常见问题**:配置了 `noAuthRouteList` 但页面仍显示 403,需同时配置 `noPermissionRouteList`
|
|
415
421
|
|
|
416
422
|
## 相关文档
|