generator-mico-cli 0.2.29 → 0.2.31
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 +10 -7
- package/generators/micro-react/README.md +34 -0
- package/generators/micro-react/index.js +19 -1
- 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/apps/layout/config/config.dev.ts +29 -3
- package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +10 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +10 -0
- package/generators/micro-react/templates/apps/layout/config/config.prod.ts +12 -0
- package/generators/micro-react/templates/apps/layout/config/config.ts +0 -15
- package/generators/micro-react/templates/apps/layout/docs/arch-/346/227/245/345/277/227/344/270/216/345/270/270/351/207/217.md +16 -8
- package/generators/micro-react/templates/apps/layout/docs/feat-/346/236/204/345/273/272define/344/270/216/345/205/215/350/256/244/350/257/201/345/210/235/345/247/213/346/200/201.md +49 -3
- 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 +3 -1
- 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 +4 -4
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +14 -0
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +22 -2
- package/generators/micro-react/templates/apps/layout/package.json +3 -2
- package/generators/micro-react/templates/apps/layout/src/app.tsx +68 -9
- package/generators/micro-react/templates/apps/layout/src/common/auth/auth-check-path.ts +14 -0
- package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +4 -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 +28 -2
- 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 +50 -18
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +27 -0
- package/generators/micro-react/templates/apps/layout/src/common/micro/types.ts +23 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +82 -20
- 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 +28 -6
- 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 +18 -6
- package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +11 -0
- package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +10 -0
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +27 -0
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +108 -12
- package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +2 -1
- package/generators/micro-react/templates/apps/layout/src/services/user.ts +53 -2
- 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 +3 -1
- package/generators/micro-react/templates/packages/common-intl/package.json +1 -1
- package/generators/micro-react/templates/packages/common-intl/src/index.ts +4 -2
- package/generators/micro-react/templates/packages/common-intl/src/intl.ts +104 -8
- package/generators/micro-react/templates/packages/common-intl/src/umiLocaleBridge.ts +101 -0
- 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/README.md +43 -0
- package/generators/subapp-react/index.js +2 -0
- package/generators/subapp-react/templates/homepage/README.md +5 -1
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +20 -0
- package/generators/subapp-react/templates/homepage/config/config.ts +10 -15
- 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 +2 -1
- package/generators/subapp-react/templates/homepage/src/app.tsx +100 -5
- 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/logger.ts +50 -18
- package/generators/subapp-react/templates/homepage/src/common/mainApp.ts +2 -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 +51 -0
- package/generators/subapp-react/templates/homepage/typings.d.ts +12 -0
- package/generators/subapp-umd/README.md +37 -0
- package/package.json +1 -1
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
# 菜单权限控制
|
|
2
2
|
|
|
3
|
-
> 创建时间:2026-01-24 更新时间:2026-03-27(同步 `getMenuPage`:pageId 优先,path
|
|
3
|
+
> 创建时间:2026-01-24 更新时间:2026-03-27(同步 `getMenuPage`:pageId 优先,path 兜底);2026-05-13(`getInitialState` 与 `authCheckPath` 说明链至 feat-构建define)
|
|
4
4
|
|
|
5
5
|
## 功能概述
|
|
6
6
|
|
|
@@ -84,6 +84,8 @@
|
|
|
84
84
|
└──────────────────────────────────────────────┘
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
**首屏 `getInitialState` 与步骤 1**:若 URL 为 **`/`** 且配置了 **`__MICO_CONFIG__.defaultPath`**(非 `/`),`onRouteChange` 会 **`replace`** 到默认路径,首屏瞬间 **`location.pathname` 可能仍为 `/`**。此时步骤 1 不能只看 **`pathname`**,需用 **`authCheckPath`**(见 [feat-构建define与免认证初始态](./feat-构建define与免认证初始态.md) 中「`defaultPath` 与 `authCheckPath`」)。图中步骤 1 的「pathname」在 **`getInitialState`** 语境下指 **`authCheckPath`**;从免认证页**手动**进入认证页时,地址已是认证路径,无「`/` 壳 vs `defaultPath` 目标」错位。**客户端 SPA** 下进入认证页后,还会由 **`useRoutePermissionRefresh` → `@@initialState.refresh()`** 重跑 `getInitialState`,在 **有 token** 时拉 **`fetchUserInfo`**(详见该文档「为何客户端从免认证跳进认证页后会拉取用户权限」)。
|
|
88
|
+
|
|
87
89
|
### 权限判断逻辑(详细)
|
|
88
90
|
|
|
89
91
|
```
|
|
@@ -9,14 +9,14 @@
|
|
|
9
9
|
1. **`routePermission`**:`BasicLayout` 中 **`isForbidden` 路由权限判定**(为何放行 / 为何 403)。
|
|
10
10
|
2. **`menuFilter`**:`filterMenuItems` 中**被隐藏的菜单项**及**隐藏原因**(侧栏不可见条目)。
|
|
11
11
|
|
|
12
|
-
日志均经 `layoutLogger`
|
|
12
|
+
日志均经 `layoutLogger` 输出;**默认仅开发环境**会在控制台看到 `log`;生产首屏带 `?debug=1`(等)时行为与开发一致,见 [日志与常量](./arch-日志与常量.md)。
|
|
13
13
|
|
|
14
14
|
## 技术方案
|
|
15
15
|
|
|
16
16
|
### 技术栈
|
|
17
17
|
|
|
18
18
|
- 框架:React 18 + @umijs/max
|
|
19
|
-
- 日志:`apps/layout/src/common/logger.ts` 的 `layoutLogger
|
|
19
|
+
- 日志:`apps/layout/src/common/logger.ts` 的 `layoutLogger`(开发环境或生产首屏 `debug` query 时输出 `log`)
|
|
20
20
|
|
|
21
21
|
### 核心实现
|
|
22
22
|
|
|
@@ -142,13 +142,13 @@ menuFilter
|
|
|
142
142
|
| 决策点 | 选择 | 理由 |
|
|
143
143
|
|--------|------|------|
|
|
144
144
|
| 日志前缀 | 固定 `'routePermission'` + 对象载荷 | 可过滤、可结构化,便于 AI/人工检索 |
|
|
145
|
-
| 生产环境 |
|
|
145
|
+
| 生产环境 | 默认静默 `log` | `layoutLogger` 在 production 下 `log`/`info`/`warn` 为 noop;首屏 `?debug=1` 等同开发 |
|
|
146
146
|
| 无 `currentRoute` 不打日志 | 跳过 | 静态路由访问频繁,避免噪音 |
|
|
147
147
|
| `menuFilter` 与超管/关权限 | 不打 | 无过滤效果时无隐藏项可记 |
|
|
148
148
|
|
|
149
149
|
## 已知限制与待改进
|
|
150
150
|
|
|
151
|
-
-
|
|
151
|
+
- **默认**仅开发环境会在控制台看到本功能的 `log`;生产首屏带 `?debug=1`(等)时亦会输出,见 [日志与常量](./arch-日志与常量.md)。线上问题仍主要依赖 Sentry 或其它监控。
|
|
152
152
|
- `renderContent` 另有 `renderContent:` 日志,与 `routePermission` 独立,排查时可同时参考。
|
|
153
153
|
|
|
154
154
|
## 注意事项
|
|
@@ -116,6 +116,20 @@ const mockMenus: MockMenuItem[] = [
|
|
|
116
116
|
adminOnly: true,
|
|
117
117
|
children: [],
|
|
118
118
|
},
|
|
119
|
+
{
|
|
120
|
+
id: 9,
|
|
121
|
+
name: '小组管理',
|
|
122
|
+
nameEn: 'Group Management',
|
|
123
|
+
nameKey: 'cs_web_menu_group_management',
|
|
124
|
+
type: 'page',
|
|
125
|
+
path: '/group-management',
|
|
126
|
+
icon: 'Group',
|
|
127
|
+
enabled: true,
|
|
128
|
+
sortOrder: 4,
|
|
129
|
+
pageId: 125,
|
|
130
|
+
adminOnly: true,
|
|
131
|
+
children: [],
|
|
132
|
+
}
|
|
119
133
|
];
|
|
120
134
|
|
|
121
135
|
export default mockMenus;
|
|
@@ -31,7 +31,7 @@ const mockPages: PublicPageItem[] = [
|
|
|
31
31
|
// htmlUrl:
|
|
32
32
|
// 'https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.2/login/index.html',
|
|
33
33
|
htmlUrl:
|
|
34
|
-
'https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.
|
|
34
|
+
'https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.9/login/index.html',
|
|
35
35
|
jsUrls: [],
|
|
36
36
|
cssUrls: [],
|
|
37
37
|
prefixPath: '/user',
|
|
@@ -51,7 +51,27 @@ const mockPages: PublicPageItem[] = [
|
|
|
51
51
|
// htmlUrl:
|
|
52
52
|
// 'https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.3/permission/index.html',
|
|
53
53
|
htmlUrl:
|
|
54
|
-
'https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.
|
|
54
|
+
'https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.9/permission/index.html',
|
|
55
|
+
jsUrls: [],
|
|
56
|
+
cssUrls: [],
|
|
57
|
+
prefixPath: '',
|
|
58
|
+
routeMode: 'prefix',
|
|
59
|
+
enabled: true,
|
|
60
|
+
accessControlEnabled: true,
|
|
61
|
+
adminOnly: true,
|
|
62
|
+
routeKey: null,
|
|
63
|
+
mainDocumentId: 59,
|
|
64
|
+
version: 'v2026.02.26-04.13-419',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 125,
|
|
68
|
+
name: '小组管理',
|
|
69
|
+
route: '/group-management',
|
|
70
|
+
base: '/',
|
|
71
|
+
// htmlUrl:
|
|
72
|
+
// 'https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.3/permission/index.html',
|
|
73
|
+
htmlUrl:
|
|
74
|
+
'https://cdn-portal.micoplatform.com/portal-center/common-web/0.0.9/group-management/index.html',
|
|
55
75
|
jsUrls: [],
|
|
56
76
|
cssUrls: [],
|
|
57
77
|
prefixPath: '',
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"test": "cross-env UMI_ENV=test max dev"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
+
"<%= packageScope %>/common-intl": "workspace:*",
|
|
26
|
+
"<%= packageScope %>/shared": "workspace:*",
|
|
25
27
|
"@mico-platform/ui": "<%= micoUiVersion %>",
|
|
26
28
|
"@mico-platform/theme": "<%= themeVersion %>",
|
|
27
29
|
"@umijs/max": "^4.6.15",
|
|
@@ -29,8 +31,7 @@
|
|
|
29
31
|
"qiankun": "^2.10.16",
|
|
30
32
|
"react": "^18.2.0",
|
|
31
33
|
"react-dom": "^18.2.0",
|
|
32
|
-
"spark-md5": "^3.0.2"
|
|
33
|
-
"<%= packageScope %>/common-intl": "workspace:*"
|
|
34
|
+
"spark-md5": "^3.0.2"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@types/react": "^18.0.33",
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import { history, type RequestConfig } from '@umijs/max';
|
|
2
2
|
import { addGlobalUncaughtErrorHandler } from 'qiankun';
|
|
3
|
+
import * as CommonWebSentry from '@common-web/sentry';
|
|
3
4
|
import { SentryErrorBoundary } from '@common-web/sentry';
|
|
4
5
|
import { errorConfig } from './requestErrorConfig';
|
|
5
6
|
// 将 @mico-platform/ui 暴露到 window,供子应用 externals 使用
|
|
6
7
|
import * as micoUI from '@mico-platform/ui';
|
|
8
|
+
import dayjs from 'dayjs';
|
|
7
9
|
import React from 'react';
|
|
8
10
|
import ReactDOM from 'react-dom';
|
|
9
11
|
|
|
12
|
+
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
|
13
|
+
import dayjsTimezone from 'dayjs/plugin/timezone';
|
|
14
|
+
import dayjsUtc from 'dayjs/plugin/utc';
|
|
10
15
|
import { request as commonRequest } from './common/request';
|
|
16
|
+
import { configureRequest } from '<%= packageScope %>/shared/services';
|
|
17
|
+
import { configureTimezone } from '<%= packageScope %>/shared/timezone';
|
|
18
|
+
import { getTimezone } from '@/common/helpers';
|
|
11
19
|
import {
|
|
20
|
+
configureLocale,
|
|
12
21
|
fetchMultilingualData,
|
|
13
|
-
getCurrentLocale as getIntlLocale,
|
|
14
22
|
type ILang,
|
|
15
23
|
} from '<%= packageScope %>/common-intl';
|
|
16
24
|
import * as CommonIntl from '<%= packageScope %>/common-intl';
|
|
25
|
+
import { resolveAuthCheckPath } from './common/auth/auth-check-path';
|
|
17
26
|
import { getStoredAuthToken } from './common/auth/auth-manager';
|
|
18
27
|
import type { IUserInfo } from './common/auth/type';
|
|
19
28
|
import { fetchUserInfo } from './services/user';
|
|
@@ -30,10 +39,32 @@ import {
|
|
|
30
39
|
} from './common/request/sso';
|
|
31
40
|
import { initTheme } from './common/theme';
|
|
32
41
|
import MicroAppLoader from './components/MicroAppLoader';
|
|
42
|
+
import { isCommonIntlEnabled } from '@/common/intl';
|
|
33
43
|
import { isNoAuthRoute } from '@/constants';
|
|
34
|
-
import { getCurrentLocale } from '@/common/locale';
|
|
35
44
|
import '@mico-platform/theme/dist/css/theme.css';
|
|
36
45
|
import './global.less';
|
|
46
|
+
import { getCurrentLocale, setLocaleToStorage } from './common/locale';
|
|
47
|
+
import { ilangToUmiLocale, umiLocaleToILang } from '@/common/intl/localeMapping';
|
|
48
|
+
|
|
49
|
+
// 主应用:将统一 request 注入 shared 包,供共享服务层(getRequest)使用
|
|
50
|
+
// 注意:模块顶层注入,确保任何业务调用 getRequest() 之前已就绪
|
|
51
|
+
configureRequest(commonRequest as <T = unknown>(url: string, options?: Record<string, unknown>) => Promise<T>);
|
|
52
|
+
|
|
53
|
+
// 主应用:扩展 dayjs 插件并注入「当前 IANA 时区」获取器,供 shared/timezone 使用
|
|
54
|
+
// 说明:apps/layout/src/common/helpers.ts 已扩展 utc/timezone,此处再补 customParseFormat(shared/timezone 的 parseDateInTimezone 需要它)
|
|
55
|
+
// dayjs.extend 内部去重,重复调用安全
|
|
56
|
+
dayjs.extend(dayjsUtc);
|
|
57
|
+
dayjs.extend(dayjsTimezone);
|
|
58
|
+
dayjs.extend(customParseFormat);
|
|
59
|
+
configureTimezone({ getTimezone });
|
|
60
|
+
|
|
61
|
+
// 主应用:common-intl initIntl 的 getLocale/setLocale 与 Umi `umi_locale` 单一数据源对齐(见 packages/common-intl configureLocale)
|
|
62
|
+
configureLocale({
|
|
63
|
+
getLocale: () => umiLocaleToILang(getCurrentLocale()),
|
|
64
|
+
setLocale: (lang: ILang) => {
|
|
65
|
+
setLocaleToStorage(ilangToUmiLocale(lang));
|
|
66
|
+
},
|
|
67
|
+
});
|
|
37
68
|
|
|
38
69
|
// ==================== qiankun 全局错误处理 ====================
|
|
39
70
|
// 捕获子应用运行时未捕获的异常,防止页面崩溃
|
|
@@ -87,15 +118,31 @@ if (typeof window !== 'undefined') {
|
|
|
87
118
|
// 注意:这里不阻止错误冒泡,让控制台仍能显示原始错误
|
|
88
119
|
// 如果需要阻止,可以 return true
|
|
89
120
|
});
|
|
121
|
+
|
|
122
|
+
// 注入构建信息
|
|
123
|
+
if (typeof window !== 'undefined') {
|
|
124
|
+
window.__MICO_BUILD__ = {
|
|
125
|
+
branchOrTag: process.env.BRANCH_OR_TAG,
|
|
126
|
+
gitCommit: process.env.GIT_COMMIT,
|
|
127
|
+
version: process.env.VERSION,
|
|
128
|
+
buildTime: dayjs(process.env.BUILD_TIME).format('YYYY-MM-DD HH:mm:ss'),
|
|
129
|
+
buildNumber: process.env.BUILD_NUMBER,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
90
132
|
}
|
|
91
133
|
|
|
92
134
|
// ==================== 微前端共享依赖 ====================
|
|
93
135
|
// 将公共库暴露到 window,供子应用复用,避免重复打包
|
|
94
136
|
// 子应用通过 externals 配置引用这些全局变量
|
|
95
|
-
if (typeof window !== 'undefined') {
|
|
137
|
+
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
|
|
96
138
|
const win = window as unknown as Record<string, unknown>;
|
|
97
139
|
win.React = React;
|
|
98
140
|
win.ReactDOM = ReactDOM;
|
|
141
|
+
win.CommonWebSentry = CommonWebSentry;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (typeof window !== 'undefined') {
|
|
145
|
+
const win = window as unknown as Record<string, unknown>;
|
|
99
146
|
win.micoUI = micoUI;
|
|
100
147
|
win.CommonIntl = CommonIntl;
|
|
101
148
|
}
|
|
@@ -117,15 +164,22 @@ export const locale = {
|
|
|
117
164
|
|
|
118
165
|
// ==================== 国际化数据预加载 ====================
|
|
119
166
|
|
|
120
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* 配置了有效 `__MICO_CONFIG__.commonIntl` 时首屏拉取中台文案;失败仍继续渲染。
|
|
169
|
+
* @see generators/micro-react/templates/packages/common-intl/README.md
|
|
170
|
+
*/
|
|
121
171
|
export function render(oldRender: () => void): void {
|
|
172
|
+
if (!isCommonIntlEnabled()) {
|
|
173
|
+
oldRender();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
122
176
|
fetchMultilingualData({
|
|
123
177
|
requestInstance: commonRequest,
|
|
124
178
|
messageInstance: {
|
|
125
179
|
error: micoUI.Message.error,
|
|
126
180
|
warning: micoUI.Message.warning,
|
|
127
181
|
},
|
|
128
|
-
lang:
|
|
182
|
+
lang: umiLocaleToILang(getCurrentLocale()) as ILang,
|
|
129
183
|
localeRequestUrl: process.env.LOCALE_REQUEST_URL,
|
|
130
184
|
})
|
|
131
185
|
.then(oldRender)
|
|
@@ -157,16 +211,21 @@ export async function getInitialState(): Promise<{
|
|
|
157
211
|
};
|
|
158
212
|
|
|
159
213
|
const { location } = history;
|
|
160
|
-
|
|
161
|
-
|
|
214
|
+
// 如果当前路径是 / 且配置了 defaultPath(onRouteChange 会立即重定向),
|
|
215
|
+
// 用目标路径判断是否需要认证,避免用免认证的兜底页绕过目标页的认证要求(与 resolveAuthCheckPath、sso handleAuthFailureRedirect 一致)
|
|
216
|
+
const authCheckPath = resolveAuthCheckPath(location.pathname);
|
|
217
|
+
|
|
218
|
+
const noAuthRoute = isNoAuthRoute(authCheckPath);
|
|
219
|
+
const skipAuth = noAuthRoute || isPageAuthFree(authCheckPath);
|
|
162
220
|
|
|
163
221
|
// 非免认证路由:走 SSO 流程
|
|
164
222
|
if (!skipAuth) {
|
|
165
223
|
await ensureSsoSession();
|
|
166
224
|
}
|
|
167
225
|
|
|
168
|
-
//
|
|
169
|
-
//
|
|
226
|
+
// 仅在「需认证」路径且本地已有 token 时拉取用户信息(登录态刷新等)。
|
|
227
|
+
// 免认证 / 页面级免认证路径不强制 SSO,本地未必有 token;不进入本分支则无 currentUser,
|
|
228
|
+
// PermissionFilter、MicroAppLoader 注入子应用的 button_perms 等按无登录用户(无权限)处理。
|
|
170
229
|
if (getStoredAuthToken() && !skipAuth) {
|
|
171
230
|
const userInfo = await fetchUserInfoFn();
|
|
172
231
|
if (userInfo) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 与 `app.tsx` 中 `getInitialState` 的鉴权路径语义一致:
|
|
3
|
+
* 访问 `/` 且配置了 `window.__MICO_CONFIG__.defaultPath`(非 `/`)时,`onRouteChange` 会 `replace` 到该路径,
|
|
4
|
+
* 首屏 `location.pathname` 可能仍为 `/`,但免认证 / SSO 失败重定向等判断应以**目标路径**为准。
|
|
5
|
+
*/
|
|
6
|
+
export function resolveAuthCheckPath(pathname: string): string {
|
|
7
|
+
if (typeof window === 'undefined') {
|
|
8
|
+
return pathname;
|
|
9
|
+
}
|
|
10
|
+
const defaultPath = window.__MICO_CONFIG__?.defaultPath;
|
|
11
|
+
const willRedirectToDefault =
|
|
12
|
+
pathname === '/' && !!defaultPath && defaultPath !== '/';
|
|
13
|
+
return willRedirectToDefault ? defaultPath : pathname;
|
|
14
|
+
}
|
|
@@ -2,12 +2,16 @@ import { authLogger } from '@/common/logger';
|
|
|
2
2
|
import { checkStaffAuth } from '@/services/auth';
|
|
3
3
|
import { handleAuthFailureRedirect } from '../request';
|
|
4
4
|
import { clearStoredTokens, getAuthInfo } from './auth-manager';
|
|
5
|
+
import { clearStoredTenantCode } from './tenant';
|
|
5
6
|
|
|
6
7
|
// 导出 STORAGE_KEYS 常量
|
|
7
8
|
export { STORAGE_KEYS } from '../../constants';
|
|
9
|
+
export { resolveAuthCheckPath } from './auth-check-path';
|
|
8
10
|
|
|
9
11
|
export function logout(): void {
|
|
10
12
|
clearStoredTokens();
|
|
13
|
+
// 清理当前租户,避免切换账号后残留
|
|
14
|
+
clearStoredTenantCode();
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
/**
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { getFromStorage, removeStorage, setStorage } from '@/common/helpers';
|
|
2
|
+
import { STORAGE_KEYS } from '@/constants';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 当前租户 code 存储工具
|
|
6
|
+
*
|
|
7
|
+
* 多租户模式下,当前选中的租户会被持久化到 localStorage,
|
|
8
|
+
* 在调用 `fetchUserInfo` 时作为 `tenant` 参数传给后端,
|
|
9
|
+
* 以获取对应租户的 menu_perms / button_perms / region_perms。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const getStoredTenantCode = (): string | null => {
|
|
13
|
+
return getFromStorage(STORAGE_KEYS.CURRENT_TENANT);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const setStoredTenantCode = (code: string): void => {
|
|
17
|
+
if (!code) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
setStorage(STORAGE_KEYS.CURRENT_TENANT, code);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const clearStoredTenantCode = (): void => {
|
|
24
|
+
removeStorage(STORAGE_KEYS.CURRENT_TENANT);
|
|
25
|
+
};
|
|
@@ -8,19 +8,43 @@ export const UID = 'uid';
|
|
|
8
8
|
/** 微应用模式下主应用传递的环境标识 */
|
|
9
9
|
export const MICRO_ENV_KEY = 'micro_env';
|
|
10
10
|
|
|
11
|
+
export interface IAppPerms {
|
|
12
|
+
key: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ITenantItem {
|
|
17
|
+
/** 租户名 */
|
|
18
|
+
name: string;
|
|
19
|
+
/** 租户 code */
|
|
20
|
+
code: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 租户模式枚举 */
|
|
24
|
+
export enum ETenantModel {
|
|
25
|
+
Single = 1,
|
|
26
|
+
Multi = 2,
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
/**
|
|
12
|
-
* OpenAPI
|
|
30
|
+
* OpenAPI — GET /user/info/ 响应 `data`
|
|
13
31
|
*/
|
|
14
32
|
export interface IUserInfoApiData {
|
|
15
33
|
id: number;
|
|
16
34
|
avatar: string;
|
|
17
35
|
email: string;
|
|
18
36
|
name: string;
|
|
19
|
-
app_perms:
|
|
37
|
+
app_perms: IAppPerms[];
|
|
20
38
|
region_perms: string[];
|
|
21
39
|
menu_perms: string[];
|
|
22
40
|
button_perms: string[];
|
|
23
41
|
is_superuser: boolean;
|
|
42
|
+
/** 租户模式:1 单租户,2 多租户 */
|
|
43
|
+
tenant_model: number;
|
|
44
|
+
/** 用户所属租户列表 */
|
|
45
|
+
tenant: ITenantItem[];
|
|
46
|
+
/** 当前租户 code,对应 ITenantItem.code */
|
|
47
|
+
current_tenant: string;
|
|
24
48
|
}
|
|
25
49
|
|
|
26
50
|
/**
|
|
@@ -32,4 +56,6 @@ export interface IUserInfo extends IUserInfoApiData {
|
|
|
32
56
|
user_name: string;
|
|
33
57
|
/** 登录名/账号展示,可与 `name` 或 `email` 一致 */
|
|
34
58
|
username: string;
|
|
59
|
+
/** 是否多租户模式(tenant_model === 2 的语义化快捷字段) */
|
|
60
|
+
isMultiTenant: boolean;
|
|
35
61
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { i18n } from '<%= packageScope %>/common-intl';
|
|
2
|
+
import { getIntl } from '@umijs/max';
|
|
3
|
+
import { isCommonIntlEnabled } from './intlRuntime';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 非组件上下文(如 request 拦截器、工具函数)的国际化方法。
|
|
7
|
+
*
|
|
8
|
+
* 与 useLayoutIntl.formatMessage 逻辑一致:
|
|
9
|
+
* - commonIntl 未启用 → 直接走 Umi getIntl()
|
|
10
|
+
* - commonIntl 启用 → 远程 i18n() 优先,返回 defaultMessage 时回退到 Umi
|
|
11
|
+
*/
|
|
12
|
+
export function formatLayoutMessage(
|
|
13
|
+
descriptor: { id: string; defaultMessage?: string },
|
|
14
|
+
values?: Record<string, string | number | boolean | Date | null | undefined>,
|
|
15
|
+
): string {
|
|
16
|
+
const umiIntl = getIntl();
|
|
17
|
+
const dm = descriptor.defaultMessage ?? descriptor.id;
|
|
18
|
+
|
|
19
|
+
if (!isCommonIntlEnabled()) {
|
|
20
|
+
return umiIntl.formatMessage(descriptor, values);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const fromIntl = i18n({ key: descriptor.id, defaultMessage: dm });
|
|
24
|
+
const fromUmi = umiIntl.formatMessage(descriptor, values);
|
|
25
|
+
|
|
26
|
+
if (fromIntl === dm && fromUmi !== fromIntl) {
|
|
27
|
+
return fromUmi || dm;
|
|
28
|
+
}
|
|
29
|
+
return fromIntl;
|
|
30
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { intl } from '<%= packageScope %>/common-intl';
|
|
2
|
+
export { getCommonIntlConfig, isCommonIntlEnabled } from './intlRuntime';
|
|
3
|
+
export { ilangToUmiLocale, umiLocaleToILang } from './localeMapping';
|
|
4
|
+
export type { IMicoConfigCommonIntl } from './types';
|
|
5
|
+
export { useLayoutIntl } from './useLayoutIntl';
|
|
6
|
+
export { formatLayoutMessage } from './formatLayoutMessage';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { IMicoConfigCommonIntl } from './types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 是否启用多语言中台拉取:以 __MICO_CONFIG__.intl 或 commonIntl 含有效 tag、app_name 为准
|
|
5
|
+
*/
|
|
6
|
+
export function isCommonIntlEnabled(): boolean {
|
|
7
|
+
const c = getCommonIntlConfig();
|
|
8
|
+
return !!(c?.tag && c?.app_name);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getCommonIntlConfig(): IMicoConfigCommonIntl | undefined {
|
|
12
|
+
const w = window.__MICO_CONFIG__;
|
|
13
|
+
return w?.intl ?? w?.commonIntl;
|
|
14
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { LANG, type ILang } from '<%= packageScope %>/common-intl';
|
|
2
|
+
import type { SupportedLocale } from '@/common/locale';
|
|
3
|
+
import { LOCALE } from '@/common/locale';
|
|
4
|
+
|
|
5
|
+
/** 与 packages/common-intl/src/umiLocaleBridge.ts 保持一致 */
|
|
6
|
+
const UMI_TO_ILANG: Record<SupportedLocale, ILang> = {
|
|
7
|
+
[LOCALE.ZH_CN]: LANG.ZH_CN,
|
|
8
|
+
[LOCALE.EN_US]: LANG.EN,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Umi 权威 locale → common-intl 中台 / 缓存键(ILang)
|
|
13
|
+
*/
|
|
14
|
+
export function umiLocaleToILang(locale: SupportedLocale): ILang {
|
|
15
|
+
return UMI_TO_ILANG[locale] ?? LANG.EN;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ILANG_TO_UMI: Partial<Record<ILang, SupportedLocale>> = {
|
|
19
|
+
[LANG.ZH_CN]: LOCALE.ZH_CN,
|
|
20
|
+
[LANG.EN]: LOCALE.EN_US,
|
|
21
|
+
[LANG.AR]: LOCALE.EN_US,
|
|
22
|
+
[LANG.TR]: LOCALE.EN_US,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* ILang → Umi 存储用 locale(layout 仅支持 zh-CN / en-US 时,其余语系落到 en-US)
|
|
27
|
+
*/
|
|
28
|
+
export function ilangToUmiLocale(lang: ILang): SupportedLocale {
|
|
29
|
+
return ILANG_TO_UMI[lang] ?? LOCALE.EN_US;
|
|
30
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* layout 运行时 common-intl 配置(见 window.__MICO_CONFIG__.intl,兼容 commonIntl)
|
|
3
|
+
*/
|
|
4
|
+
export interface IMicoConfigCommonIntl {
|
|
5
|
+
/** 多语言中台 tag */
|
|
6
|
+
tag: string;
|
|
7
|
+
app_name: string;
|
|
8
|
+
indexedDBParams?: {
|
|
9
|
+
dbName?: string;
|
|
10
|
+
dbVersion?: number;
|
|
11
|
+
storeName?: string;
|
|
12
|
+
keyPathKey?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { i18n, intl } from '<%= packageScope %>/common-intl';
|
|
2
|
+
import { useIntl } from '@umijs/max';
|
|
3
|
+
import { getCurrentLocale } from '@/common/locale';
|
|
4
|
+
import { isCommonIntlEnabled } from './intlRuntime';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* layout 统一国际化 Hook:未配置 commonIntl 时仅 Umi;配置后 common-intl 优先,无命中时 Umi 兜底。
|
|
8
|
+
*/
|
|
9
|
+
export function useLayoutIntl() {
|
|
10
|
+
const umiIntl = useIntl();
|
|
11
|
+
const commonIntlEnabled = isCommonIntlEnabled();
|
|
12
|
+
const locale = getCurrentLocale();
|
|
13
|
+
|
|
14
|
+
const formatMessage = (
|
|
15
|
+
descriptor: { id: string; defaultMessage?: string },
|
|
16
|
+
values?: Record<string, string | number | boolean | Date | null | undefined>,
|
|
17
|
+
): string => {
|
|
18
|
+
if (!commonIntlEnabled) {
|
|
19
|
+
return umiIntl.formatMessage(descriptor, values);
|
|
20
|
+
}
|
|
21
|
+
const dm = descriptor.defaultMessage ?? descriptor.id;
|
|
22
|
+
const fromIntl = i18n({
|
|
23
|
+
key: descriptor.id,
|
|
24
|
+
defaultMessage: dm,
|
|
25
|
+
});
|
|
26
|
+
const fromUmi = umiIntl.formatMessage(descriptor, values);
|
|
27
|
+
if (fromIntl === dm && fromUmi !== fromIntl) {
|
|
28
|
+
return fromUmi;
|
|
29
|
+
}
|
|
30
|
+
return fromIntl;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
commonIntlEnabled,
|
|
35
|
+
locale,
|
|
36
|
+
formatMessage,
|
|
37
|
+
/** 仅 common-intl 启用时有值 */
|
|
38
|
+
intl: commonIntlEnabled ? intl : undefined,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -1,38 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 日志工具
|
|
3
|
-
*
|
|
3
|
+
* - 开发环境:全量输出
|
|
4
|
+
* - 生产环境:默认仅 `error` 带前缀输出;可在**首屏 URL** 带 query 打开全量 `log` / `info` / `warn`(与 dev 一致)
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
+
* 生产开启方式:首次进入页面时地址栏带 `?debug=1`(或 `debug=true` / `yes`),
|
|
7
|
+
* 在**本 JS 模块加载时刻**解析一次并固定,后续路由或 query 变更**不会**再切换日志级别(需整页刷新带参 URL)。
|
|
8
|
+
*
|
|
9
|
+
* **安全与合规**:`debug` 仅用于临时排障;全量日志可能在控制台暴露业务或个人信息,**勿长期公开或转发**带 `debug` 的生产链接;若接入日志采集需自行脱敏。
|
|
10
|
+
*
|
|
11
|
+
* 使用 bind 保持原始调用位置,避免错误栈指向 logger 内部(在开启详细日志时)
|
|
6
12
|
*/
|
|
7
13
|
|
|
8
14
|
const isDev = process.env.NODE_ENV === 'development';
|
|
9
15
|
|
|
10
|
-
|
|
11
|
-
const
|
|
16
|
+
/** Query 参数名;勿随意改名以免已分享的排障链接失效 */
|
|
17
|
+
export const DEBUG_LOGS_QUERY_KEY = 'debug';
|
|
12
18
|
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
/** 模块初始化时读取一次 `location.search`,之后不变 */
|
|
20
|
+
function readVerboseDebugQueryOnce(): boolean {
|
|
21
|
+
if (typeof window === 'undefined') {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const v = new URLSearchParams(window.location.search).get(DEBUG_LOGS_QUERY_KEY);
|
|
26
|
+
if (v == null || v === '') {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const normalized = v.toLowerCase();
|
|
30
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes';
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
26
33
|
}
|
|
34
|
+
}
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
/** 开发环境,或生产环境首屏 URL 已带 `debug` 开关(模块加载时确定);供业务侧按需分支 */
|
|
37
|
+
export const isLayoutDebugLogsEnabled: boolean = isDev || readVerboseDebugQueryOnce();
|
|
38
|
+
|
|
39
|
+
const noop = (): void => {};
|
|
29
40
|
|
|
41
|
+
function createVerboseHandlers(formattedPrefix: string) {
|
|
30
42
|
return {
|
|
31
43
|
log: console.log.bind(console, formattedPrefix),
|
|
32
44
|
info: console.info.bind(console, formattedPrefix),
|
|
33
45
|
warn: console.warn.bind(console, formattedPrefix),
|
|
34
46
|
error: console.error.bind(console, formattedPrefix),
|
|
35
47
|
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createQuietHandlers(formattedPrefix: string) {
|
|
51
|
+
return {
|
|
52
|
+
log: noop,
|
|
53
|
+
info: noop,
|
|
54
|
+
warn: noop,
|
|
55
|
+
error: console.error.bind(console, formattedPrefix),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 创建带前缀的日志函数
|
|
61
|
+
* 使用 bind 绑定前缀,保持控制台显示正确的调用位置(在开启详细日志时)
|
|
62
|
+
*/
|
|
63
|
+
const createLogger = (prefix: string) => {
|
|
64
|
+
const formattedPrefix = `[${prefix}]`;
|
|
65
|
+
return isLayoutDebugLogsEnabled
|
|
66
|
+
? createVerboseHandlers(formattedPrefix)
|
|
67
|
+
: createQuietHandlers(formattedPrefix);
|
|
36
68
|
};
|
|
37
69
|
|
|
38
70
|
// 预定义的日志实例
|