generator-mico-cli 0.2.21 → 0.2.22

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 (41) hide show
  1. package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +14 -4
  2. package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +36 -26
  3. package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +5 -2
  4. package/generators/micro-react/templates/CLAUDE.md +4 -2
  5. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +7 -3
  6. package/generators/micro-react/templates/apps/layout/config/routes.ts +0 -5
  7. 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 +5 -2
  8. 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 +107 -48
  9. 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 +179 -0
  10. package/generators/micro-react/templates/apps/layout/mock/menus.ts +89 -144
  11. package/generators/micro-react/templates/apps/layout/mock/pages.ts +83 -0
  12. package/generators/micro-react/templates/apps/layout/src/app.tsx +10 -8
  13. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +118 -43
  14. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +31 -4
  15. package/generators/micro-react/templates/apps/layout/src/common/micro-prefetch.ts +3 -2
  16. package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +45 -0
  17. package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +49 -10
  18. package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +1 -1
  19. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +6 -0
  20. package/generators/micro-react/templates/apps/layout/src/common/theme.ts +0 -2
  21. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.less +0 -1
  22. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +4 -4
  23. package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +4 -5
  24. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +20 -1
  25. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +4 -3
  26. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/micro-app-manager.ts +7 -1
  27. package/generators/micro-react/templates/apps/layout/src/global.less +15 -2
  28. package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +3 -2
  29. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +30 -3
  30. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +15 -4
  31. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +75 -38
  32. package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +3 -7
  33. package/generators/micro-react/templates/apps/layout/src/services/user.ts +2 -2
  34. package/generators/micro-react/templates/dev.preset.json +1 -1
  35. package/generators/subapp-react/index.js +160 -2
  36. package/generators/subapp-react/templates/homepage/.env +2 -1
  37. package/generators/subapp-react/templates/homepage/config/config.dev.ts +1 -0
  38. package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +2 -1
  39. package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +2 -1
  40. package/generators/subapp-react/templates/homepage/config/config.prod.ts +2 -1
  41. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  import type { MenuItem, ParsedMenuItem, ParsedRoute } from '@/common/menu';
2
- import { extractRoutes, getWindowMenus, parseMenuItems } from '@/common/menu';
2
+ import { extractRoutes, parseMenuItems } from '@/common/menu';
3
+ import { getMenus } from '@/common/portal-data';
3
4
  import { useMemo } from 'react';
4
5
 
5
6
  /**
@@ -8,7 +9,7 @@ import { useMemo } from 'react';
8
9
  export const useMenu = () => {
9
10
  // 获取原始菜单数据
10
11
  const rawMenus = useMemo<MenuItem[]>(() => {
11
- return getWindowMenus();
12
+ return getMenus();
12
13
  }, []);
13
14
 
14
15
  // 解析后的菜单项(用于渲染菜单组件)
@@ -9,8 +9,10 @@
9
9
  }
10
10
  }
11
11
 
12
- .layout-menu {
12
+ .layout-menu.arco-menu-dark,
13
+ .layout-menu.arco-menu {
13
14
  font-size: @font-size-base;
15
+ background-color: @color-text-5;
14
16
 
15
17
  // Base menu item styles
16
18
  .arco-menu-item {
@@ -48,7 +50,7 @@
48
50
  }
49
51
 
50
52
  .arco-menu-indent {
51
- width: 32px;
53
+ width: 12px;
52
54
  }
53
55
 
54
56
  .layout-menu-level-3 {
@@ -135,8 +137,9 @@
135
137
  // Sider styles
136
138
  .<%= projectName %>-sider {
137
139
  box-shadow: none;
140
+ background-color: @color-text-5 !important;
138
141
  position: fixed !important;
139
- height: calc(100vh - 56px) !important;
142
+ height: calc(100vh - @header-height) !important;
140
143
 
141
144
  .arco-layout-sider-trigger {
142
145
  display: flex;
@@ -148,6 +151,10 @@
148
151
  .click-trigger-btn {
149
152
  cursor: pointer;
150
153
  display: flex;
154
+ width: 24px;
155
+ height: 24px;
156
+ align-items: center;
157
+ justify-content: center;
151
158
 
152
159
  // 图标颜色适配主题
153
160
  .arco-icon {
@@ -163,4 +170,24 @@
163
170
  .click-trigger-btn.collapsed {
164
171
  justify-content: center;
165
172
  }
173
+
174
+ .arco-menu-collapse .arco-menu-item .arco-icon,
175
+ .arco-menu-collapse .arco-menu-pop-header .arco-icon {
176
+ margin-left: 0;
177
+ }
178
+
179
+ .arco-menu .arco-menu-item,
180
+ .arco-menu .arco-menu-group-title,
181
+ .arco-menu .arco-menu-pop-header,
182
+ .arco-menu .arco-menu-inline-header .arco-menu-dark .arco-menu-item,
183
+ .arco-menu-dark .arco-menu-group-title,
184
+ .arco-menu-dark .arco-menu-pop-header,
185
+ .arco-menu-dark .arco-menu-inline-header {
186
+ background-color: @color-text-5;
187
+ color: @color-text-2;
188
+
189
+ &.arco-menu-selected {
190
+ color: @Brand1-6;
191
+ }
192
+ }
166
193
  }
@@ -1,7 +1,8 @@
1
1
  import type { ParsedMenuItem } from '@/common/menu';
2
- import { filterMenuItems, getWindowMenus, parseMenuItems } from '@/common/menu';
3
- import { isAuthDisabled, isNoPermissionRoute } from '@/constants';
2
+ import { filterMenuItems, parseMenuItems } from '@/common/menu';
3
+ import { getMenus } from '@/common/portal-data';
4
4
  import IconFont from '@/components/IconFont';
5
+ import { isAuthDisabled, isNoPermissionRoute } from '@/constants';
5
6
  import { useMenuState } from '@/hooks/useMenuState';
6
7
  import { useTheme } from '@/hooks/useTheme';
7
8
  import { Layout, Menu } from '@mico-platform/ui';
@@ -30,6 +31,16 @@ const ICON_ALIAS_MAP: Record<string, keyof typeof Icons> = {
30
31
  const getIconComponent = (iconName: string): React.ReactNode => {
31
32
  if (!iconName) return null;
32
33
 
34
+ // 从中台配置的 icon 是有 Icon 前缀的。类似 IconMenu 这种
35
+ const directIconKey = iconName as keyof typeof Icons;
36
+ const DirectIconComponent = Icons[directIconKey] as
37
+ | React.ComponentType
38
+ | undefined;
39
+
40
+ if (DirectIconComponent) {
41
+ return <DirectIconComponent />;
42
+ }
43
+
33
44
  // 尝试直接匹配 Icon${name} 格式
34
45
  const iconKey = `Icon${iconName}` as keyof typeof Icons;
35
46
  const IconComponent = Icons[iconKey] as React.ComponentType | undefined;
@@ -112,7 +123,7 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
112
123
  // Parse menu data
113
124
  // disableAuth 或免权限校验路由时不过滤,显示全部菜单
114
125
  const menuItems = useMemo(() => {
115
- const menus = getWindowMenus();
126
+ const menus = getMenus();
116
127
  if (isAuthDisabled() || isNoPermissionRoute(location.pathname)) {
117
128
  return parseMenuItems(menus);
118
129
  }
@@ -166,7 +177,7 @@ const LayoutMenu: React.FC<LayoutMenuProps> = () => {
166
177
  onCollapse={handleCollapsed}
167
178
  collapsible
168
179
  breakpoint="xl"
169
- className="<%= projectName %>-sider"
180
+ className="common-web-sider"
170
181
  trigger={clickTriggerBtn}
171
182
  theme={isDark ? 'dark' : 'light'}
172
183
  >
@@ -2,9 +2,12 @@ import { layoutLogger } from '@/common/logger';
2
2
  import {
3
3
  extractRoutes,
4
4
  filterMenuItems,
5
+ findPageByPath,
5
6
  findRouteByPath,
6
- getWindowMenus,
7
+ getDynamicRoutes,
8
+ isSuperuserUser,
7
9
  } from '@/common/menu';
10
+ import { getMenus, getPages, hasPages } from '@/common/portal-data';
8
11
  import { getAppNameFromEntry } from '@/common/micro';
9
12
  import AppTabs from '@/components/AppTabs';
10
13
  import MicroAppLoader from '@/components/MicroAppLoader';
@@ -46,57 +49,89 @@ const BasicLayout: React.FC = () => {
46
49
  [currentUser?.is_superuser, currentUser?.side_menus],
47
50
  );
48
51
 
49
- // 所有路由(不过滤权限,用于判断路径是否存在)
50
- const allRoutes = useMemo(() => {
51
- const menus = getWindowMenus();
52
- return extractRoutes(menus);
52
+ // 所有页面路由(优先 PAGES,降级 MENUS)— 用于路由匹配和渲染
53
+ const allPageRoutes = useMemo(() => {
54
+ return getDynamicRoutes();
53
55
  }, []);
54
56
 
55
- // 有权限的路由(disableAuth 时不过滤,显示全部)
56
- const allowedRoutes = useMemo(() => {
57
+ // 菜单路由(从 MENUS)— 用于权限交叉引用
58
+ const allMenuRoutes = useMemo(() => {
59
+ return extractRoutes(getMenus());
60
+ }, []);
61
+
62
+ const allowedMenuRoutes = useMemo(() => {
57
63
  if (isAuthDisabled()) {
58
- return allRoutes;
64
+ return allMenuRoutes;
59
65
  }
60
- const menus = getWindowMenus();
61
- const filteredMenus = filterMenuItems(menus, filterOptions);
66
+ const filteredMenus = filterMenuItems(getMenus(), filterOptions);
62
67
  return extractRoutes(filteredMenus);
63
- }, [filterOptions, allRoutes]);
68
+ }, [filterOptions, allMenuRoutes]);
64
69
 
65
- // 查找当前路由配置(优先从有权限的路由中查找)
70
+ // 当前路由配置(从所有页面路由中查找)
66
71
  const currentRoute = useMemo(() => {
67
- return findRouteByPath(allowedRoutes, location.pathname);
68
- }, [allowedRoutes, location.pathname]);
72
+ return findRouteByPath(allPageRoutes, location.pathname);
73
+ }, [allPageRoutes, location.pathname]);
69
74
 
70
- // 判断是否是动态路由但无权限(disableAuth 时始终返回 false)
75
+ // 权限判断:菜单交叉引用 + 隐藏页面级兜底
71
76
  const isForbidden = useMemo(() => {
72
- // 关闭权限控制时,不校验权限
73
77
  if (isAuthDisabled()) return false;
74
- // 免权限校验路由,不检查菜单权限
75
78
  if (isNoPermissionRoute(location.pathname)) return false;
76
- // 如果在有权限的路由中找到了,说明有权限
77
- if (currentRoute) return false;
78
- // 如果在所有路由中也找不到,说明不是动态路由,交给 Umi 处理
79
- const routeInAll = findRouteByPath(allRoutes, location.pathname);
80
- // 在所有路由中存在,但在有权限的路由中不存在 无权限
81
- const forbidden = !!routeInAll;
82
-
83
- layoutLogger.log('isForbidden check:', {
84
- pathname: location.pathname,
85
- currentRoute,
86
- routeInAll,
87
- forbidden,
88
- allRoutesCount: allRoutes.length,
89
- allowedRoutesCount: allowedRoutes.length,
90
- filterOptions,
91
- });
79
+ // 非动态路由,交给 Umi 处理(404 等)
80
+ if (!currentRoute) return false;
81
+ if (isSuperuserUser(currentUser?.is_superuser)) return false;
82
+
83
+ // Tier 1: 菜单权限交叉引用
84
+ const inAllMenu = findRouteByPath(allMenuRoutes, location.pathname);
85
+ if (inAllMenu) {
86
+ const inAllowed = findRouteByPath(allowedMenuRoutes, location.pathname);
87
+ const forbidden = !inAllowed;
88
+
89
+ layoutLogger.log('isForbidden (menu check):', {
90
+ pathname: location.pathname,
91
+ inAllMenu: true,
92
+ inAllowed: !!inAllowed,
93
+ forbidden,
94
+ });
95
+
96
+ return forbidden;
97
+ }
98
+
99
+ // Tier 2: 隐藏页面级权限(仅在 PAGES 数据可用时生效)
100
+ if (!hasPages()) return false;
101
+
102
+ const page = findPageByPath(getPages(), location.pathname);
103
+ if (!page) return false;
104
+
105
+ if (page.adminOnly) {
106
+ layoutLogger.log('isForbidden (hidden page adminOnly):', {
107
+ pathname: location.pathname,
108
+ pageId: page.id,
109
+ });
110
+ return true;
111
+ }
112
+
113
+ if (page.accessControlEnabled) {
114
+ const sideMenus = (currentUser?.side_menus || []) as string[];
115
+ const forbidden = !page.routeKey || !sideMenus.includes(page.routeKey);
116
+
117
+ layoutLogger.log('isForbidden (hidden page accessControl):', {
118
+ pathname: location.pathname,
119
+ pageId: page.id,
120
+ routeKey: page.routeKey,
121
+ hasSideMenuMatch: !forbidden,
122
+ });
123
+
124
+ return forbidden;
125
+ }
92
126
 
93
- return forbidden;
127
+ return false;
94
128
  }, [
95
129
  currentRoute,
96
- allRoutes,
97
- allowedRoutes,
130
+ allMenuRoutes,
131
+ allowedMenuRoutes,
98
132
  location.pathname,
99
- filterOptions,
133
+ currentUser?.is_superuser,
134
+ currentUser?.side_menus,
100
135
  ]);
101
136
 
102
137
  // 判断是否需要显示布局
@@ -195,7 +230,9 @@ const BasicLayout: React.FC = () => {
195
230
  <AppTabs />
196
231
  <Content className="layout-content-outlet">
197
232
  <Suspense
198
- fallback={<Spin dot style={{ width: '100%', marginTop: 100 }} />}
233
+ fallback={
234
+ <Spin dot style={{ width: '100%', marginTop: 100 }} />
235
+ }
199
236
  >
200
237
  {renderContent()}
201
238
  </Suspense>
@@ -1,9 +1,5 @@
1
- import {
2
- extractRoutes,
3
- filterMenuItems,
4
- getWindowMenus,
5
- type MenuFilterOptions,
6
- } from '@/common/menu';
1
+ import { extractRoutes, filterMenuItems, type MenuFilterOptions } from '@/common/menu';
2
+ import { getMenus } from '@/common/portal-data';
7
3
  import { isAuthDisabled } from '@/constants';
8
4
  import { Button, Result, Space } from '@mico-platform/ui';
9
5
  import { history, useModel } from '@umijs/max';
@@ -13,7 +9,7 @@ import React, { useMemo } from 'react';
13
9
  * 获取第一个可访问的路由路径
14
10
  */
15
11
  const getFirstAvailablePath = (filterOptions: MenuFilterOptions): string => {
16
- const menus = getWindowMenus();
12
+ const menus = getMenus();
17
13
 
18
14
  // 根据权限过滤菜单
19
15
  const filteredMenus = isAuthDisabled()
@@ -10,8 +10,8 @@ const USER_INFO_API = '/api/user/info';
10
10
  * - en: 2 (英文)
11
11
  */
12
12
  const LOCALE_TO_LANG: Partial<Record<SupportedLocale, number>> = {
13
- zh_CN: 1,
14
- en: 2,
13
+ 'zh-CN': 1,
14
+ 'en-US': 2,
15
15
  };
16
16
 
17
17
  /**
@@ -10,5 +10,5 @@
10
10
  "apps": ["layout"]
11
11
  }
12
12
  },
13
- "default": "minimal"
13
+ "default": "full"
14
14
  }
@@ -118,12 +118,26 @@ module.exports = class extends Generator {
118
118
  if (!input.startsWith('@')) return 'Package scope must start with @';
119
119
  return true;
120
120
  }
121
+ },
122
+ {
123
+ type: 'input',
124
+ name: 'devPort',
125
+ message: 'Dev server port',
126
+ default: rc.devPort || '8010',
127
+ validate: (input) => {
128
+ const port = Number(input);
129
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) {
130
+ return 'Port must be an integer between 1024 and 65535';
131
+ }
132
+ return true;
133
+ }
121
134
  }
122
135
  ]);
123
136
 
124
137
  this.appName = toKebab(this.answers.appName);
125
138
  this.appNamePascal = toPascal(this.appName);
126
139
  this.packageScope = this.answers.packageScope;
140
+ this.devPort = this.answers.devPort;
127
141
  this.templateDir = this.templatePath('homepage');
128
142
  this.destDir = path.join(this.monorepoRoot, 'apps', this.appName);
129
143
  } catch (error) {
@@ -172,6 +186,7 @@ module.exports = class extends Generator {
172
186
  appName: this.appName,
173
187
  AppName: this.appNamePascal,
174
188
  packageScope: this.packageScope,
189
+ devPort: this.devPort,
175
190
  micoUiVersion: `^${micoUiVer}`,
176
191
  themeVersion: `^${themeVer}`,
177
192
  micoUiVersionExact: micoUiVer
@@ -206,7 +221,7 @@ module.exports = class extends Generator {
206
221
 
207
222
  for (const relPath of files) {
208
223
  const destRelPath = transformDestPath(relPath);
209
- const isTemplate = isTemplateFile(relPath);
224
+ const isTemplate = isTemplateFile(relPath) || path.basename(relPath) === '.env';
210
225
  const tag = isTemplate ? '\x1b[32m[tpl]\x1b[0m' : '\x1b[36m[cpy]\x1b[0m';
211
226
  this.log(` ${tag} apps/${this.appName}/${destRelPath}`);
212
227
 
@@ -243,7 +258,8 @@ module.exports = class extends Generator {
243
258
  const destRelPath = transformDestPath(relPath);
244
259
  const destPath = path.join(this.destDir, destRelPath);
245
260
 
246
- if (isTemplateFile(relPath)) {
261
+ const isEnvFile = path.basename(relPath) === '.env';
262
+ if (isTemplateFile(relPath) || isEnvFile) {
247
263
  this.logger.file('template', destRelPath);
248
264
  this.fs.copyTpl(srcPath, destPath, templateData);
249
265
  templateCount++;
@@ -255,6 +271,10 @@ module.exports = class extends Generator {
255
271
  }
256
272
 
257
273
  this.logger.verbose(`Processed: ${templateCount} templates, ${copyCount} copied`);
274
+
275
+ this._updateDevPreset();
276
+ this._updateMockPages();
277
+ this._updateConfigDev();
258
278
  } catch (error) {
259
279
  console.error('');
260
280
  console.error('❌ Error during file generation:');
@@ -264,6 +284,144 @@ module.exports = class extends Generator {
264
284
  }
265
285
  }
266
286
 
287
+ _updateDevPreset() {
288
+ const presetPath = path.join(this.monorepoRoot, 'dev.preset.json');
289
+ if (!fs.existsSync(presetPath)) {
290
+ this.logger.verbose('dev.preset.json not found, skipping preset update');
291
+ return;
292
+ }
293
+
294
+ try {
295
+ const preset = JSON.parse(fs.readFileSync(presetPath, 'utf-8'));
296
+ const fullApps = preset.presets?.full?.apps;
297
+
298
+ if (!Array.isArray(fullApps)) {
299
+ this.logger.verbose('dev.preset.json has no full.apps array, skipping');
300
+ return;
301
+ }
302
+
303
+ if (fullApps.includes(this.appName)) {
304
+ this.logger.verbose(`"${this.appName}" already in full preset, skipping`);
305
+ return;
306
+ }
307
+
308
+ fullApps.push(this.appName);
309
+ fs.writeFileSync(presetPath, `${JSON.stringify(preset, null, 2)}\n`, 'utf-8');
310
+
311
+ this.log(` 📝 已将 "${this.appName}" 添加到 dev.preset.json 的 full 预设中`);
312
+ } catch (e) {
313
+ console.warn(` ⚠️ 更新 dev.preset.json 失败: ${e.message}`);
314
+ }
315
+ }
316
+
317
+ _updateMockPages() {
318
+ const pagesPath = path.join(this.monorepoRoot, 'apps/layout/mock/pages.ts');
319
+ if (!fs.existsSync(pagesPath)) {
320
+ this.logger.verbose('apps/layout/mock/pages.ts not found, skipping');
321
+ return;
322
+ }
323
+
324
+ try {
325
+ const content = fs.readFileSync(pagesPath, 'utf-8');
326
+
327
+ if (content.includes(`route: '/${this.appName}'`)) {
328
+ this.logger.verbose(`Route "/${this.appName}" already in mock pages, skipping`);
329
+ return;
330
+ }
331
+
332
+ // 找出已有的最大 id
333
+ const idMatches = [...content.matchAll(/id:\s*(\d+)/g)];
334
+ const maxId = idMatches.reduce((max, m) => Math.max(max, Number(m[1])), 0);
335
+
336
+ const newEntry = [
337
+ ' {',
338
+ ` id: ${maxId + 1},`,
339
+ ` name: '${this.appNamePascal}',`,
340
+ ` nameEn: '${this.appNamePascal}',`,
341
+ ` nameKey: 'page.${this.appName}',`,
342
+ ` route: '/${this.appName}',`,
343
+ ` htmlUrl: '//localhost:${this.devPort}',`,
344
+ ' jsUrls: [],',
345
+ ' cssUrls: [],',
346
+ " prefixPath: '',",
347
+ " routeMode: 'prefix',",
348
+ ' enabled: true,',
349
+ ' accessControlEnabled: false,',
350
+ ' adminOnly: false,',
351
+ ' routeKey: null,',
352
+ ` mainDocumentId: ${Math.floor(Math.random() * 900) + 100},`,
353
+ " version: '',",
354
+ ' },',
355
+ ].join('\n');
356
+
357
+ // 插入到兜底路由 (route: '/*') 之前
358
+ const lines = content.split('\n');
359
+ let insertIdx = -1;
360
+
361
+ for (let i = 0; i < lines.length; i++) {
362
+ if (lines[i].includes("route: '/*'")) {
363
+ for (let j = i - 1; j >= 0; j--) {
364
+ if (lines[j].trim() === '{') {
365
+ insertIdx = j;
366
+ break;
367
+ }
368
+ }
369
+ break;
370
+ }
371
+ }
372
+
373
+ if (insertIdx === -1) {
374
+ const closingIdx = lines.findIndex((l) => l.trim() === '];');
375
+ if (closingIdx > 0) insertIdx = closingIdx;
376
+ }
377
+
378
+ if (insertIdx > 0) {
379
+ lines.splice(insertIdx, 0, newEntry);
380
+ fs.writeFileSync(pagesPath, lines.join('\n'), 'utf-8');
381
+ this.log(` 📝 已将 "${this.appName}" 的页面配置添加到 mock/pages.ts`);
382
+ }
383
+ } catch (e) {
384
+ console.warn(` ⚠️ 更新 mock/pages.ts 失败: ${e.message}`);
385
+ }
386
+ }
387
+
388
+ _updateConfigDev() {
389
+ const configPath = path.join(this.monorepoRoot, 'apps/layout/config/config.dev.ts');
390
+ if (!fs.existsSync(configPath)) {
391
+ this.logger.verbose('apps/layout/config/config.dev.ts not found, skipping');
392
+ return;
393
+ }
394
+
395
+ try {
396
+ const content = fs.readFileSync(configPath, 'utf-8');
397
+ const routeEntry = `'/${this.appName}/*'`;
398
+
399
+ if (content.includes(routeEntry)) {
400
+ this.logger.verbose(`Route "${routeEntry}" already in noPermissionRouteList, skipping`);
401
+ return;
402
+ }
403
+
404
+ const updated = content.replace(
405
+ /noPermissionRouteList:\s*\[([^\]]*)\]/,
406
+ (match, inner) => {
407
+ const trimmed = inner.trim();
408
+ const entries = trimmed ? `${trimmed}, ${routeEntry}` : routeEntry;
409
+ return `noPermissionRouteList: [${entries}]`;
410
+ },
411
+ );
412
+
413
+ if (updated === content) {
414
+ this.logger.verbose('noPermissionRouteList not found in config.dev.ts, skipping');
415
+ return;
416
+ }
417
+
418
+ fs.writeFileSync(configPath, updated, 'utf-8');
419
+ this.log(` 📝 已将 "${routeEntry}" 添加到 config.dev.ts 的 noPermissionRouteList 中`);
420
+ } catch (e) {
421
+ console.warn(` ⚠️ 更新 config.dev.ts 失败: ${e.message}`);
422
+ }
423
+ }
424
+
267
425
  install() {
268
426
  // 跳过 dry-run 模式
269
427
  if (this._skipInstall) return;
@@ -1,4 +1,5 @@
1
1
  # Default: 5
2
2
  CHECK_TIMEOUT=10
3
3
  DID_YOU_KNOW=none
4
- PORT=8010
4
+ PORT=<%= devPort %>
5
+ SOCKET_SERVER=http://localhost:<%= devPort %>
@@ -50,6 +50,7 @@ const config: ReturnType<typeof defineConfig> = {
50
50
  // externals: {
51
51
  // react: 'window.React',
52
52
  // 'react-dom': 'window.ReactDOM',
53
+ // 'react-dom/client': 'window.ReactDOMClient',
53
54
  // '@mico-platform/ui': 'window.micoUI',
54
55
  // },
55
56
  /**
@@ -21,12 +21,13 @@ const config: ReturnType<typeof defineConfig> = {
21
21
  * @doc https://umijs.org/docs/api/config#externals
22
22
  *
23
23
  * 作为 qiankun 子应用时,这些库由主应用提供:
24
- * - react / react-dom: 主应用已加载,避免多实例问题
24
+ * - react / react-dom / react-dom/client: 主应用已加载,避免多实例问题
25
25
  * - @mico-platform/ui: 主应用已加载,复用组件和样式
26
26
  */
27
27
  externals: {
28
28
  react: 'React',
29
29
  'react-dom': 'ReactDOM',
30
+ 'react-dom/client': 'ReactDOMClient',
30
31
  '@mico-platform/ui': 'micoUI',
31
32
  },
32
33
  };
@@ -21,12 +21,13 @@ const config: ReturnType<typeof defineConfig> = {
21
21
  * @doc https://umijs.org/docs/api/config#externals
22
22
  *
23
23
  * 作为 qiankun 子应用时,这些库由主应用提供:
24
- * - react / react-dom: 主应用已加载,避免多实例问题
24
+ * - react / react-dom / react-dom/client: 主应用已加载,避免多实例问题
25
25
  * - @mico-platform/ui: 主应用已加载,复用组件和样式
26
26
  */
27
27
  externals: {
28
28
  react: 'React',
29
29
  'react-dom': 'ReactDOM',
30
+ 'react-dom/client': 'ReactDOMClient',
30
31
  '@mico-platform/ui': 'micoUI',
31
32
  },
32
33
  };
@@ -27,12 +27,13 @@ const config: ReturnType<typeof defineConfig> = {
27
27
  * @doc https://umijs.org/docs/api/config#externals
28
28
  *
29
29
  * 作为 qiankun 子应用时,这些库由主应用提供:
30
- * - react / react-dom: 主应用已加载,避免多实例问题
30
+ * - react / react-dom / react-dom/client: 主应用已加载,避免多实例问题
31
31
  * - @mico-platform/ui: 主应用已加载,复用组件和样式
32
32
  */
33
33
  externals: {
34
34
  react: 'React',
35
35
  'react-dom': 'ReactDOM',
36
+ 'react-dom/client': 'ReactDOMClient',
36
37
  '@mico-platform/ui': 'micoUI',
37
38
  },
38
39
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-mico-cli",
3
- "version": "0.2.21",
3
+ "version": "0.2.22",
4
4
  "description": "Yeoman generator for Mico CLI projects",
5
5
  "keywords": ["yeoman-generator", "generator", "cli"],
6
6
  "license": "MIT",