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.
Files changed (96) hide show
  1. package/README.md +10 -7
  2. package/generators/micro-react/README.md +34 -0
  3. package/generators/micro-react/index.js +19 -1
  4. package/generators/micro-react/templates/CICD/start_dev.sh +11 -0
  5. package/generators/micro-react/templates/CICD/start_local.sh +9 -0
  6. package/generators/micro-react/templates/CICD/start_prod.sh +13 -0
  7. package/generators/micro-react/templates/CICD/start_test.sh +11 -0
  8. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +29 -3
  9. package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +10 -0
  10. package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +10 -0
  11. package/generators/micro-react/templates/apps/layout/config/config.prod.ts +12 -0
  12. package/generators/micro-react/templates/apps/layout/config/config.ts +0 -15
  13. 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
  14. 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
  15. package/generators/micro-react/templates/apps/layout/docs/feature-/345/233/275/351/231/205/345/214/226.md +121 -0
  16. 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
  17. 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
  18. 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
  19. package/generators/micro-react/templates/apps/layout/mock/menus.ts +14 -0
  20. package/generators/micro-react/templates/apps/layout/mock/pages.ts +22 -2
  21. package/generators/micro-react/templates/apps/layout/package.json +3 -2
  22. package/generators/micro-react/templates/apps/layout/src/app.tsx +68 -9
  23. package/generators/micro-react/templates/apps/layout/src/common/auth/auth-check-path.ts +14 -0
  24. package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +4 -0
  25. package/generators/micro-react/templates/apps/layout/src/common/auth/tenant.ts +25 -0
  26. package/generators/micro-react/templates/apps/layout/src/common/auth/type.ts +28 -2
  27. package/generators/micro-react/templates/apps/layout/src/common/intl/formatLayoutMessage.ts +30 -0
  28. package/generators/micro-react/templates/apps/layout/src/common/intl/index.ts +6 -0
  29. package/generators/micro-react/templates/apps/layout/src/common/intl/intlRuntime.ts +14 -0
  30. package/generators/micro-react/templates/apps/layout/src/common/intl/localeMapping.ts +30 -0
  31. package/generators/micro-react/templates/apps/layout/src/common/intl/types.ts +14 -0
  32. package/generators/micro-react/templates/apps/layout/src/common/intl/useLayoutIntl.ts +40 -0
  33. package/generators/micro-react/templates/apps/layout/src/common/logger.ts +50 -18
  34. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +27 -0
  35. package/generators/micro-react/templates/apps/layout/src/common/micro/types.ts +23 -0
  36. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +82 -20
  37. package/generators/micro-react/templates/apps/layout/src/common/request/token-refresh.ts +2 -0
  38. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +28 -6
  39. package/generators/micro-react/templates/apps/layout/src/components/RightContent/TenantDropdown.tsx +76 -0
  40. package/generators/micro-react/templates/apps/layout/src/components/RightContent/index.ts +1 -0
  41. package/generators/micro-react/templates/apps/layout/src/components/RightContent/tenant-dropdown.less +48 -0
  42. package/generators/micro-react/templates/apps/layout/src/constants/index.ts +1 -0
  43. package/generators/micro-react/templates/apps/layout/src/hooks/index.ts +1 -0
  44. package/generators/micro-react/templates/apps/layout/src/hooks/useMenuState.ts +18 -0
  45. package/generators/micro-react/templates/apps/layout/src/hooks/useTenant.ts +41 -0
  46. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +4 -1
  47. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +18 -6
  48. package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +11 -0
  49. package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +10 -0
  50. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +27 -0
  51. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +108 -12
  52. package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +2 -1
  53. package/generators/micro-react/templates/apps/layout/src/services/user.ts +53 -2
  54. package/generators/micro-react/templates/apps/layout/typings.d.ts +16 -0
  55. package/generators/micro-react/templates/docs/package-shared.md +189 -0
  56. package/generators/micro-react/templates/package.json +1 -1
  57. package/generators/micro-react/templates/packages/common-intl/README.md +3 -1
  58. package/generators/micro-react/templates/packages/common-intl/package.json +1 -1
  59. package/generators/micro-react/templates/packages/common-intl/src/index.ts +4 -2
  60. package/generators/micro-react/templates/packages/common-intl/src/intl.ts +104 -8
  61. package/generators/micro-react/templates/packages/common-intl/src/umiLocaleBridge.ts +101 -0
  62. package/generators/micro-react/templates/packages/shared/README.md +120 -0
  63. package/generators/micro-react/templates/packages/shared/package.json +26 -0
  64. package/generators/micro-react/templates/packages/shared/services/common/index.ts +43 -0
  65. package/generators/micro-react/templates/packages/shared/services/index.ts +21 -0
  66. package/generators/micro-react/templates/packages/shared/services/request.ts +43 -0
  67. package/generators/micro-react/templates/packages/shared/timezone/index.ts +228 -0
  68. package/generators/micro-react/templates/packages/shared/tsconfig.json +20 -0
  69. package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +6 -1
  70. package/generators/micro-react/templates/turbo.json +9 -1
  71. package/generators/subapp-react/README.md +43 -0
  72. package/generators/subapp-react/index.js +2 -0
  73. package/generators/subapp-react/templates/homepage/README.md +5 -1
  74. package/generators/subapp-react/templates/homepage/config/config.dev.ts +20 -0
  75. package/generators/subapp-react/templates/homepage/config/config.ts +10 -15
  76. package/generators/subapp-react/templates/homepage/docs/feature-/345/233/275/351/231/205/345/214/226.md +124 -0
  77. package/generators/subapp-react/templates/homepage/package.json +2 -1
  78. package/generators/subapp-react/templates/homepage/src/app.tsx +100 -5
  79. package/generators/subapp-react/templates/homepage/src/common/intl/index.ts +15 -0
  80. package/generators/subapp-react/templates/homepage/src/common/intl/intlRuntime.ts +14 -0
  81. package/generators/subapp-react/templates/homepage/src/common/intl/localeMapping.ts +24 -0
  82. package/generators/subapp-react/templates/homepage/src/common/intl/subappIntlConfig.ts +28 -0
  83. package/generators/subapp-react/templates/homepage/src/common/intl/subappLocale.ts +18 -0
  84. package/generators/subapp-react/templates/homepage/src/common/intl/subappOwnIntl.ts +63 -0
  85. package/generators/subapp-react/templates/homepage/src/common/intl/types.ts +14 -0
  86. package/generators/subapp-react/templates/homepage/src/common/intl/useSubappIntl.ts +61 -0
  87. package/generators/subapp-react/templates/homepage/src/common/locale.ts +80 -0
  88. package/generators/subapp-react/templates/homepage/src/common/logger.ts +50 -18
  89. package/generators/subapp-react/templates/homepage/src/common/mainApp.ts +2 -0
  90. package/generators/subapp-react/templates/homepage/src/locales/en-US.ts +6 -0
  91. package/generators/subapp-react/templates/homepage/src/locales/zh-CN.ts +6 -0
  92. package/generators/subapp-react/templates/homepage/src/pages/index.less +10 -0
  93. package/generators/subapp-react/templates/homepage/src/pages/index.tsx +51 -0
  94. package/generators/subapp-react/templates/homepage/typings.d.ts +12 -0
  95. package/generators/subapp-umd/README.md +37 -0
  96. 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`(`NODE_ENV === 'development'` `console.log`)
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
- | 生产环境 | 静默 | `layoutLogger` 在 `production` 下不输出 |
145
+ | 生产环境 | 默认静默 `log` | `layoutLogger` 在 production`log`/`info`/`warn` noop;首屏 `?debug=1` 等同开发 |
146
146
  | 无 `currentRoute` 不打日志 | 跳过 | 静态路由访问频繁,避免噪音 |
147
147
  | `menuFilter` 与超管/关权限 | 不打 | 无过滤效果时无隐藏项可记 |
148
148
 
149
149
  ## 已知限制与待改进
150
150
 
151
- - 日志仅在**开发环境**可见;线上问题需依赖 Sentry 或其它监控,不在本功能内。
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.4/login/index.html',
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.4/permission/index.html',
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
- /** @see https://umijs.org/docs/api/runtime-config#render */
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: getIntlLocale() as ILang,
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
- const noAuthRoute = isNoAuthRoute(location.pathname);
161
- const skipAuth = noAuthRoute || isPageAuthFree(location.pathname);
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
- // token 就获取用户信息(登录后 refresh、免认证页同样需要 currentUser:
169
- // PermissionFilter / MicroAppLoader 注入子应用的 button_perms)
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 8 — GET /user/info/ 响应 `data`(与 Apifox 导出 8 一致)
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: string[];
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
- * 使用 bind 保持原始调用位置,避免错误栈指向 logger 内部
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 noop = (): void => {};
16
+ /** Query 参数名;勿随意改名以免已分享的排障链接失效 */
17
+ export const DEBUG_LOGS_QUERY_KEY = 'debug';
12
18
 
13
- /**
14
- * 创建带前缀的日志函数
15
- * 使用 bind 绑定前缀,保持控制台显示正确的调用位置
16
- */
17
- const createLogger = (prefix: string) => {
18
- if (!isDev) {
19
- // 生产环境返回空操作
20
- return {
21
- log: noop,
22
- info: noop,
23
- warn: noop,
24
- error: noop,
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
- const formattedPrefix = `[${prefix}]`;
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
  // 预定义的日志实例