generator-mico-cli 0.2.28 → 0.2.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +7 -20
  2. package/bin/mico.js +27 -62
  3. package/generators/micro-react/index.js +25 -1
  4. package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +3 -0
  5. package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +1 -0
  6. package/generators/micro-react/templates/CICD/start_dev.sh +11 -0
  7. package/generators/micro-react/templates/CICD/start_local.sh +9 -0
  8. package/generators/micro-react/templates/CICD/start_prod.sh +13 -0
  9. package/generators/micro-react/templates/CICD/start_test.sh +11 -0
  10. package/generators/micro-react/templates/CLAUDE.md +1 -0
  11. package/generators/micro-react/templates/README.md +1 -1
  12. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +13 -5
  13. package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +12 -0
  14. package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +12 -0
  15. package/generators/micro-react/templates/apps/layout/config/config.prod.ts +14 -0
  16. package/generators/micro-react/templates/apps/layout/docs/feature-PermissionFilter/346/214/211/351/222/256/346/235/203/351/231/220.md +116 -0
  17. package/generators/micro-react/templates/apps/layout/docs/feature-/345/233/275/351/231/205/345/214/226.md +121 -0
  18. 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
  19. package/generators/micro-react/templates/apps/layout/docs/feature-/350/217/234/345/215/225/346/235/203/351/231/220/346/216/247/345/210/266.md +83 -77
  20. package/generators/micro-react/templates/apps/layout/docs/feature-/350/267/257/347/224/261/344/270/216/350/217/234/345/215/225/350/247/243/350/200/246.md +50 -35
  21. package/generators/micro-react/templates/apps/layout/docs/feature-/350/267/257/347/224/261/346/235/203/351/231/220/346/227/245/345/277/227.md +162 -0
  22. package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +23 -31
  23. package/generators/micro-react/templates/apps/layout/mock/menus.ts +14 -0
  24. package/generators/micro-react/templates/apps/layout/mock/pages.ts +27 -8
  25. package/generators/micro-react/templates/apps/layout/package.json +2 -0
  26. package/generators/micro-react/templates/apps/layout/src/app.tsx +85 -4
  27. package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +3 -0
  28. package/generators/micro-react/templates/apps/layout/src/common/auth/tenant.ts +25 -0
  29. package/generators/micro-react/templates/apps/layout/src/common/auth/type.ts +41 -27
  30. package/generators/micro-react/templates/apps/layout/src/common/intl/formatLayoutMessage.ts +30 -0
  31. package/generators/micro-react/templates/apps/layout/src/common/intl/index.ts +6 -0
  32. package/generators/micro-react/templates/apps/layout/src/common/intl/intlRuntime.ts +14 -0
  33. package/generators/micro-react/templates/apps/layout/src/common/intl/localeMapping.ts +30 -0
  34. package/generators/micro-react/templates/apps/layout/src/common/intl/types.ts +14 -0
  35. package/generators/micro-react/templates/apps/layout/src/common/intl/useLayoutIntl.ts +40 -0
  36. package/generators/micro-react/templates/apps/layout/src/common/logger.ts +3 -4
  37. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +148 -85
  38. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +29 -6
  39. package/generators/micro-react/templates/apps/layout/src/common/micro/types.ts +23 -0
  40. package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +46 -2
  41. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +74 -15
  42. package/generators/micro-react/templates/apps/layout/src/common/request/token-refresh.ts +2 -0
  43. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +32 -6
  44. package/generators/micro-react/templates/apps/layout/src/components/PermissionFilter/index.tsx +51 -0
  45. package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +10 -1
  46. package/generators/micro-react/templates/apps/layout/src/components/RightContent/TenantDropdown.tsx +76 -0
  47. package/generators/micro-react/templates/apps/layout/src/components/RightContent/index.ts +1 -0
  48. package/generators/micro-react/templates/apps/layout/src/components/RightContent/tenant-dropdown.less +48 -0
  49. package/generators/micro-react/templates/apps/layout/src/constants/index.ts +1 -0
  50. package/generators/micro-react/templates/apps/layout/src/hooks/index.ts +1 -0
  51. package/generators/micro-react/templates/apps/layout/src/hooks/useMenuState.ts +18 -0
  52. package/generators/micro-react/templates/apps/layout/src/hooks/useTenant.ts +41 -0
  53. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +4 -1
  54. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +21 -9
  55. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +105 -60
  56. package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +28 -0
  57. package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +26 -0
  58. package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +7 -3
  59. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +32 -0
  60. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +148 -4
  61. package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +2 -1
  62. package/generators/micro-react/templates/apps/layout/src/services/user.ts +79 -21
  63. package/generators/micro-react/templates/apps/layout/typings.d.ts +16 -0
  64. package/generators/micro-react/templates/docs/package-shared.md +189 -0
  65. package/generators/micro-react/templates/package.json +1 -1
  66. package/generators/micro-react/templates/packages/common-intl/README.md +78 -368
  67. package/generators/micro-react/templates/packages/common-intl/package.json +3 -13
  68. package/generators/micro-react/templates/packages/common-intl/src/index.ts +5 -6
  69. package/generators/micro-react/templates/packages/common-intl/src/intl.ts +115 -28
  70. package/generators/micro-react/templates/packages/common-intl/src/umiLocaleBridge.ts +101 -0
  71. package/generators/micro-react/templates/packages/common-intl/tsconfig.json +2 -4
  72. package/generators/micro-react/templates/packages/shared/README.md +120 -0
  73. package/generators/micro-react/templates/packages/shared/package.json +26 -0
  74. package/generators/micro-react/templates/packages/shared/services/common/index.ts +43 -0
  75. package/generators/micro-react/templates/packages/shared/services/index.ts +21 -0
  76. package/generators/micro-react/templates/packages/shared/services/request.ts +43 -0
  77. package/generators/micro-react/templates/packages/shared/timezone/index.ts +228 -0
  78. package/generators/micro-react/templates/packages/shared/tsconfig.json +20 -0
  79. package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +6 -1
  80. package/generators/micro-react/templates/turbo.json +9 -1
  81. package/generators/subapp-react/index.js +28 -22
  82. package/generators/subapp-react/templates/homepage/README.md +1 -0
  83. package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +1 -0
  84. package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +1 -0
  85. package/generators/subapp-react/templates/homepage/config/config.prod.ts +1 -0
  86. package/generators/subapp-react/templates/homepage/config/config.ts +10 -0
  87. package/generators/subapp-react/templates/homepage/docs/feature-PermissionFilter/346/214/211/351/222/256/346/235/203/351/231/220.md +35 -0
  88. package/generators/subapp-react/templates/homepage/docs/feature-/345/233/275/351/231/205/345/214/226.md +124 -0
  89. package/generators/subapp-react/templates/homepage/package.json +3 -1
  90. package/generators/subapp-react/templates/homepage/src/app.tsx +104 -2
  91. package/generators/subapp-react/templates/homepage/src/common/intl/index.ts +15 -0
  92. package/generators/subapp-react/templates/homepage/src/common/intl/intlRuntime.ts +14 -0
  93. package/generators/subapp-react/templates/homepage/src/common/intl/localeMapping.ts +24 -0
  94. package/generators/subapp-react/templates/homepage/src/common/intl/subappIntlConfig.ts +28 -0
  95. package/generators/subapp-react/templates/homepage/src/common/intl/subappLocale.ts +18 -0
  96. package/generators/subapp-react/templates/homepage/src/common/intl/subappOwnIntl.ts +63 -0
  97. package/generators/subapp-react/templates/homepage/src/common/intl/types.ts +14 -0
  98. package/generators/subapp-react/templates/homepage/src/common/intl/useSubappIntl.ts +61 -0
  99. package/generators/subapp-react/templates/homepage/src/common/locale.ts +80 -0
  100. package/generators/subapp-react/templates/homepage/src/common/mainApp.ts +41 -2
  101. package/generators/subapp-react/templates/homepage/src/components/PermissionFilter/index.tsx +48 -0
  102. package/generators/subapp-react/templates/homepage/src/locales/en-US.ts +6 -0
  103. package/generators/subapp-react/templates/homepage/src/locales/zh-CN.ts +6 -0
  104. package/generators/subapp-react/templates/homepage/src/pages/index.less +10 -0
  105. package/generators/subapp-react/templates/homepage/src/pages/index.tsx +86 -1
  106. package/generators/subapp-react/templates/homepage/typings.d.ts +12 -0
  107. package/lib/utils.js +0 -1
  108. package/package.json +2 -2
  109. package/generators/micro-react/templates/apps/layout/docs/common-intl.md +0 -372
  110. package/generators/micro-react/templates/packages/common-intl/src/indexedDBUtils.ts +0 -51
  111. package/generators/micro-react/templates/packages/common-intl/src/utils.ts +0 -482
  112. package/generators/micro-react/templates/packages/common-intl/vite.config.ts +0 -25
@@ -0,0 +1,228 @@
1
+ /**
2
+ * 时区工具
3
+ *
4
+ * 设计:
5
+ * - 时区**读写**(如 localStorage 存储)由消费方(主应用)负责
6
+ * - 本模块通过 `configureTimezone({ getTimezone })` 注入器拿到「当前 IANA 时区」
7
+ * - 包内业务函数(解析、格式化、范围)统一调用注入函数,避免与具体存储耦合
8
+ *
9
+ * 外部依赖:dayjs + utc/timezone/customParseFormat 插件,需消费方在入口预先调用
10
+ * dayjs.extend(utc); dayjs.extend(timezone); dayjs.extend(customParseFormat);
11
+ */
12
+
13
+ import dayjs, { type Dayjs } from 'dayjs';
14
+
15
+ // ==================== 常量与类型 ====================
16
+
17
+ /**
18
+ * UTC 偏移量键的枚举映射,覆盖 UTC-12 到 UTC+14
19
+ */
20
+ export const TIMEZONE = {
21
+ 'UTC-12': 'UTC-12',
22
+ 'UTC-11': 'UTC-11',
23
+ 'UTC-10': 'UTC-10',
24
+ 'UTC-9': 'UTC-9',
25
+ 'UTC-8': 'UTC-8',
26
+ 'UTC-7': 'UTC-7',
27
+ 'UTC-6': 'UTC-6',
28
+ 'UTC-5': 'UTC-5',
29
+ 'UTC-4': 'UTC-4',
30
+ 'UTC-3': 'UTC-3',
31
+ 'UTC-2': 'UTC-2',
32
+ 'UTC-1': 'UTC-1',
33
+ 'UTC+0': 'UTC+0',
34
+ 'UTC+1': 'UTC+1',
35
+ 'UTC+2': 'UTC+2',
36
+ 'UTC+3': 'UTC+3',
37
+ 'UTC+4': 'UTC+4',
38
+ 'UTC+5': 'UTC+5',
39
+ 'UTC+6': 'UTC+6',
40
+ 'UTC+7': 'UTC+7',
41
+ 'UTC+8': 'UTC+8',
42
+ 'UTC+9': 'UTC+9',
43
+ 'UTC+10': 'UTC+10',
44
+ 'UTC+11': 'UTC+11',
45
+ 'UTC+12': 'UTC+12',
46
+ 'UTC+13': 'UTC+13',
47
+ 'UTC+14': 'UTC+14',
48
+ } as const;
49
+
50
+ export type TTimezone = keyof typeof TIMEZONE;
51
+
52
+ /**
53
+ * 地理上 24 个时区,但实际上有约 38 个不同的偏移时区,行政上则有上百个不同的时区规则地区。
54
+ * 24 个理论时区基于经度划分,每 15° 一时区,整数小时 UTC 偏移。
55
+ * 大部分时区可能有多个可用的城市/地区标识,此处仅使用一个常用标识。
56
+ */
57
+ export const TIMEZONE_HASH: Record<TTimezone, string> = {
58
+ [TIMEZONE['UTC-12']]: 'Etc/GMT+12',
59
+ [TIMEZONE['UTC-11']]: 'Pacific/Pago_Pago',
60
+ [TIMEZONE['UTC-10']]: 'Pacific/Honolulu',
61
+ [TIMEZONE['UTC-9']]: 'America/Anchorage',
62
+ [TIMEZONE['UTC-8']]: 'America/Los_Angeles',
63
+ [TIMEZONE['UTC-7']]: 'America/Denver',
64
+ [TIMEZONE['UTC-6']]: 'America/Chicago',
65
+ [TIMEZONE['UTC-5']]: 'America/New_York',
66
+ [TIMEZONE['UTC-4']]: 'America/Puerto_Rico',
67
+ [TIMEZONE['UTC-3']]: 'America/Sao_Paulo',
68
+ [TIMEZONE['UTC-2']]: 'America/Noronha',
69
+ [TIMEZONE['UTC-1']]: 'Atlantic/Azores',
70
+ [TIMEZONE['UTC+0']]: 'Europe/London',
71
+ [TIMEZONE['UTC+1']]: 'Europe/Paris',
72
+ [TIMEZONE['UTC+2']]: 'Europe/Kyiv',
73
+ [TIMEZONE['UTC+3']]: 'Europe/Moscow',
74
+ [TIMEZONE['UTC+4']]: 'Asia/Dubai',
75
+ [TIMEZONE['UTC+5']]: 'Asia/Tashkent',
76
+ [TIMEZONE['UTC+6']]: 'Asia/Dhaka',
77
+ [TIMEZONE['UTC+7']]: 'Asia/Bangkok',
78
+ [TIMEZONE['UTC+8']]: 'Asia/Shanghai',
79
+ [TIMEZONE['UTC+9']]: 'Asia/Tokyo',
80
+ [TIMEZONE['UTC+10']]: 'Australia/Sydney',
81
+ [TIMEZONE['UTC+11']]: 'Pacific/Noumea',
82
+ [TIMEZONE['UTC+12']]: 'Pacific/Auckland',
83
+ [TIMEZONE['UTC+13']]: 'Pacific/Tongatapu',
84
+ [TIMEZONE['UTC+14']]: 'Pacific/Kiritimati',
85
+ };
86
+
87
+ /** 默认 IANA 时区,所有注入未就绪 / 未匹配时的兜底 */
88
+ const DEFAULT_IANA = TIMEZONE_HASH[TIMEZONE['UTC+8']];
89
+
90
+ // ==================== 纯转换函数 ====================
91
+
92
+ /**
93
+ * 将 UTC offset 数字转换为 UTC+N 格式的时区字符串
94
+ * @example utcOffsetToTimezone(8) => 'UTC+8'
95
+ * @example utcOffsetToTimezone(-5) => 'UTC-5'
96
+ */
97
+ export const utcOffsetToTimezone = (utcOffset: number): string => {
98
+ if (utcOffset >= 0) {
99
+ return `UTC+${utcOffset}`;
100
+ }
101
+ return `UTC${utcOffset}`;
102
+ };
103
+
104
+ /**
105
+ * 将 UTC 偏移量转换为 IANA 时区标识(带兜底)
106
+ * 回退顺序:TIMEZONE_HASH → dayjs.tz.guess() → Asia/Shanghai
107
+ */
108
+ export const utcOffsetToTimeZoneStr = (utcOffset: number): string => {
109
+ const key = utcOffsetToTimezone(utcOffset) as TTimezone;
110
+ return TIMEZONE_HASH[key] ?? dayjs.tz?.guess?.() ?? DEFAULT_IANA;
111
+ };
112
+
113
+ // ==================== 注入器 ====================
114
+
115
+ /**
116
+ * 时区注入函数:返回当前 IANA 时区标识(如 'Asia/Shanghai')
117
+ * 由消费方在应用入口注入,通常代理到主应用的时区管理模块
118
+ */
119
+ export type TimezoneGetter = () => string;
120
+
121
+ let _getTimezone: TimezoneGetter | null = null;
122
+
123
+ /**
124
+ * 注入时区获取器
125
+ *
126
+ * @example
127
+ * import { configureTimezone } from '<%= packageScope %>/shared/timezone';
128
+ * import { getTimezone } from '@/common/helpers';
129
+ *
130
+ * configureTimezone({ getTimezone });
131
+ */
132
+ export function configureTimezone(options: { getTimezone: TimezoneGetter }): void {
133
+ _getTimezone = options.getTimezone;
134
+ }
135
+
136
+ /**
137
+ * 获取当前 IANA 时区
138
+ * - 已注入:调用注入器
139
+ * - 未注入:尝试 `dayjs.tz.guess()`,最终兜底到 'Asia/Shanghai'
140
+ *
141
+ * 包内所有格式化函数都通过此函数取时区,避免直接耦合 storage 实现
142
+ */
143
+ export function getCurrentTimezoneIANA(): string {
144
+ if (_getTimezone) {
145
+ try {
146
+ const tz = _getTimezone();
147
+ if (tz) return tz;
148
+ } catch {
149
+ // 注入器异常时走兜底
150
+ }
151
+ }
152
+ return dayjs.tz?.guess?.() ?? DEFAULT_IANA;
153
+ }
154
+
155
+ // ==================== 时间解析与格式化 ====================
156
+
157
+ const BUSINESS_TIME_PARSE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
158
+ const BUSINESS_MINUTE_PARSE_FORMAT = 'YYYY-MM-DD HH:mm';
159
+ const BUSINESS_DATE_PARSE_FORMAT = 'YYYY-MM-DD';
160
+
161
+ const getBusinessTimeParseFormat = (value: string): string => {
162
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(value)) {
163
+ return BUSINESS_TIME_PARSE_FORMAT;
164
+ }
165
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(value)) {
166
+ return BUSINESS_MINUTE_PARSE_FORMAT;
167
+ }
168
+ return BUSINESS_DATE_PARSE_FORMAT;
169
+ };
170
+
171
+ /**
172
+ * 按当前业务时区解析日期字符串,避免回退到浏览器本机时区
173
+ */
174
+ export const parseDateInTimezone = (value: string): Dayjs => {
175
+ return dayjs.tz(value, getBusinessTimeParseFormat(value), getCurrentTimezoneIANA());
176
+ };
177
+
178
+ export type TShortcutDateRangeUnit = 'day' | 'week';
179
+
180
+ /**
181
+ * 生成当前业务时区下的快捷日期范围
182
+ * - day: 当天开始与结束
183
+ * - week: 当周开始与结束
184
+ * - undefined: 当前时刻
185
+ */
186
+ export const getShortcutDateRange = (
187
+ unit?: TShortcutDateRangeUnit,
188
+ ): [Dayjs, Dayjs] => {
189
+ const now = dayjs().tz(getCurrentTimezoneIANA());
190
+ if (!unit) {
191
+ return [now, now];
192
+ }
193
+ return [now.startOf(unit), now.endOf(unit)];
194
+ };
195
+
196
+ /**
197
+ * 格式化时间戳为用户时区的标准日期时间字符串
198
+ * @returns YYYY.MM.DD HH:mm:ss;非法输入原样返回
199
+ *
200
+ * @example
201
+ * formatTimestampToDateTime(1730721621000) // => '2025.11.04 16:20:21' (UTC+8)
202
+ */
203
+ export const formatTimestampToDateTime = (timestamp: number): string | number => {
204
+ if (!Number.isFinite(timestamp)) {
205
+ return timestamp;
206
+ }
207
+ return dayjs.utc(timestamp).tz(getCurrentTimezoneIANA()).format('YYYY.MM.DD HH:mm:ss');
208
+ };
209
+
210
+ /**
211
+ * 格式化时间戳为指定时区的标准日期时间字符串(带时区后缀)
212
+ * @returns YYYY.MM.DD HH:mm:ss(UTC+n);非法输入或未知时区原样返回
213
+ *
214
+ * @example
215
+ * formatTimestampToDateTimeWithTimezone(1730721621000, 'UTC+8') // => '2025.11.04 16:20:21(UTC+8)'
216
+ */
217
+ export const formatTimestampToDateTimeWithTimezone = (
218
+ timestamp: number,
219
+ timezone: TTimezone,
220
+ ): string | number => {
221
+ if (!Number.isFinite(timestamp) || !TIMEZONE[timezone]) {
222
+ return timestamp;
223
+ }
224
+ return dayjs
225
+ .utc(timestamp)
226
+ .tz(TIMEZONE_HASH[timezone])
227
+ .format(`YYYY.MM.DD HH:mm:ss(${timezone})`);
228
+ };
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "strict": true,
14
+ "noUnusedLocals": true,
15
+ "noUnusedParameters": true,
16
+ "noFallthroughCasesInSwitch": true,
17
+ "esModuleInterop": true
18
+ },
19
+ "include": ["services", "timezone"]
20
+ }
@@ -16,6 +16,11 @@ export function applySentryPlugin({
16
16
  project = '<%= projectName %>',
17
17
  }: ApplySentryPluginOptions) {
18
18
  const version = require('../package.json').version;
19
+ const buildNumber = process.env.BUILD_NUMBER?.trim();
20
+ const releaseName =
21
+ buildNumber && buildNumber.length > 0
22
+ ? `${version}+${buildNumber}`
23
+ : version;
19
24
 
20
25
  // 使用 Legacy Upload 方式,自托管 Sentry 不支持 Debug ID / Artifact Bundle
21
26
  const { sentryWebpackPlugin } = require('@sentry/webpack-plugin');
@@ -29,7 +34,7 @@ export function applySentryPlugin({
29
34
  /** @see https://micoworld.feishu.cn/wiki/AmXQwmnEairvQ9k2m9KcgJNcndv#share-OupCdqeeUoESO4x6YKPcrs4nn3c */
30
35
  authToken: process.env.SENTRY_AUTH_TOKEN,
31
36
  release: {
32
- name: version,
37
+ name: releaseName,
33
38
  uploadLegacySourcemaps: {
34
39
  paths: ['./dist'],
35
40
  urlPrefix: `~/<%= cdnPrefixPath %><%= projectName %>/${version}/${appName}`,
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "$schema": "https://turbo.build/schema.json",
3
- "globalEnv": ["CDN_PUBLIC_PATH", "SENTRY_AUTH_TOKEN"],
3
+ "globalEnv": ["CDN_PUBLIC_PATH", "SENTRY_AUTH_TOKEN", "BRANCH_OR_TAG", "GIT_COMMIT", "VERSION"],
4
+ "globalPassThroughEnv": [
5
+ "VERSION",
6
+ "BUILD_NUMBER",
7
+ "GIT_COMMIT",
8
+ "BRANCH_OR_TAG",
9
+ "BUILD_TIME",
10
+ "GIT_BRANCH"
11
+ ],
4
12
  "tasks": {
5
13
  "build": {
6
14
  "dependsOn": ["^build"],
@@ -257,7 +257,7 @@ module.exports = class extends Generator {
257
257
 
258
258
  this._updateDevPreset();
259
259
  this._updateMockPages();
260
- this._updateConfigDev();
260
+ this._updateApiMockMenuPerms();
261
261
  } catch (error) {
262
262
  console.error('');
263
263
  console.error('❌ Error during file generation:');
@@ -297,6 +297,12 @@ module.exports = class extends Generator {
297
297
  }
298
298
  }
299
299
 
300
+ /** 与 mock menus / api.mock menu_perms 一致的权限 key(cs_web_menu_subapp_page 风格) */
301
+ _subappRouteKey() {
302
+ const snake = this.appName.replace(/-/g, '_');
303
+ return `cs_web_menu_${snake}_page`;
304
+ }
305
+
300
306
  _updateMockPages() {
301
307
  const pagesPath = path.join(this.monorepoRoot, 'apps/layout/mock/pages.ts');
302
308
  if (!fs.existsSync(pagesPath)) {
@@ -312,6 +318,8 @@ module.exports = class extends Generator {
312
318
  return;
313
319
  }
314
320
 
321
+ const routeKey = this._subappRouteKey();
322
+
315
323
  // 找出已有的最大 id
316
324
  const idMatches = [...content.matchAll(/id:\s*(\d+)/g)];
317
325
  const maxId = idMatches.reduce((max, m) => Math.max(max, Number(m[1])), 0);
@@ -330,9 +338,9 @@ module.exports = class extends Generator {
330
338
  " prefixPath: '',",
331
339
  " routeMode: 'prefix',",
332
340
  ' enabled: true,',
333
- ' accessControlEnabled: false,',
341
+ ' accessControlEnabled: true,',
334
342
  ' adminOnly: false,',
335
- ' routeKey: null,',
343
+ ` routeKey: '${routeKey}',`,
336
344
  ` mainDocumentId: ${maxId + 1},`,
337
345
  " version: '',",
338
346
  ' },',
@@ -369,40 +377,38 @@ module.exports = class extends Generator {
369
377
  }
370
378
  }
371
379
 
372
- _updateConfigDev() {
373
- const configPath = path.join(this.monorepoRoot, 'apps/layout/config/config.dev.ts');
374
- if (!fs.existsSync(configPath)) {
375
- this.logger.verbose('apps/layout/config/config.dev.ts not found, skipping');
380
+ /** 将子应用 routeKey 写入 layout mock 的 GET /user/info menu_perms,本地 mock 用户具备该页菜单权限 */
381
+ _updateApiMockMenuPerms() {
382
+ const apiMockPath = path.join(this.monorepoRoot, 'apps/layout/mock/api.mock.ts');
383
+ if (!fs.existsSync(apiMockPath)) {
384
+ this.logger.verbose('apps/layout/mock/api.mock.ts not found, skipping');
376
385
  return;
377
386
  }
378
387
 
379
388
  try {
380
- const content = fs.readFileSync(configPath, 'utf-8');
381
- const routeEntry = `'/${this.appName}/*'`;
389
+ const content = fs.readFileSync(apiMockPath, 'utf-8');
390
+ const routeKey = this._subappRouteKey();
391
+ const quotedKey = `'${routeKey}'`;
382
392
 
383
- if (content.includes(routeEntry)) {
384
- this.logger.verbose(`Route "${routeEntry}" already in noPermissionRouteList, skipping`);
393
+ if (content.includes(quotedKey)) {
394
+ this.logger.verbose(`menu_perms already contains ${routeKey}, skipping`);
385
395
  return;
386
396
  }
387
397
 
388
398
  const updated = content.replace(
389
- /noPermissionRouteList:\s*\[([^\]]*)\]/,
390
- (match, inner) => {
391
- const trimmed = inner.trim();
392
- const entries = trimmed ? `${trimmed}, ${routeEntry}` : routeEntry;
393
- return `noPermissionRouteList: [${entries}]`;
394
- },
399
+ /(menu_perms:\s*\[[\s\S]*?)(\n\s*\],)/,
400
+ (match, before, closing) => `${before}\n ${quotedKey},${closing}`,
395
401
  );
396
402
 
397
403
  if (updated === content) {
398
- this.logger.verbose('noPermissionRouteList not found in config.dev.ts, skipping');
404
+ this.logger.verbose('menu_perms array not found in api.mock.ts, skipping');
399
405
  return;
400
406
  }
401
407
 
402
- fs.writeFileSync(configPath, updated, 'utf-8');
403
- this.log(` 📝 已将 "${routeEntry}" 添加到 config.dev.ts 的 noPermissionRouteList 中`);
408
+ fs.writeFileSync(apiMockPath, updated, 'utf-8');
409
+ this.log(` 📝 已将 "${routeKey}" 添加到 api.mock.ts 的 GET /user/info menu_perms 中`);
404
410
  } catch (e) {
405
- console.warn(` ⚠️ 更新 config.dev.ts 失败: ${e.message}`);
411
+ console.warn(` ⚠️ 更新 api.mock.ts 失败: ${e.message}`);
406
412
  }
407
413
  }
408
414
 
@@ -429,7 +435,7 @@ module.exports = class extends Generator {
429
435
  if (fs.existsSync(path.join(layoutDir, '.prettierrc'))) {
430
436
  const filesToFormat = [
431
437
  path.join(layoutDir, 'mock/pages.ts'),
432
- path.join(layoutDir, 'config/config.dev.ts'),
438
+ path.join(layoutDir, 'mock/api.mock.ts'),
433
439
  ].filter((f) => fs.existsSync(f));
434
440
 
435
441
  if (filesToFormat.length > 0) {
@@ -102,6 +102,7 @@ PORT=8001
102
102
  ### 项目文档
103
103
 
104
104
  - [项目根目录 README](../../README.md)
105
+ - [PermissionFilter 按钮权限](./docs/feature-PermissionFilter按钮权限.md)(子应用用法与主应用文档入口)
105
106
 
106
107
  ### UmiJS 官方文档
107
108
 
@@ -45,6 +45,7 @@ const config: ReturnType<typeof defineConfig> = {
45
45
  'react-dom': 'ReactDOM',
46
46
  '@mico-platform/ui': 'micoUI',
47
47
  '@common-web/sentry': 'CommonWebSentry',
48
+ '<%= packageScope %>/common-intl': 'CommonIntl',
48
49
  },
49
50
  };
50
51
 
@@ -45,6 +45,7 @@ const config: ReturnType<typeof defineConfig> = {
45
45
  'react-dom': 'ReactDOM',
46
46
  '@mico-platform/ui': 'micoUI',
47
47
  '@common-web/sentry': 'CommonWebSentry',
48
+ '<%= packageScope %>/common-intl': 'CommonIntl',
48
49
  },
49
50
  };
50
51
 
@@ -44,6 +44,7 @@ const config: ReturnType<typeof defineConfig> = {
44
44
  'react-dom': 'ReactDOM',
45
45
  '@mico-platform/ui': 'micoUI',
46
46
  '@common-web/sentry': 'CommonWebSentry',
47
+ '<%= packageScope %>/common-intl': 'CommonIntl',
47
48
  },
48
49
  };
49
50
 
@@ -76,6 +76,16 @@ const config: ReturnType<typeof defineConfig> = {
76
76
  */
77
77
  layout: false,
78
78
 
79
+ /**
80
+ * @name 国际化插件(与 layout 一致;intl 未命中 key 时读 src/locales)
81
+ * @doc https://umijs.org/docs/max/i18n
82
+ */
83
+ locale: {
84
+ default: 'zh-CN',
85
+ antd: false,
86
+ baseNavigator: false,
87
+ },
88
+
79
89
  /**
80
90
  * @name 其他配置
81
91
  */
@@ -0,0 +1,35 @@
1
+ # PermissionFilter 按钮权限(子应用)
2
+
3
+ > 创建时间:2025-03-27
4
+
5
+ ## 说明
6
+
7
+ 子应用内 `PermissionFilter` 与主应用**同名同 Props**,但权限数据来源不同:通过 qiankun 接收主应用注入的 `button_perms`、`is_superuser`,详见 `src/common/mainApp.ts` 与 `useMainAppProps()`。
8
+
9
+ **完整架构、主应用用法、Mock 与 `getInitialState` 说明**请阅读主应用文档:
10
+
11
+ `apps/layout/docs/feature-PermissionFilter按钮权限.md`
12
+
13
+ ## 子应用要点
14
+
15
+ | 项目 | 说明 |
16
+ | --- | --- |
17
+ | 权限来源 | 主应用 `MicroAppLoader` 传入的 props,非子应用自行请求 `/user/info/` |
18
+ | Hook | `useMainAppProps()`,props 变更时组件会重渲染 |
19
+ | 独立运行 | 无主应用 props,无 `button_perms`,默认走 `fallback` |
20
+ | 示例 | `src/pages/index.tsx` 中「PermissionFilter 按钮权限示例」卡片 |
21
+
22
+ ## 使用示例
23
+
24
+ ```tsx
25
+ import PermissionFilter from '@/components/PermissionFilter';
26
+
27
+ <PermissionFilter
28
+ permissionKey="cs_web_btn_subapp_demo"
29
+ fallback={<Alert type="warning" title="无权限" content="..." />}
30
+ >
31
+ <Button type="primary">有权限时可见</Button>
32
+ </PermissionFilter>
33
+ ```
34
+
35
+ 主应用需在用户信息接口返回的 `button_perms` 中包含与页面一致的 `permissionKey`;本地联调可修改主应用 `mock/api.mock.ts`。
@@ -0,0 +1,124 @@
1
+ # 国际化(common-intl 与 Umi 兜底)
2
+
3
+ > 创建时间:2026-04-07
4
+
5
+ ## 功能概述
6
+
7
+ 子应用通过 **`<%= packageScope %>/common-intl`**(生产环境常与主应用 **externals 共享**)拉取中台文案;**未命中 key** 时回落到 **Umi `src/locales`**。可选在工程内配置 **独立 `initIntl`**(独立 tag / IndexedDB),与主应用默认实例隔离。语言与主应用对齐:qiankun 下优先主应用注入的 `locale`,否则与 layout 相同的 URL / `umi_locale` / 浏览器规则(见 `getSubappCurrentLocale`)。
8
+
9
+ ## 技术方案
10
+
11
+ ### 技术栈
12
+
13
+ - Umi Max `locale` 插件 + `src/locales/zh-CN.ts`、`en-US.ts`
14
+ - `<%= packageScope %>/common-intl`:`configureLocale`、`i18n`、`fetchMultilingualData`、`initIntl`(独立实例时)
15
+ - 统一入口:`useSubappIntl`(合并逻辑对齐主应用 `useLayoutIntl`)
16
+
17
+ ### 核心实现
18
+
19
+ 1. **`app.tsx`**:最早调用 `configureLocale`,使包内 `getLocale`/`setLocale` 与 `getSubappCurrentLocale` 一致;导出 Umi 运行时 `locale.getLocale`;在 `render` 中按条件调用中台 `fetchMultilingualData`(默认 tag 与可选的独立 intl 实例并行)。
20
+ 2. **`useSubappIntl`**:若 `subappIntlConfig` 有效则优先 **独立实例** `i18n`;否则在未启用中台时仅用 Umi,启用时优先包内 **`i18n`**;与 `defaultMessage` 比较后决定是否采用 Umi 译文。
21
+ 3. **`subappIntlConfig.ts`**:默认返回 `undefined`;返回含 **`tag`、`app_name`** 的对象即启用独立 intl(见 `subappOwnIntl.ts` 内 `initIntl`)。
22
+
23
+ ## 文件清单
24
+
25
+ ### 新增 / 模块目录
26
+
27
+ | 文件路径 | 说明 |
28
+ | --- | --- |
29
+ | `src/common/intl/subappIntlConfig.ts` | 独立 intl 配置入口 `getSubappIntlInitOptions()` |
30
+ | `src/common/intl/subappOwnIntl.ts` | 条件 `initIntl`、独立 `fetchMultilingualData` |
31
+ | `src/common/intl/useSubappIntl.ts` | 页面统一 Hook |
32
+ | `src/common/intl/intlRuntime.ts` | `isCommonIntlEnabled`(`window.__MICO_CONFIG__.intl` / `commonIntl`) |
33
+ | `src/common/intl/types.ts` | `IMicoConfigCommonIntl` |
34
+ | `src/common/intl/index.ts` | 对外导出 |
35
+ | `src/common/intl/localeMapping.ts` | Umi locale ↔ `ILang`(模板已存在) |
36
+ | `src/common/intl/subappLocale.ts` | 当前 Umi 风格 locale(模板已存在) |
37
+ | `src/locales/zh-CN.ts` | Umi 兜底文案 |
38
+ | `src/locales/en-US.ts` | Umi 兜底文案 |
39
+
40
+ ### 修改
41
+
42
+ | 文件路径 | 说明 |
43
+ | --- | --- |
44
+ | `config/config.ts` | 开启 `locale` 插件 |
45
+ | `src/app.tsx` | `configureLocale`、`export const locale`、`render` 预拉取 |
46
+ | `src/pages/index.tsx` | 国际化示例区(可选参考) |
47
+ | `typings.d.ts` | `__MICO_CONFIG__.intl` / `commonIntl` 类型 |
48
+
49
+ ## API / 组件接口
50
+
51
+ ### `useSubappIntl`
52
+
53
+ ```typescript
54
+ const {
55
+ formatMessage, // 与 layout useLayoutIntl 相同签名
56
+ commonIntlEnabled, // 是否启用中台(有效 tag + app_name)
57
+ locale, // 当前 Umi 风格 locale 字符串
58
+ subappOwnIntl, // 是否启用子应用独立 initIntl
59
+ subappOwnIntlTag, // 独立 tag(未启用时为 undefined)
60
+ } = useSubappIntl();
61
+ ```
62
+
63
+ **参数说明**:无入参;`formatMessage` 第二参数为插值对象,与 `useIntl().formatMessage` 一致。
64
+
65
+ ### `getSubappIntlInitOptions`
66
+
67
+ 返回 `undefined` 表示使用包默认实例;返回 `ISubappIntlInitOptions` 时需至少包含 **`tag`**、**`app_name`**,可选 **`indexedDBParams`**(与 `packages/common-intl` README 一致)。
68
+
69
+ ## 使用示例
70
+
71
+ ```tsx
72
+ import { useSubappIntl } from '@/common/intl';
73
+
74
+ export default function Page() {
75
+ const { formatMessage, commonIntlEnabled, locale, subappOwnIntl, subappOwnIntlTag } =
76
+ useSubappIntl();
77
+
78
+ return (
79
+ <p>
80
+ {formatMessage(
81
+ { id: 'page.subapp.home.title', defaultMessage: '标题' },
82
+ )}
83
+ </p>
84
+ );
85
+ }
86
+ ```
87
+
88
+ 独立 intl(节选,`subappIntlConfig.ts`):
89
+
90
+ ```typescript
91
+ export function getSubappIntlInitOptions() {
92
+ return {
93
+ tag: 'my_subapp_tag',
94
+ app_name: 'middle',
95
+ indexedDBParams: { dbName: 'my_subapp_i18n_db' },
96
+ };
97
+ }
98
+ ```
99
+
100
+ 启用独立 tag 且生产仍使用 `externals` 指向主应用 `CommonIntl` 时,需评估与上游 **多实例 / 单例** 语义是否冲突,详见 **`generators/micro-react/templates/packages/common-intl/README.md`**「不同 tag」。
101
+
102
+ ## 设计决策
103
+
104
+ | 决策点 | 选择 | 理由 |
105
+ | --- | --- | --- |
106
+ | 统一 Hook | `useSubappIntl` 对齐 `useLayoutIntl` | 与主应用行为一致,intl 与 Umi 兜底规则可维护 |
107
+ | 独立 intl 配置 | 单一入口 `getSubappIntlInitOptions` | 默认关闭,按需开启,避免误初始化 |
108
+ | 预拉取 | `render` 内并行 fetch 默认 tag + 独立实例 | 首屏前尽量拉齐中台数据,失败仍渲染 |
109
+
110
+ ## 已知限制与待改进
111
+
112
+ - 独立 intl 与 **externals 共享同一 `common-intl` 运行时** 时的边界行为以 **上游包** 为准;若需完全隔离,可能需调整打包策略(见 common-intl README)。
113
+ - `LOCALE_REQUEST_URL` 未配置时,行为依赖上游 `fetchMultilingualData` 实现。
114
+
115
+ ## 注意事项
116
+
117
+ - 主应用通过 qiankun 注入的 **`locale`** 会参与 `getSubappCurrentLocale`;独立运行依赖本地存储与 URL。
118
+ - 中台未配置时 **`commonIntlEnabled`** 为 `false`,页面仅走 Umi `locales`。
119
+
120
+ ## 相关文档
121
+
122
+ - [packages/common-intl README](../../../../micro-react/templates/packages/common-intl/README.md)
123
+ - [子应用国际化(CLI 仓库总览)](../../../../../docs/feat-subapp-国际化翻译.md)
124
+ - [Layout 国际化](../../../../../docs/feat-layout-国际化翻译.md)
@@ -19,7 +19,9 @@
19
19
  "@mico-platform/theme": "<%= themeVersion %>",
20
20
  "@umijs/max": "^4.4.8",
21
21
  "react": "^18.2.0",
22
- "react-dom": "^18.2.0"
22
+ "react-dom": "^18.2.0",
23
+ "<%= packageScope %>/common-intl": "workspace:*",
24
+ "<%= packageScope %>/shared": "workspace:*"
23
25
  },
24
26
  "devDependencies": {
25
27
  "@mico-platform/ui": "<%= micoUiVersion %>",