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
package/README.md CHANGED
@@ -6,13 +6,14 @@
6
6
 
7
7
  | 文档 | 说明 |
8
8
  |---|---|
9
- | [docs/mico-cli.md](docs/mico-cli.md) | Mico CLI 功能概述与技术方案 |
10
- | [docs/micro-react-generator.md](docs/micro-react-generator.md) | Monorepo 项目生成器说明 |
11
- | [docs/subapp-react-generator.md](docs/subapp-react-generator.md) | React 子应用生成器说明 |
12
- | [docs/subapp-umd-generator.md](docs/subapp-umd-generator.md) | UMD 组件包生成器说明 |
13
- | [docs/feat-integration-tests.md](docs/feat-integration-tests.md) | 集成测试方案(Vitest + yeoman-test) |
14
- | [docs/fix-command-injection.md](docs/fix-command-injection.md) | 修复 npm version 查询命令注入风险 |
15
- | [docs/refactor-code-quality-optimizations.md](docs/refactor-code-quality-optimizations.md) | 代码质量优化(DRY / 错误处理 / 确定性 ID / 可配置化 / 冗余清理) |
9
+ | [docs/architecture.md](docs/architecture.md) | 项目架构、模块职责、关键约定、依赖关系 |
10
+ | [docs/generators.md](docs/generators.md) | 三个生成器的 prompt / 模板变量 / 联动修改细节 |
11
+ | [docs/configuration.md](docs/configuration.md) | `.micorc` 配置字段与加载顺序 |
12
+ | [docs/testing.md](docs/testing.md) | Vitest + yeoman-test 测试体系与 Mock 策略 |
13
+ | [docs/scripts.md](docs/scripts.md) | `sync:subapp-react-template` `verify:micro-react` |
14
+ | [generators/micro-react/README.md](generators/micro-react/README.md) | `micro-react` 生成器就近说明 |
15
+ | [generators/subapp-react/README.md](generators/subapp-react/README.md) | `subapp-react` 生成器就近说明 |
16
+ | [generators/subapp-umd/README.md](generators/subapp-umd/README.md) | `subapp-umd` 生成器就近说明 |
16
17
 
17
18
  ## 要求
18
19
 
@@ -69,6 +70,7 @@ mico doctor
69
70
  ```json
70
71
  {
71
72
  "projectName": "my-app",
73
+ "appId": "portal_dev",
72
74
  "packageScope": "@my-company",
73
75
  "cdnPrefix": "portal",
74
76
  "author": "Team <team@example.com>",
@@ -82,6 +84,7 @@ mico doctor
82
84
  | 字段 | 类型 | 适用生成器 | 说明 | 默认值 |
83
85
  |------|------|-----------|------|--------|
84
86
  | `projectName` | string | micro-react | 项目名称 | 当前目录名 |
87
+ | `appId` | string | micro-react | 开发环境 `window.__MICO_CONFIG__.appId`(请求代理等) | `portal_dev` |
85
88
  | `packageScope` | string | 所有 | 包作用域(如 `@my-company`) | micro-react: `@<projectName>`;其他: 从根 `package.json` 检测 |
86
89
  | `cdnPrefix` | string | micro-react | CDN 路径前缀(如 `portal`、`admin/v2`) | 空字符串 |
87
90
  | `author` | string | micro-react | 作者信息 | `Your Name <email@example.com>` |
@@ -0,0 +1,34 @@
1
+ # micro-react 生成器
2
+
3
+ 创建完整的 qiankun 微前端 Monorepo。详细 prompt、模板变量、后置动作请见 [docs/generators.md#micro-react](../../docs/generators.md#micro-react)。
4
+
5
+ ## 快速索引
6
+
7
+ | 文件 | 职责 |
8
+ |---|---|
9
+ | `index.js` | Yeoman 生成器类(`initializing` / `prompting` / `writing` / `install` / `end`) |
10
+ | `meta.json` | `mico list` 展示的名称、描述、features |
11
+ | `ignore-list.json` | `collectFiles` 按路径片段过滤的清单 |
12
+ | `templates/` | 模板根(本仓库文档审计不覆盖此目录) |
13
+
14
+ ## 使用
15
+
16
+ ```bash
17
+ mkdir my-project && cd my-project
18
+ mico create micro-react
19
+ # 可选:--dry-run / --verbose / --force
20
+ ```
21
+
22
+ 要求当前目录**不**是已有 monorepo(没有 `pnpm-workspace.yaml`);`--force` 可绕过此检查。
23
+
24
+ ## 模板变量速查
25
+
26
+ | 变量 | 类型 | 来源 |
27
+ |---|---|---|
28
+ | `projectName` / `ProjectName` | string | prompt,`toKebab` / `toPascal` |
29
+ | `appId` | string | prompt |
30
+ | `packageScope` | string | prompt |
31
+ | `cdnPrefix` / `cdnPrefixPath` | string | prompt;`cdnPrefixPath` 为空或带尾斜杠 |
32
+ | `author` | string | prompt |
33
+ | `intlTag` | string | prompt |
34
+ | `micoUiVersion` / `themeVersion` | string | `npm view @mico-platform/{ui,theme} version`,前缀 `^`,失败回退 `^1.0.0` |
@@ -76,6 +76,18 @@ module.exports = class extends Generator {
76
76
  return true;
77
77
  },
78
78
  },
79
+ {
80
+ type: 'input',
81
+ name: 'appId',
82
+ message:
83
+ 'App ID(开发环境 window.__MICO_CONFIG__.appId,用于请求代理 / 网关等)',
84
+ default: rc.appId || 'portal_dev',
85
+ validate: (input) => {
86
+ const v = String(input ?? '').trim();
87
+ if (!v) return 'App ID is required';
88
+ return true;
89
+ },
90
+ },
79
91
  {
80
92
  type: 'input',
81
93
  name: 'packageScope',
@@ -91,7 +103,7 @@ module.exports = class extends Generator {
91
103
  {
92
104
  type: 'input',
93
105
  name: 'cdnPrefix',
94
- message: `CDN path prefix (用于区分不同业务线的 CDN 目录)
106
+ message: `CDN path prefix (用于区分不同业务线的 CDN 目录,一般是运营配置的group名,切记不可乱填)
95
107
  示例:CDN 完整路径 = https://cdn.example.com/<prefix>/<projectName>/<version>/
96
108
  - 留空: https://cdn.example.com/my-project/1.0.0/
97
109
  - 输入 "portal": https://cdn.example.com/portal/my-project/1.0.0/
@@ -119,6 +131,7 @@ module.exports = class extends Generator {
119
131
 
120
132
  this.projectName = toKebab(this.answers.projectName);
121
133
  this.ProjectName = toPascal(this.projectName);
134
+ this.appId = String(this.answers.appId ?? '').trim();
122
135
  this.packageScope = this.answers.packageScope;
123
136
  this.cdnPrefix = this.answers.cdnPrefix;
124
137
  this.author = this.answers.author;
@@ -173,6 +186,9 @@ module.exports = class extends Generator {
173
186
  const templateData = {
174
187
  projectName: this.projectName,
175
188
  ProjectName: this.ProjectName,
189
+ // 用于 MFSU mfName 等需要合法 JS 标识符的场景(不允许 `-`)
190
+ projectNameSnake: this.projectName.replace(/-/g, '_'),
191
+ appId: this.appId,
176
192
  packageScope: this.packageScope,
177
193
  author: this.author,
178
194
  micoUiVersion: `^${micoUiVer}`,
@@ -204,6 +220,7 @@ module.exports = class extends Generator {
204
220
  this.log('\x1b[33m📋 Dry run mode - no files will be created\x1b[0m');
205
221
  this.log('');
206
222
  this.log(` Project: ${this.projectName}`);
223
+ this.log(` App ID: ${this.appId}`);
207
224
  this.log(` Scope: ${this.packageScope}`);
208
225
  this.log(` Destination: ${this.destDir}`);
209
226
  this.log('');
@@ -239,6 +256,7 @@ module.exports = class extends Generator {
239
256
 
240
257
  this.log('');
241
258
  this.log(`📦 Creating project: ${this.projectName}`);
259
+ this.log(` App ID: ${this.appId}`);
242
260
  this.log(` Scope: ${this.packageScope}`);
243
261
  this.log('');
244
262
 
@@ -14,6 +14,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
14
14
 
15
15
  # 用 node 命令读取 package.json 里的 version 字段
16
16
  VERSION=$(node -p "require('$PROJECT_ROOT/package.json').version")
17
+ export VERSION
17
18
  # 输出项目版本号
18
19
  echo "项目版本: $VERSION"
19
20
 
@@ -23,6 +24,16 @@ cd "$PROJECT_ROOT"
23
24
  # 导出 PROJECT_ROOT 供 before_build.sh 使用
24
25
  export PROJECT_ROOT
25
26
 
27
+ # Jenkins 等 CI 注入的构建号(供 Sentry release、layout window.__MICO_BUILD__ 等)
28
+ export BUILD_NUMBER="${BUILD_NUMBER:-}"
29
+ echo "BUILD_NUMBER: ${BUILD_NUMBER:-<unset>}"
30
+
31
+ export GIT_COMMIT="${GIT_COMMIT:-$(git rev-parse HEAD 2>/dev/null || echo '')}"
32
+ export BRANCH_OR_TAG="${BRANCH_OR_TAG:-${GIT_BRANCH:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')}}"
33
+ export BUILD_TIME="${BUILD_TIME:-$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")}"
34
+ echo "GIT_COMMIT: ${GIT_COMMIT:-<empty>}"
35
+ echo "BRANCH_OR_TAG: ${BRANCH_OR_TAG:-<empty>}"
36
+
26
37
  # 在执行构建前,计算 BASE_REF / TURBO_FILTER 等增量构建信息
27
38
  if [ -f "$PROJECT_ROOT/CICD/before_build.sh" ]; then
28
39
  # shellcheck disable=SC1090
@@ -14,12 +14,21 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
14
14
 
15
15
  # 用 node 命令读取 package.json 里的 version 字段
16
16
  VERSION=$(node -p "require('$PROJECT_ROOT/package.json').version")
17
+ export VERSION
17
18
  # 输出项目版本号
18
19
  echo "项目版本: $VERSION"
19
20
 
20
21
  # 切换到项目根目录,确保后续命令在根目录下运行
21
22
  cd "$PROJECT_ROOT"
22
23
 
24
+ # Jenkins 等 CI 注入的构建号(供 Sentry release、layout window.__MICO_BUILD__ 等)
25
+ export BUILD_NUMBER="${BUILD_NUMBER:-}"
26
+ echo "BUILD_NUMBER: ${BUILD_NUMBER:-<unset>}"
27
+
28
+ export GIT_COMMIT="${GIT_COMMIT:-$(git rev-parse HEAD 2>/dev/null || echo '')}"
29
+ export BRANCH_OR_TAG="${BRANCH_OR_TAG:-${GIT_BRANCH:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')}}"
30
+ export BUILD_TIME="${BUILD_TIME:-$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")}"
31
+
23
32
  # 设置子应用的 CDN 公共路径(本地环境使用相对路径)
24
33
  export CDN_PUBLIC_PATH="./"
25
34
  echo "CDN_PUBLIC_PATH: $CDN_PUBLIC_PATH"
@@ -14,6 +14,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
14
14
 
15
15
  # 用 node 命令读取 package.json 里的 version 字段
16
16
  VERSION=$(node -p "require('$PROJECT_ROOT/package.json').version")
17
+ export VERSION
17
18
  # 输出项目版本号
18
19
  echo "项目版本: $VERSION"
19
20
 
@@ -23,6 +24,18 @@ cd "$PROJECT_ROOT"
23
24
  # 导出 PROJECT_ROOT 供 before_build.sh 使用
24
25
  export PROJECT_ROOT
25
26
 
27
+ # Jenkins 等 CI 注入的构建号(供 Sentry release、layout window.__MICO_BUILD__ 等)
28
+ export BUILD_NUMBER="${BUILD_NUMBER:-}"
29
+ echo "BUILD_NUMBER: ${BUILD_NUMBER:-<unset>}"
30
+
31
+ # 供 Umi define 注入 window.__MICO_BUILD__(与 package.json 一致;未导出时子进程 process.env.VERSION 为空)
32
+ # Jenkins 常注入 GIT_BRANCH / GIT_COMMIT,BRANCH_OR_TAG 为业务约定名时可由 Job 覆盖
33
+ export GIT_COMMIT="${GIT_COMMIT:-$(git rev-parse HEAD 2>/dev/null || echo '')}"
34
+ export BRANCH_OR_TAG="${BRANCH_OR_TAG:-${GIT_BRANCH:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')}}"
35
+ export BUILD_TIME="${BUILD_TIME:-$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")}"
36
+ echo "GIT_COMMIT: ${GIT_COMMIT:-<empty>}"
37
+ echo "BRANCH_OR_TAG: ${BRANCH_OR_TAG:-<empty>}"
38
+
26
39
  # 在执行构建前,计算 BASE_REF / TURBO_FILTER 等增量构建信息(用于增量构建)
27
40
  if [ -f "$PROJECT_ROOT/CICD/before_build.sh" ]; then
28
41
  # shellcheck disable=SC1090
@@ -14,6 +14,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
14
14
 
15
15
  # 用 node 命令读取 package.json 里的 version 字段
16
16
  VERSION=$(node -p "require('$PROJECT_ROOT/package.json').version")
17
+ export VERSION
17
18
  # 输出项目版本号
18
19
  echo "项目版本: $VERSION"
19
20
 
@@ -23,6 +24,16 @@ cd "$PROJECT_ROOT"
23
24
  # 导出 PROJECT_ROOT 供 before_build.sh 使用
24
25
  export PROJECT_ROOT
25
26
 
27
+ # Jenkins 等 CI 注入的构建号(供 Sentry release、layout window.__MICO_BUILD__ 等)
28
+ export BUILD_NUMBER="${BUILD_NUMBER:-}"
29
+ echo "BUILD_NUMBER: ${BUILD_NUMBER:-<unset>}"
30
+
31
+ export GIT_COMMIT="${GIT_COMMIT:-$(git rev-parse HEAD 2>/dev/null || echo '')}"
32
+ export BRANCH_OR_TAG="${BRANCH_OR_TAG:-${GIT_BRANCH:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '')}}"
33
+ export BUILD_TIME="${BUILD_TIME:-$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")}"
34
+ echo "GIT_COMMIT: ${GIT_COMMIT:-<empty>}"
35
+ echo "BRANCH_OR_TAG: ${BRANCH_OR_TAG:-<empty>}"
36
+
26
37
  # 在执行构建前,计算 BASE_REF / TURBO_FILTER 等增量构建信息
27
38
  if [ -f "$PROJECT_ROOT/CICD/before_build.sh" ]; then
28
39
  # shellcheck disable=SC1090
@@ -19,7 +19,7 @@ const config: ReturnType<typeof defineConfig> = {
19
19
  window.__MICO_MENUS__ = ${JSON.stringify(mockMenus)};
20
20
  window.__MICO_PAGES__ = ${JSON.stringify(mockPages)};
21
21
  window.__MICO_CONFIG__ = {
22
- appId: '<%= projectName %>',
22
+ appId: '<%= appId %>',
23
23
  appName: '<%= projectName %>',
24
24
  title: '测试样例',
25
25
  logo: '',
@@ -34,8 +34,14 @@ const config: ReturnType<typeof defineConfig> = {
34
34
  // 关闭权限控制(调试用)
35
35
  disableAuth: false,
36
36
  // SSO 外跳地址(与 resolveExternalLoginPath 读取的 externalLoginPath 一致;生产由注入的 __MICO_CONFIG__ 提供)
37
- externalLoginPath:
38
- 'https://micous-idp.cig.tencentcs.com/sso/xxxxxxx/cas',
37
+ externalLoginPath: 'https://micous-idp.cig.tencentcs.com/sso/tn-456d1d3feb5f4e09ad28ab35ee4d2e66/ai-4919cf3d4883490b956b90376cfb86e7/cas',
38
+ // 多语言中台(可选):intl 与 common-intl 包 initIntl 合并;见 packages/common-intl/README.md
39
+ // 这里只是一个例子,需要根据业务在翻译中台的配置来替换
40
+ // intl: {
41
+ // tag: 'cs_fe_pc',
42
+ // app_name: 'middle',
43
+ // indexedDBParams: { dbName: 'mico_cs_pc_i18n_db' },
44
+ // },
39
45
  };
40
46
  `,
41
47
  },
@@ -74,6 +80,26 @@ const config: ReturnType<typeof defineConfig> = {
74
80
  'process.env.LOCALE_REQUEST_URL':
75
81
  'https://api-test.micoplatform.com/lang_server/pull',
76
82
  },
83
+
84
+ /**
85
+ * @name MFSU 配置
86
+ * @description
87
+ * - mfName: 唯一名隔离微前端 runtime,避免多应用共存时 MFSU 容器冲突
88
+ * - exclude:
89
+ * - `@mico-platform/theme`:theme 子路径解析
90
+ * - `@mico-platform/ui`:尽量与主应用一起编译
91
+ * - `<%= packageScope %>/*`:workspace 包走 webpack 编译,开发态可在 DevTools 直接定位到 packages/* 源码
92
+ * - shared: React 单例,确保 MFSU 预打包里的组件(如 Layout)与主应用共用同一份 React,否则 useContext 报 null
93
+ * @doc https://umijs.org/docs/api/config#mfsu
94
+ */
95
+ mfsu: {
96
+ mfName: 'mf_<%= projectNameSnake %>_layout',
97
+ exclude: ['@mico-platform/theme', '@mico-platform/ui', /^<%= packageScope %>\//],
98
+ shared: {
99
+ react: { singleton: true },
100
+ 'react-dom': { singleton: true },
101
+ },
102
+ },
77
103
  };
78
104
 
79
105
  export default defineConfig(config);
@@ -1,8 +1,13 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
+ import { readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
4
6
  // 开发环境,不上传 sourcemap。调试时如有需要再解开
5
7
  // import { applySentryPlugin } from "../../../scripts/apply-sentry-plugin";
8
+ const packageVersion: string = JSON.parse(
9
+ readFileSync(join(__dirname, '../../../package.json'), 'utf-8'),
10
+ ).version;
6
11
  const { CDN_PUBLIC_PATH } = process.env;
7
12
 
8
13
  const PUBLIC_PATH: string = CDN_PUBLIC_PATH
@@ -33,6 +38,11 @@ const config: ReturnType<typeof defineConfig> = {
33
38
  'https://dashboard-api-test.micoplatform.com/api/yufu_login/',
34
39
  'process.env.REFRESH_ENDPOINT':
35
40
  'https://dashboard-api-test.micoplatform.com/api/yufu_login/refresh/',
41
+ 'process.env.BUILD_NUMBER': process.env.BUILD_NUMBER ?? '',
42
+ 'process.env.BRANCH_OR_TAG': process.env.BRANCH_OR_TAG ?? '',
43
+ 'process.env.GIT_COMMIT': process.env.GIT_COMMIT ?? '',
44
+ 'process.env.VERSION': process.env.VERSION ?? packageVersion,
45
+ 'process.env.BUILD_TIME': process.env.BUILD_TIME ?? '',
36
46
  'process.env.LOCALE_REQUEST_URL':
37
47
  'https://api-test.micoplatform.com/lang_server/pull',
38
48
  },
@@ -1,8 +1,13 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
+ import { readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
4
6
  // 测试环境,不上传 sourcemap。调试时如有需要再解开
5
7
  // import { applySentryPlugin } from "../../../scripts/apply-sentry-plugin";
8
+ const packageVersion: string = JSON.parse(
9
+ readFileSync(join(__dirname, '../../../package.json'), 'utf-8'),
10
+ ).version;
6
11
  const { CDN_PUBLIC_PATH } = process.env;
7
12
 
8
13
  const PUBLIC_PATH: string = CDN_PUBLIC_PATH
@@ -33,6 +38,11 @@ const config: ReturnType<typeof defineConfig> = {
33
38
  'https://dashboard-api-test.micoplatform.com/api/yufu_login/',
34
39
  'process.env.REFRESH_ENDPOINT':
35
40
  'https://dashboard-api-test.micoplatform.com/api/yufu_login/refresh/',
41
+ 'process.env.BUILD_NUMBER': process.env.BUILD_NUMBER ?? '',
42
+ 'process.env.BRANCH_OR_TAG': process.env.BRANCH_OR_TAG ?? '',
43
+ 'process.env.GIT_COMMIT': process.env.GIT_COMMIT ?? '',
44
+ 'process.env.VERSION': process.env.VERSION ?? packageVersion,
45
+ 'process.env.BUILD_TIME': process.env.BUILD_TIME ?? '',
36
46
  'process.env.LOCALE_REQUEST_URL':
37
47
  'https://api-test.micoplatform.com/lang_server/pull',
38
48
  },
@@ -1,7 +1,14 @@
1
1
  // https://umijs.org/config/
2
2
 
3
3
  import { defineConfig } from '@umijs/max';
4
+ import { readFileSync } from 'node:fs';
5
+ import { join } from 'node:path';
4
6
  import { applySentryPlugin } from '../../../scripts/apply-sentry-plugin';
7
+
8
+ const packageVersion: string = JSON.parse(
9
+ readFileSync(join(__dirname, '../../../package.json'), 'utf-8'),
10
+ ).version;
11
+
5
12
  const { CDN_PUBLIC_PATH } = process.env;
6
13
 
7
14
  const PUBLIC_PATH: string = CDN_PUBLIC_PATH
@@ -21,6 +28,11 @@ const config: ReturnType<typeof defineConfig> = {
21
28
  'https://dashboard-api.micoplatform.com/api/yufu_login/',
22
29
  'process.env.REFRESH_ENDPOINT':
23
30
  'https://dashboard-api.micoplatform.com/api/yufu_login/refresh/',
31
+ 'process.env.BUILD_NUMBER': process.env.BUILD_NUMBER ?? '',
32
+ 'process.env.BRANCH_OR_TAG': process.env.BRANCH_OR_TAG ?? '',
33
+ 'process.env.GIT_COMMIT': process.env.GIT_COMMIT ?? '',
34
+ 'process.env.VERSION': process.env.VERSION ?? packageVersion,
35
+ 'process.env.BUILD_TIME': process.env.BUILD_TIME ?? '',
24
36
  'process.env.LOCALE_REQUEST_URL':
25
37
  'https://api.micoplatform.com/lang_server/pull',
26
38
  },
@@ -124,21 +124,6 @@ const config: ReturnType<typeof defineConfig> = {
124
124
  */
125
125
  extraBabelPresets: ['@mico-platform/ui/babel-preset'],
126
126
 
127
- /**
128
- * @name MFSU 配置
129
- * @description
130
- * - exclude: theme 子路径解析;ui 尽量与主应用一起编译
131
- * - shared: React 单例,确保 MFSU 预打包里的组件(如 Layout)与主应用共用同一份 React,否则 useContext 报 null
132
- * @doc https://umijs.org/docs/guides/mfsu
133
- */
134
- mfsu: {
135
- exclude: ['@mico-platform/theme', '@mico-platform/ui'],
136
- shared: {
137
- react: { singleton: true },
138
- 'react-dom': { singleton: true },
139
- },
140
- },
141
-
142
127
  /**
143
128
  * @name qiankun 微前端配置
144
129
  * @description 作为主应用,动态加载子应用
@@ -4,7 +4,15 @@
4
4
 
5
5
  ## Logger 工具
6
6
 
7
- 生产环境静默的日志工具,使用 `bind` 保持正确的调用栈位置。
7
+ 默认:**开发环境**全量输出;**生产环境**仅 `log` / `info` / `warn` 静默,`error` 仍带前缀输出。使用 `bind` 保持正确的调用栈位置。
8
+
9
+ ### 生产临时全量日志(首屏 query)
10
+
11
+ 首屏 URL 带 **`?debug=1`**(或 `debug=true` / `yes`,大小写不敏感)时,在**本模块首次加载**时解析一次,之后 `log` / `info` / `warn` 与开发环境行为一致;**不**随 SPA 内路由或 query 变更而动态开关(需整页刷新并带上参数)。
12
+
13
+ 常量 **`DEBUG_LOGS_QUERY_KEY`**(当前为 `'debug'`)与 **`isLayoutDebugLogsEnabled`**(布尔)可从 `@/common/logger` 引用。
14
+
15
+ **安全与合规**:仅用于临时排障;控制台可能含业务或个人信息,**勿长期公开或转发**带 `debug` 的生产链接;接入日志采集时需自行脱敏。
8
16
 
9
17
  ### 使用
10
18
 
@@ -18,12 +26,12 @@ layoutLogger.error('error'); // 保持原始调用栈位置
18
26
  ### 实现原理
19
27
 
20
28
  ```typescript
21
- // 使用 bind 而非 wrapper,保持控制台显示正确的调用位置
22
- const createLogger = (prefix: string) => ({
23
- log: console.log.bind(console, `[${prefix}]`),
24
- error: console.error.bind(console, `[${prefix}]`),
25
- // ...
26
- });
29
+ // 使用 bind 而非 wrapper,保持控制台显示实际调用位置
30
+ // 生产且未开 debug:log/info/warn noop,error bind
31
+ const createLogger = (prefix: string) =>
32
+ isLayoutDebugLogsEnabled
33
+ ? createVerboseHandlers(`[${prefix}]`)
34
+ : createQuietHandlers(`[${prefix}]`);
27
35
  ```
28
36
 
29
37
  ### 预定义 Logger
@@ -100,6 +108,6 @@ export const STORAGE_KEYS = {
100
108
 
101
109
  ## 注意事项
102
110
 
103
- - Logger 仅在开发环境输出,生产环境静默
111
+ - 生产默认仅 `error` 输出;首屏 `?debug=1` 等可打开全量日志(见上文),用完建议去掉参数或整页刷新无参 URL
104
112
  - 使用 `bind` 实现,DevTools 显示实际调用位置而非 logger.ts
105
113
  - 新增路由常量应添加到 `ROUTES` 对象
@@ -1,11 +1,13 @@
1
1
  # 构建 define 精简与免认证页初始态
2
2
 
3
- > 创建时间:2026-03-24
3
+ > 创建时间:2026-03-24
4
+ > 更新:2026-05-13(补充 `defaultPath` 首屏与 `authCheckPath`、与手动跳转认证页的差异;补充从免认证页跳进认证页后 **`refresh` → `fetchUserInfo`** 的说明)
4
5
 
5
6
  ## 功能概述
6
7
 
7
8
  1. **Umi `define`**:不再通过构建期注入 `process.env.EXTERNAL_LOGIN_PATH`(本模板从未注入 `APP_ID`);SSO 外跳等依赖运行时 `window.__MICO_CONFIG__`。
8
9
  2. **`getInitialState`**:免认证路由下即使本地仍有 token,也不请求用户信息接口,减少无效调用。
10
+ 3. **`/` + `defaultPath` 首屏**:若仅用 `location.pathname === '/'` 判断是否免认证,可能与「即将 `replace` 到的默认页」的认证要求不一致;见下文 **`authCheckPath`**。
9
11
 
10
12
  ## 技术方案
11
13
 
@@ -21,7 +23,46 @@
21
23
 
22
24
  ### 初始态
23
25
 
24
- `src/app.tsx` 中仅在 `getStoredAuthToken() && !skipAuth` 时调用 `fetchUserInfoFn()`;`skipAuth = isNoAuthRoute || isPageAuthFree`。
26
+ `src/app.tsx` 中仅在 `getStoredAuthToken() && !skipAuth` 时调用 `fetchUserInfoFn()`;`skipAuth` **`isNoAuthRoute(authCheckPath) || isPageAuthFree(authCheckPath)`** 计算(见下节 **`authCheckPath`**,勿与仅看 `location.pathname` 混淆)。
27
+
28
+ ### `defaultPath` 与 `authCheckPath`(首屏重定向 vs 手动进认证页)
29
+
30
+ #### 背景
31
+
32
+ 访问 **`/`** 且配置了 **`window.__MICO_CONFIG__.defaultPath`**(且不为 `/`)时,`app.tsx` 的 **`onRouteChange`** 会 **`history.replace(defaultPath)`**(例如跳到工作台)。**`getInitialState` 与 `onRouteChange` 的执行顺序下,首屏拿到的 `history.location.pathname` 往往仍是 `/`**。
33
+
34
+ 若 **`/`** 被配进 **`noAuthRouteList`**,或 **`/`** 对应页面 **`accessControlEnabled: false`**(页面级免认证),则仅按 **`pathname === '/'`** 会得到 **`skipAuth === true`**:不执行 **`ensureSsoSession`**、不按需认证页拉用户;随后用户马上被重定向到 **`defaultPath` 所指的需登录页**,形成「用免认证壳路径误判、绕开目标页认证」的窗口。为此在 **`getInitialState`**(约自 `app.tsx` 215 行起)对 **`defaultPath`** 做与 **`onRouteChange`** 一致的重定向预判。
35
+
36
+ #### `authCheckPath` 规则
37
+
38
+ - 当 **`pathname === '/'`** 且存在有效 **`defaultPath`**(与 `/` 不同)时,视为即将发生默认重定向,令 **`authCheckPath = defaultPath`**;
39
+ - 否则 **`authCheckPath = location.pathname`**。
40
+
41
+ **`isNoAuthRoute`、`isPageAuthFree` 以及由此得到的 `skipAuth`、`ensureSsoSession`、是否拉 `fetchUserInfo` 等,均基于 `authCheckPath`**,使首屏认证语义与 **重定向目标页** 一致,而不是与瞬时的 **`/`** 一致。
42
+
43
+ 实现上 **`authCheckPath`** 与 **`handleAuthFailureRedirect`**(`src/common/request/sso.ts`)共用 **`resolveAuthCheckPath(pathname)`**(`src/common/auth/auth-check-path.ts`),避免无 token 时 **`getInitialState` 已按 `defaultPath` 判定需认证**,但 SSO 外跳仍按浏览器地址 **`/`** 误判为免认证而**不触发重定向**。
44
+
45
+ #### 与「从免认证页手动点到认证页」的区别
46
+
47
+ | 场景 | 典型 `location.pathname` | 为何不会误判 |
48
+ | --- | --- | --- |
49
+ | **`/` + `defaultPath` 首屏** | 首屏仍为 **`/`**,真实要去 **`defaultPath`** | 若只看 **`/`**,可能与 **`defaultPath`** 的认证要求不一致;必须用 **`authCheckPath`** 把判断「对齐到目标页」。 |
50
+ | **应用内从免认证页导航到认证页**(如 `/login` → `/app`) | 导航后 **`/app/...`** | 不会用旧页的免认证 pathname 算 **`skipAuth`**;且布局里 **`useRoutePermissionRefresh`** 在 pathname 变为**非** `isNoAuthRoute` 时会调用 **`@@initialState` 的 `refresh()`**,从而**再次执行 `getInitialState`**,在 **`getStoredAuthToken() && !skipAuth`** 成立时拉 **`fetchUserInfo`**(见下节)。整页刷新直达认证页时逻辑相同,仅触发时机为首屏而非路由变更。 |
51
+ | **外链直达或地址栏改为认证页** | 直接进入 **`/app/xxx`** | 首屏 `getInitialState` 即按认证页计算 **`skipAuth`**;有 token 则拉 **`fetchUserInfo`**。 |
52
+
53
+ #### 为何客户端从免认证跳进认证页后会拉取用户权限(`fetchUserInfo`)
54
+
55
+ 1. **首屏停在免认证路径**:`getInitialState` 里 **`skipAuth === true`**,模板在 **`getStoredAuthToken() && !skipAuth`** 条件下**不会**为当前首屏去调用户信息接口,`currentUser` 可能仍为空(见 `app.tsx`)。
56
+
57
+ 2. **`pathname` 变为需认证路径**:`layouts/index.tsx` 使用 **`useRoutePermissionRefresh`**(`src/hooks/useRoutePermissionRefresh.ts`)。在**跳过首次渲染**之后,每当 **`location.pathname` 变化**且新路径**不是** **`isNoAuthRoute`** 所指的静态免认证路由时,会防抖调用 Umi **`useModel('@@initialState').refresh()`**。
58
+
59
+ 3. **`refresh()` 会重新执行 `getInitialState`**:此时用于计算 **`skipAuth`** 的路径已是认证页(并适用上文 **`authCheckPath`** 规则),一般 **`skipAuth === false`**;若本地仍有 **`getStoredAuthToken()`**,即进入 **`fetchUserInfoFn()`**,请求 **`GET /user/info/`** 等,更新 **`menu_perms`、`button_perms`** 等,供菜单、PermissionFilter、MicroAppLoader 注入子应用等使用。
60
+
61
+ 4. **与「整页刷新且首屏已在认证页」**:走同一套 `getInitialState` 分支;差别是触发源为 **路由变更 + `refresh()`**,而非 **应用冷启动首屏**。
62
+
63
+ 5. **实现细节**:`useRoutePermissionRefresh` 仅以 **`isNoAuthRoute(pathname)`** 判断是否跳过刷新;若某路由**仅**页面级 **`isPageAuthFree`** 而不在 `noAuthRouteList` 中,离开该页时 pathname 变化仍可能触发 **`refresh()`**——即「离开静态免认证名单就允许刷新权限」的策略。
64
+
65
+ **结论**:「绕过认证」风险来自 **「URL 暂时是免认证的 `/`,但实际业务要去需 SSO 的 `defaultPath`」** 这一种 **首屏 + 默认重定向** 组合;**`authCheckPath`** 专门纠偏这一种。**手动从免认证页进入认证页**时,浏览器地址已是认证路径,鉴权按认证页执行,**不依赖**与 `defaultPath` 相同的特殊分支;二者差异在于 **是否存在「pathname 与真实首屏业务路径不一致」的短暂错位**。在 **SPA** 下,进入认证页后还会经 **`useRoutePermissionRefresh` → `refresh()`** 在 **有 token** 时补拉 **`fetchUserInfo`**(见上节),避免 **`currentUser` 仍为空**、菜单与子应用权限数据落后。
25
66
 
26
67
  ## 文件清单
27
68
 
@@ -31,7 +72,11 @@
31
72
  | `config/config.prod.ts` | 移除 `EXTERNAL_LOGIN_PATH` define |
32
73
  | `config/config.prod.development.ts` | 同上 |
33
74
  | `config/config.prod.testing.ts` | 同上 |
34
- | `src/app.tsx` | `fetchUserInfo` 条件增加 `!skipAuth` |
75
+ | `src/app.tsx` | `getInitialState` 使用 `resolveAuthCheckPath` |
76
+ | `src/common/auth/auth-check-path.ts` | `resolveAuthCheckPath`:`/` + `defaultPath` 与真实鉴权路径对齐 |
77
+ | `src/common/request/sso.ts` | `handleAuthFailureRedirect` 使用 `resolveAuthCheckPath`,与 `getInitialState` 一致 |
78
+ | `src/hooks/useRoutePermissionRefresh.ts` | 非静态免认证路径的 pathname 变更时 `refresh()`,触发重跑 `getInitialState` 以拉权限 |
79
+ | `src/layouts/index.tsx` | 使用 `useRoutePermissionRefresh` |
35
80
 
36
81
  ## 部署注意
37
82
 
@@ -40,5 +85,6 @@
40
85
 
41
86
  ## 相关文档
42
87
 
88
+ - [菜单权限控制](./feature-菜单权限控制.md)(SSO 步骤与 pathname / `authCheckPath` 关系)
43
89
  - [fix-SSO无限重定向](./fix-SSO无限重定向.md)
44
90
  - [arch-请求模块](./arch-请求模块.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`)