generator-mico-cli 0.2.27 → 0.2.29

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 (58) hide show
  1. package/README.md +5 -20
  2. package/bin/mico.js +27 -62
  3. package/generators/micro-react/index.js +8 -0
  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/CLAUDE.md +1 -0
  7. package/generators/micro-react/templates/README.md +1 -1
  8. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +7 -4
  9. package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +2 -2
  10. package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +2 -2
  11. package/generators/micro-react/templates/apps/layout/config/config.prod.ts +3 -3
  12. 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 +44 -0
  13. 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
  14. 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 +11 -6
  15. 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
  16. 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
  17. 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
  18. package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +23 -31
  19. package/generators/micro-react/templates/apps/layout/mock/pages.ts +5 -6
  20. package/generators/micro-react/templates/apps/layout/package.json +2 -1
  21. package/generators/micro-react/templates/apps/layout/src/app.tsx +31 -2
  22. package/generators/micro-react/templates/apps/layout/src/common/auth/type.ts +15 -27
  23. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +148 -85
  24. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +2 -6
  25. package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +46 -2
  26. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +5 -1
  27. package/generators/micro-react/templates/apps/layout/src/components/PermissionFilter/index.tsx +51 -0
  28. package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +10 -1
  29. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +3 -3
  30. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +105 -60
  31. package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +17 -0
  32. package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +16 -0
  33. package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +7 -3
  34. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +5 -0
  35. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +49 -1
  36. package/generators/micro-react/templates/apps/layout/src/services/user.ts +28 -21
  37. package/generators/micro-react/templates/packages/common-intl/README.md +77 -369
  38. package/generators/micro-react/templates/packages/common-intl/package.json +3 -13
  39. package/generators/micro-react/templates/packages/common-intl/src/index.ts +3 -6
  40. package/generators/micro-react/templates/packages/common-intl/src/intl.ts +20 -23
  41. package/generators/micro-react/templates/packages/common-intl/tsconfig.json +2 -4
  42. package/generators/subapp-react/index.js +28 -22
  43. package/generators/subapp-react/templates/homepage/README.md +1 -0
  44. package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +1 -0
  45. package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +1 -0
  46. package/generators/subapp-react/templates/homepage/config/config.prod.ts +1 -0
  47. package/generators/subapp-react/templates/homepage/docs/feature-PermissionFilter/346/214/211/351/222/256/346/235/203/351/231/220.md +35 -0
  48. package/generators/subapp-react/templates/homepage/package.json +2 -1
  49. package/generators/subapp-react/templates/homepage/src/app.tsx +7 -0
  50. package/generators/subapp-react/templates/homepage/src/common/mainApp.ts +39 -2
  51. package/generators/subapp-react/templates/homepage/src/components/PermissionFilter/index.tsx +48 -0
  52. package/generators/subapp-react/templates/homepage/src/pages/index.tsx +35 -1
  53. package/lib/utils.js +0 -1
  54. package/package.json +2 -2
  55. package/generators/micro-react/templates/apps/layout/docs/common-intl.md +0 -372
  56. package/generators/micro-react/templates/packages/common-intl/src/indexedDBUtils.ts +0 -51
  57. package/generators/micro-react/templates/packages/common-intl/src/utils.ts +0 -482
  58. package/generators/micro-react/templates/packages/common-intl/vite.config.ts +0 -25
@@ -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
 
@@ -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`。
@@ -19,7 +19,8 @@
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:*"
23
24
  },
24
25
  "devDependencies": {
25
26
  "@mico-platform/ui": "<%= micoUiVersion %>",
@@ -5,6 +5,13 @@
5
5
  *
6
6
  * 注意:app.tsx 只能导出 Umi 规定的 API(qiankun、getInitialState 等)
7
7
  * 自定义函数请放在 @/common/mainApp.ts 中
8
+ *
9
+ * 国际化使用:
10
+ * 子应用默认共享主应用的 intl,直接导入即可:
11
+ * import { intl } from '<%= packageScope %>/common-intl';
12
+ * intl.common_hello();
13
+ * 如需独立 tag,直接依赖 @common-web/common-intl 自行调用 initIntl,
14
+ * 参考 packages/common-intl/README.md
8
15
  */
9
16
 
10
17
  import React from 'react';
@@ -3,6 +3,8 @@
3
3
  * @description 存储和获取主应用传递的 props
4
4
  */
5
5
 
6
+ import { useSyncExternalStore } from 'react';
7
+
6
8
  /**
7
9
  * 主应用传递的 props 类型定义
8
10
  */
@@ -13,17 +15,54 @@ export interface IMicroAppProps {
13
15
  request?: <T = any>(url: string, options?: any) => Promise<T>;
14
16
  /** 当前路由路径(由主应用传递,用于子应用内部路由切换) */
15
17
  routePath?: string;
18
+ /**
19
+ * 按钮权限列表(与主应用 fetchUserInfo.button_perms 一致,由主应用 MicroAppLoader 注入)
20
+ */
21
+ button_perms?: string[];
22
+ /** 是否超级用户(与主应用用户信息一致) */
23
+ is_superuser?: boolean;
16
24
  }
17
25
 
18
26
  /** 存储主应用传递的 props */
19
27
  let mainAppProps: IMicroAppProps | null = null;
20
28
 
29
+ const propsListeners = new Set<() => void>();
30
+
31
+ const notifyPropsListeners = (): void => {
32
+ propsListeners.forEach((listener) => listener());
33
+ };
34
+
35
+ /**
36
+ * 订阅主应用 props 变更(qiankun update 时触发,供 PermissionFilter 等订阅刷新)
37
+ */
38
+ export const subscribeMainAppProps = (listener: () => void): (() => void) => {
39
+ propsListeners.add(listener);
40
+ return () => propsListeners.delete(listener);
41
+ };
42
+
21
43
  /**
22
44
  * 设置主应用 props(由 qiankun 生命周期调用)
23
45
  * @internal
24
46
  */
25
47
  export const setMainAppProps = (props: IMicroAppProps | null) => {
26
48
  mainAppProps = props;
49
+ notifyPropsListeners();
50
+ };
51
+
52
+ /**
53
+ * 获取当前主应用注入的 props(非 React 场景)
54
+ */
55
+ export const getMainAppProps = (): IMicroAppProps | null => mainAppProps;
56
+
57
+ /**
58
+ * React Hook:订阅主应用 props,与 qiankun update 同步
59
+ */
60
+ export const useMainAppProps = (): IMicroAppProps | null => {
61
+ return useSyncExternalStore(
62
+ subscribeMainAppProps,
63
+ () => mainAppProps,
64
+ () => null,
65
+ );
27
66
  };
28
67
 
29
68
  /**
@@ -51,5 +90,3 @@ export const isRunningInQiankun = () => !!mainAppProps;
51
90
  * 获取主应用标识
52
91
  */
53
92
  export const getMainAppName = () => mainAppProps?.mainApp;
54
-
55
-
@@ -0,0 +1,48 @@
1
+ import { type IMicroAppProps, useMainAppProps } from '@/common/mainApp';
2
+ import React from 'react';
3
+
4
+ export interface IPermissionFilterProps {
5
+ /** 按钮/操作权限标识,需在主应用传入的 `button_perms` 中 */
6
+ permissionKey: string;
7
+ children?: React.ReactNode;
8
+ /** 无权限时渲染内容;不传则渲染 null */
9
+ fallback?: React.ReactNode;
10
+ }
11
+
12
+ const isSuperuserUser = (value?: boolean | number): boolean => {
13
+ return value === true || value === 1;
14
+ };
15
+
16
+ function hasButtonPermission(
17
+ permissionKey: string,
18
+ props: IMicroAppProps | null,
19
+ ): boolean {
20
+ if (!permissionKey?.trim()) {
21
+ return false;
22
+ }
23
+ if (isSuperuserUser(props?.is_superuser)) {
24
+ return true;
25
+ }
26
+ const buttonPerms = props?.button_perms ?? [];
27
+ return buttonPerms.includes(permissionKey);
28
+ }
29
+
30
+ /**
31
+ * 按主应用通过 qiankun 传入的 `button_perms` / `is_superuser` 判断是否渲染子节点。
32
+ * 独立运行子应用时无主应用 props,默认无按钮权限。
33
+ */
34
+ const PermissionFilter: React.FC<IPermissionFilterProps> = ({
35
+ permissionKey,
36
+ children,
37
+ fallback = null,
38
+ }) => {
39
+ const mainProps = useMainAppProps();
40
+ const allowed = hasButtonPermission(permissionKey, mainProps);
41
+
42
+ if (!allowed) {
43
+ return <>{fallback}</>;
44
+ }
45
+ return <>{children}</>;
46
+ };
47
+
48
+ export default PermissionFilter;
@@ -1,3 +1,4 @@
1
+ import PermissionFilter from '@/components/PermissionFilter';
1
2
  import { getRequestSource, request } from '@/common/request';
2
3
  import {
3
4
  Alert,
@@ -265,6 +266,39 @@ export default function HomePage() {
265
266
  </Card>
266
267
  </Spin>
267
268
 
269
+ {/* PermissionFilter:按主应用传入的 button_perms 控制展示 */}
270
+ <Card title="🔐 PermissionFilter 按钮权限示例" style={{ marginBottom: 16 }}>
271
+ <Paragraph>
272
+ 子应用通过 qiankun 接收主应用注入的{' '}
273
+ <Text code>button_perms</Text> / <Text code>is_superuser</Text>。
274
+ 仅当权限匹配(或超级用户)时渲染 <Text code>children</Text>,否则渲染{' '}
275
+ <Text code>fallback</Text>。
276
+ </Paragraph>
277
+ <Paragraph type="secondary" style={{ marginBottom: 12 }}>
278
+ 示例 permissionKey 为 <Text code>cs_web_btn_subapp_demo</Text>
279
+ 。嵌入主应用时,需在主应用用户信息接口的{' '}
280
+ <Text code>button_perms</Text> 中包含该标识;独立运行子应用时无主应用
281
+ props,将始终走 fallback。
282
+ </Paragraph>
283
+ <PermissionFilter
284
+ permissionKey="cs_web_btn_subapp_demo"
285
+ fallback={
286
+ <Alert
287
+ type="warning"
288
+ title="无权限"
289
+ content="未在 button_perms 中包含 cs_web_btn_subapp_demo,或为独立运行子应用。"
290
+ />
291
+ }
292
+ >
293
+ <Space>
294
+ <Button type="primary" status="success">
295
+ 有权限时可见的操作
296
+ </Button>
297
+ <Text type="success">(当前用户具备该按钮权限)</Text>
298
+ </Space>
299
+ </PermissionFilter>
300
+ </Card>
301
+
268
302
  {/* @mico-platform/ui 组件示例 */}
269
303
  <Card title="📦 @mico-platform/ui 组件" style={{ marginBottom: 16 }}>
270
304
  <Paragraph>
@@ -363,7 +397,7 @@ export default function HomePage() {
363
397
  <Alert
364
398
  type="info"
365
399
  title="依赖共享"
366
- content="通过 externals 配置,子应用复用主应用的 React 和 @mico-platform/ui,打包体积减少 ~1MB"
400
+ content="通过 externals 配置,子应用复用主应用的 React、@mico-platform/ui 和 common-intl 包(含多语言文案数据),打包体积减少 ~1MB"
367
401
  />
368
402
  <Alert
369
403
  type="success"
package/lib/utils.js CHANGED
@@ -376,7 +376,6 @@ async function runDoctorChecks() {
376
376
  const results = {
377
377
  node: checkNodeVersion('18'),
378
378
  pnpm: checkCommand('pnpm'),
379
- yo: checkCommand('yo'),
380
379
  git: checkCommand('git'),
381
380
  npm: null
382
381
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-mico-cli",
3
- "version": "0.2.27",
3
+ "version": "0.2.29",
4
4
  "description": "Yeoman generator for Mico CLI projects",
5
5
  "keywords": [
6
6
  "yeoman-generator",
@@ -30,7 +30,6 @@
30
30
  "@biomejs/biome": "^1.9.4",
31
31
  "husky": "^9.1.7",
32
32
  "vitest": "^4.0.18",
33
- "yeoman-environment": "^3.19.3",
34
33
  "yeoman-test": "^6.3.0"
35
34
  },
36
35
  "engines": {
@@ -39,6 +38,7 @@
39
38
  "dependencies": {
40
39
  "semver": "^7.6.3",
41
40
  "update-notifier": "^7.3.1",
41
+ "yeoman-environment": "^3.19.3",
42
42
  "yeoman-generator": "^5.9.0"
43
43
  }
44
44
  }