generator-mico-cli 0.2.20 → 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 (60) hide show
  1. package/README.md +29 -0
  2. package/bin/mico.js +124 -5
  3. package/generators/micro-react/index.js +76 -17
  4. package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +14 -4
  5. package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +36 -26
  6. package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +5 -2
  7. package/generators/micro-react/templates/CLAUDE.md +15 -7
  8. package/generators/micro-react/templates/_gitignore +2 -0
  9. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +7 -3
  10. package/generators/micro-react/templates/apps/layout/config/config.ts +21 -0
  11. package/generators/micro-react/templates/apps/layout/config/routes.ts +0 -5
  12. package/generators/micro-react/templates/apps/layout/docs/common-intl.md +8 -6
  13. 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 +65 -37
  14. 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 +112 -48
  15. 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
  16. package/generators/micro-react/templates/apps/layout/docs/utils-timezone.md +4 -2
  17. package/generators/micro-react/templates/apps/layout/mock/menus.ts +89 -139
  18. package/generators/micro-react/templates/apps/layout/mock/pages.ts +83 -0
  19. package/generators/micro-react/templates/apps/layout/package.json +3 -2
  20. package/generators/micro-react/templates/apps/layout/src/app.tsx +10 -8
  21. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +121 -58
  22. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +35 -4
  23. package/generators/micro-react/templates/apps/layout/src/common/micro-prefetch.ts +3 -2
  24. package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +45 -0
  25. package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +49 -10
  26. package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +1 -1
  27. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +6 -0
  28. package/generators/micro-react/templates/apps/layout/src/common/theme.ts +0 -2
  29. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.less +0 -1
  30. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +4 -4
  31. package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +4 -5
  32. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +20 -1
  33. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +4 -3
  34. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/micro-app-manager.ts +7 -1
  35. package/generators/micro-react/templates/apps/layout/src/global.less +15 -3
  36. package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +3 -2
  37. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +30 -3
  38. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +15 -4
  39. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +75 -38
  40. package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +3 -7
  41. package/generators/micro-react/templates/apps/layout/src/services/user.ts +2 -2
  42. package/generators/micro-react/templates/dev.preset.json +1 -1
  43. package/generators/micro-react/templates/package.json +2 -1
  44. package/generators/subapp-react/index.js +240 -14
  45. package/generators/subapp-react/templates/homepage/.env +2 -1
  46. package/generators/subapp-react/templates/homepage/config/config.dev.ts +9 -1
  47. package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +2 -1
  48. package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +2 -1
  49. package/generators/subapp-react/templates/homepage/config/config.prod.ts +2 -1
  50. package/generators/subapp-react/templates/homepage/config/config.ts +21 -0
  51. package/generators/subapp-react/templates/homepage/config/routes.ts +1 -1
  52. package/generators/subapp-react/templates/homepage/mock/api.mock.ts +2 -2
  53. package/generators/subapp-react/templates/homepage/package.json +3 -2
  54. package/generators/subapp-react/templates/homepage/src/app.tsx +1 -1
  55. package/generators/subapp-react/templates/homepage/src/common/request.ts +2 -2
  56. package/generators/subapp-react/templates/homepage/src/global.less +2 -1
  57. package/generators/subapp-react/templates/homepage/src/pages/index.less +1 -1
  58. package/generators/subapp-react/templates/homepage/src/pages/index.tsx +27 -27
  59. package/lib/utils.js +200 -2
  60. package/package.json +1 -1
package/README.md CHANGED
@@ -48,12 +48,41 @@ mico list
48
48
  mico create micro-react --verbose
49
49
  ```
50
50
 
51
+ 预览模式(只显示将创建的文件,不实际创建):
52
+
53
+ ```bash
54
+ mico create micro-react --dry-run
55
+ ```
56
+
57
+ 检查环境依赖:
58
+
59
+ ```bash
60
+ mico doctor
61
+ ```
62
+
51
63
  将额外参数传递给生成器:
52
64
 
53
65
  ```bash
54
66
  mico create micro-react -- --help
55
67
  ```
56
68
 
69
+ ## 配置文件
70
+
71
+ 可以在项目目录或用户主目录创建 `.micorc` 或 `.micorc.json` 文件来预设默认值:
72
+
73
+ ```json
74
+ {
75
+ "packageScope": "@my-company",
76
+ "cdnPrefix": "portal",
77
+ "author": "Team <team@example.com>",
78
+ "defaultSubappName": "subapp"
79
+ }
80
+ ```
81
+
82
+ 配置查找顺序:
83
+ 1. 当前目录的 `.micorc` 或 `.micorc.json`
84
+ 2. 用户主目录的 `.micorc` 或 `.micorc.json`
85
+
57
86
  ## Monorepo 项目生成器 (micro-react)
58
87
 
59
88
  创建基于 qiankun 微前端架构的完整 Monorepo 项目:
package/bin/mico.js CHANGED
@@ -22,18 +22,32 @@ Commands:
22
22
  create <generator> Run a generator (e.g., mico create subapp-react)
23
23
  list List all available generators
24
24
  update Update mico-cli to the latest version
25
+ doctor Check environment dependencies
25
26
 
26
27
  Options:
27
28
  --help, -h Show this help message
28
29
  --version, -v Show version number
29
30
  --verbose Show detailed output during generation
31
+ --dry-run Preview files without creating them
30
32
  --no-update-check Skip update check
31
33
 
32
34
  Examples:
33
35
  mico create subapp-react
34
36
  mico create micro-react --verbose
37
+ mico create micro-react --dry-run
35
38
  mico list
36
39
  mico update
40
+ mico doctor
41
+
42
+ Configuration:
43
+ Create a .micorc or .micorc.json file in your project or home directory
44
+ to set default values for prompts:
45
+
46
+ {
47
+ "packageScope": "@my-company",
48
+ "cdnPrefix": "portal",
49
+ "author": "Team <team@example.com>"
50
+ }
37
51
 
38
52
  Notes:
39
53
  Requires Yeoman CLI (yo) installed globally.
@@ -118,6 +132,85 @@ function printVersion() {
118
132
  console.log(`mico-cli v${pkg.version}`);
119
133
  }
120
134
 
135
+ /**
136
+ * 运行 doctor 检查
137
+ */
138
+ async function runDoctor() {
139
+ const { runDoctorChecks, loadMicorc } = require('../lib/utils');
140
+
141
+ console.log('');
142
+ console.log(` \x1b[1mMico CLI Environment Check\x1b[0m (v${pkg.version})`);
143
+ console.log('');
144
+
145
+ const results = await runDoctorChecks();
146
+
147
+ // Node.js
148
+ const nodeIcon = results.node.satisfied ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
149
+ const nodeStatus = results.node.satisfied ? 'OK' : `Required: >= ${results.node.required}`;
150
+ console.log(` ${nodeIcon} Node.js ${results.node.current} (${nodeStatus})`);
151
+
152
+ // pnpm
153
+ const pnpmIcon = results.pnpm.available ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
154
+ if (results.pnpm.available) {
155
+ console.log(` ${pnpmIcon} pnpm ${results.pnpm.version}`);
156
+ } else {
157
+ console.log(` ${pnpmIcon} pnpm not found`);
158
+ console.log(' Install: npm install -g pnpm');
159
+ }
160
+
161
+ // Yeoman
162
+ const yoIcon = results.yo.available ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
163
+ if (results.yo.available) {
164
+ console.log(` ${yoIcon} yo ${results.yo.version}`);
165
+ } else {
166
+ console.log(` ${yoIcon} yo not found`);
167
+ console.log(' Install: npm install -g yo');
168
+ }
169
+
170
+ // Git
171
+ const gitIcon = results.git.available ? '\x1b[32m✓\x1b[0m' : '\x1b[33m⚠\x1b[0m';
172
+ if (results.git.available) {
173
+ console.log(` ${gitIcon} git ${results.git.version}`);
174
+ } else {
175
+ console.log(` ${gitIcon} git not found (optional)`);
176
+ }
177
+
178
+ // npm registry
179
+ const npmIcon = results.npm.reachable ? '\x1b[32m✓\x1b[0m' : '\x1b[33m⚠\x1b[0m';
180
+ if (results.npm.reachable) {
181
+ console.log(` ${npmIcon} npm registry reachable`);
182
+ } else {
183
+ console.log(` ${npmIcon} npm registry unreachable (${results.npm.error})`);
184
+ }
185
+
186
+ // .micorc 配置
187
+ console.log('');
188
+ const { config, configPath } = loadMicorc();
189
+ if (configPath) {
190
+ console.log(` \x1b[32m✓\x1b[0m Config loaded from: ${configPath}`);
191
+ const keys = Object.keys(config);
192
+ if (keys.length > 0) {
193
+ console.log(` Keys: ${keys.join(', ')}`);
194
+ }
195
+ } else {
196
+ console.log(' \x1b[2m○\x1b[0m No .micorc config found (optional)');
197
+ }
198
+
199
+ console.log('');
200
+
201
+ // 总结
202
+ const allGood = results.node.satisfied && results.pnpm.available && results.yo.available;
203
+ if (allGood) {
204
+ console.log(' \x1b[32m✅ All required dependencies are installed!\x1b[0m');
205
+ } else {
206
+ console.log(' \x1b[31m❌ Some required dependencies are missing.\x1b[0m');
207
+ console.log(' Please install them before using mico-cli.');
208
+ }
209
+ console.log('');
210
+
211
+ return allGood;
212
+ }
213
+
121
214
  /**
122
215
  * 检查是否有新版本
123
216
  * @returns {Promise<{current: string, latest: string, hasUpdate: boolean} | null>}
@@ -256,6 +349,7 @@ function performUpdate(latestVersion) {
256
349
  * @param {string[]} passthroughArgs - 透传参数
257
350
  * @param {object} options - 选项
258
351
  * @param {boolean} options.verbose - 是否启用详细输出
352
+ * @param {boolean} options.dryRun - 是否启用 dry-run 模式
259
353
  */
260
354
  function runGenerator(generator, rest, passthroughArgs, options = {}) {
261
355
  const localGeneratorEntry = path.join(
@@ -275,17 +369,31 @@ function runGenerator(generator, rest, passthroughArgs, options = {}) {
275
369
 
276
370
  const yoArgs = [yoGenerator, ...rest];
277
371
 
372
+ // 添加 dry-run 参数给 yeoman
373
+ if (options.dryRun) {
374
+ yoArgs.push('--dry-run');
375
+ }
376
+
278
377
  if (passthroughArgs.length > 0) {
279
378
  yoArgs.push('--', ...passthroughArgs);
280
379
  }
281
380
 
282
- // 通过环境变量传递 verbose 标志给生成器
381
+ // 通过环境变量传递标志给生成器
283
382
  const env = { ...process.env };
284
383
  if (options.verbose) {
285
384
  env.MICO_VERBOSE = '1';
385
+ }
386
+ if (options.dryRun) {
387
+ env.MICO_DRY_RUN = '1';
388
+ }
389
+
390
+ if (options.verbose) {
286
391
  console.log('');
287
392
  console.log(' \x1b[2m[verbose] Running generator:', yoGenerator, '\x1b[0m');
288
393
  console.log(' \x1b[2m[verbose] Arguments:', yoArgs.join(' '), '\x1b[0m');
394
+ if (options.dryRun) {
395
+ console.log(' \x1b[2m[verbose] Dry-run mode enabled\x1b[0m');
396
+ }
289
397
  console.log('');
290
398
  }
291
399
 
@@ -323,6 +431,7 @@ async function main() {
323
431
  const hasVersion = mainArgs.includes('--version') || mainArgs.includes('-v');
324
432
  const skipUpdateCheck = mainArgs.includes('--no-update-check');
325
433
  const isVerbose = mainArgs.includes('--verbose');
434
+ const isDryRun = mainArgs.includes('--dry-run');
326
435
 
327
436
  // 过滤掉标志参数
328
437
  const filteredArgs = mainArgs.filter(
@@ -332,7 +441,8 @@ async function main() {
332
441
  arg !== '--version' &&
333
442
  arg !== '-v' &&
334
443
  arg !== '--no-update-check' &&
335
- arg !== '--verbose'
444
+ arg !== '--verbose' &&
445
+ arg !== '--dry-run'
336
446
  );
337
447
 
338
448
  // 处理 --version
@@ -355,6 +465,12 @@ async function main() {
355
465
  process.exit(0);
356
466
  }
357
467
 
468
+ // 处理 doctor 命令
469
+ if (command === 'doctor') {
470
+ const success = await runDoctor();
471
+ process.exit(success ? 0 : 1);
472
+ }
473
+
358
474
  // 处理 update 命令
359
475
  if (command === 'update') {
360
476
  const updateInfo = await checkForUpdate();
@@ -376,8 +492,8 @@ async function main() {
376
492
  process.exit(1);
377
493
  }
378
494
 
379
- // 检查更新(除非跳过)
380
- if (!skipUpdateCheck) {
495
+ // 检查更新(除非跳过或 dry-run)
496
+ if (!skipUpdateCheck && !isDryRun) {
381
497
  const updateInfo = await checkForUpdate();
382
498
  if (updateInfo && updateInfo.hasUpdate) {
383
499
  const shouldUpdate = await askForUpdate(
@@ -396,7 +512,10 @@ async function main() {
396
512
  }
397
513
 
398
514
  // 运行生成器
399
- runGenerator(generator, rest.slice(1), passthroughArgs, { verbose: isVerbose });
515
+ runGenerator(generator, rest.slice(1), passthroughArgs, {
516
+ verbose: isVerbose,
517
+ dryRun: isDryRun
518
+ });
400
519
  return;
401
520
  }
402
521
 
@@ -9,10 +9,10 @@ const {
9
9
  collectFiles,
10
10
  transformDestPath,
11
11
  isTemplateFile,
12
- getLatestNpmVersion,
12
+ getPackageVersionsParallel,
13
13
  setupErrorHandlers,
14
14
  createLogger,
15
- // isVerbose,
15
+ loadMicorc,
16
16
  } = require('../../lib/utils');
17
17
 
18
18
  const IGNORE_LIST = require('./ignore-list.json');
@@ -25,6 +25,17 @@ module.exports = class extends Generator {
25
25
  this.projectRoot = process.cwd();
26
26
  this.logger = createLogger(this);
27
27
 
28
+ // 检查 dry-run 模式
29
+ this.isDryRun = this.options.dryRun || process.env.MICO_DRY_RUN === '1';
30
+
31
+ // 加载 .micorc 配置
32
+ const { config: rcConfig, configPath } = loadMicorc(this.projectRoot);
33
+ this.rcConfig = rcConfig;
34
+ if (configPath) {
35
+ this.logger.verbose('Loaded config from:', configPath);
36
+ this.logger.verbose('Config:', JSON.stringify(rcConfig, null, 2));
37
+ }
38
+
28
39
  // 检查当前目录是否已是一个 monorepo
29
40
  const workspaceFile = path.join(this.projectRoot, 'pnpm-workspace.yaml');
30
41
  if (fs.existsSync(workspaceFile)) {
@@ -46,12 +57,15 @@ module.exports = class extends Generator {
46
57
 
47
58
  async prompting() {
48
59
  try {
60
+ // 使用 .micorc 中的值作为默认值
61
+ const rc = this.rcConfig || {};
62
+
49
63
  this.answers = await this.prompt([
50
64
  {
51
65
  type: 'input',
52
66
  name: 'projectName',
53
67
  message: 'Project name',
54
- default: path.basename(this.projectRoot),
68
+ default: rc.projectName || path.basename(this.projectRoot),
55
69
  filter: (input) => toKebab(input),
56
70
  validate: (input) => {
57
71
  const value = toKebab(input);
@@ -63,7 +77,7 @@ module.exports = class extends Generator {
63
77
  type: 'input',
64
78
  name: 'packageScope',
65
79
  message: 'Package scope (e.g., @my-project)',
66
- default: (answers) => `@${toKebab(answers.projectName)}`,
80
+ default: (answers) => rc.packageScope || `@${toKebab(answers.projectName)}`,
67
81
  validate: (input) => {
68
82
  if (!input) return 'Package scope is required';
69
83
  if (!input.startsWith('@'))
@@ -80,7 +94,7 @@ module.exports = class extends Generator {
80
94
  - 输入 "portal": https://cdn.example.com/portal/my-project/1.0.0/
81
95
  - 输入 "admin/v2": https://cdn.example.com/admin/v2/my-project/1.0.0/
82
96
  Prefix`,
83
- default: '',
97
+ default: rc.cdnPrefix || '',
84
98
  filter: (input) => {
85
99
  // 移除首尾斜杠,规范化路径
86
100
  return input.trim().replace(/^\/+|\/+$/g, '');
@@ -90,7 +104,7 @@ module.exports = class extends Generator {
90
104
  type: 'input',
91
105
  name: 'author',
92
106
  message: 'Author',
93
- default: 'Your Name <email@example.com>',
107
+ default: rc.author || 'Your Name <email@example.com>',
94
108
  },
95
109
  ]);
96
110
 
@@ -110,7 +124,7 @@ module.exports = class extends Generator {
110
124
  }
111
125
  }
112
126
 
113
- writing() {
127
+ async writing() {
114
128
  try {
115
129
  if (!fs.existsSync(this.templateDir)) {
116
130
  console.error('');
@@ -125,21 +139,21 @@ module.exports = class extends Generator {
125
139
 
126
140
  // 在 mico_cli 根目录执行 npm view,以使用该目录 .npmrc 中的 Nexus 认证
127
141
  const cliRoot = path.resolve(__dirname, '../..');
128
- this.logger.verbose('Fetching latest package versions...');
142
+ this.logger.verbose('Fetching latest package versions (parallel)...');
129
143
 
130
- const micoUiVer = getLatestNpmVersion(
131
- '@mico-platform/ui',
132
- '1.0.0',
133
- 8000,
134
- cliRoot,
135
- );
136
- const themeVer = getLatestNpmVersion(
137
- '@mico-platform/theme',
138
- '1.0.0',
144
+ // 并行获取版本
145
+ const versions = await getPackageVersionsParallel(
146
+ [
147
+ { name: '@mico-platform/ui', fallback: '1.0.0' },
148
+ { name: '@mico-platform/theme', fallback: '1.0.0' },
149
+ ],
139
150
  8000,
140
151
  cliRoot,
141
152
  );
142
153
 
154
+ const micoUiVer = versions['@mico-platform/ui'];
155
+ const themeVer = versions['@mico-platform/theme'];
156
+
143
157
  this.logger.verbose('@mico-platform/ui version:', micoUiVer);
144
158
  this.logger.verbose('@mico-platform/theme version:', themeVer);
145
159
 
@@ -173,6 +187,45 @@ module.exports = class extends Generator {
173
187
  process.exit(1);
174
188
  }
175
189
 
190
+ // Dry-run 模式:只列出文件,不实际创建
191
+ if (this.isDryRun) {
192
+ this.log('');
193
+ this.log('\x1b[33m📋 Dry run mode - no files will be created\x1b[0m');
194
+ this.log('');
195
+ this.log(` Project: ${this.projectName}`);
196
+ this.log(` Scope: ${this.packageScope}`);
197
+ this.log(` Destination: ${this.destDir}`);
198
+ this.log('');
199
+ this.log(' Would create the following files:');
200
+ this.log('');
201
+
202
+ let templateCount = 0;
203
+ let copyCount = 0;
204
+
205
+ for (const relPath of files) {
206
+ const destRelPath = transformDestPath(relPath);
207
+ const isTemplate = isTemplateFile(relPath);
208
+ const tag = isTemplate ? '\x1b[32m[tpl]\x1b[0m' : '\x1b[36m[cpy]\x1b[0m';
209
+ this.log(` ${tag} ${destRelPath}`);
210
+
211
+ if (isTemplate) {
212
+ templateCount++;
213
+ } else {
214
+ copyCount++;
215
+ }
216
+ }
217
+
218
+ this.log('');
219
+ this.log(` Total: ${files.length} files (${templateCount} templates, ${copyCount} static)`);
220
+ this.log('');
221
+ this.log(' Run without --dry-run to actually create these files.');
222
+ this.log('');
223
+
224
+ // 设置标记以跳过后续阶段
225
+ this._skipInstall = true;
226
+ return;
227
+ }
228
+
176
229
  this.log('');
177
230
  this.log(`📦 Creating project: ${this.projectName}`);
178
231
  this.log(` Scope: ${this.packageScope}`);
@@ -210,6 +263,9 @@ module.exports = class extends Generator {
210
263
  }
211
264
 
212
265
  install() {
266
+ // 跳过 dry-run 模式
267
+ if (this._skipInstall) return;
268
+
213
269
  // 检查并初始化 git
214
270
  const gitDir = path.join(this.destDir, '.git');
215
271
  if (!fs.existsSync(gitDir)) {
@@ -228,6 +284,9 @@ module.exports = class extends Generator {
228
284
  }
229
285
 
230
286
  end() {
287
+ // 跳过 dry-run 模式
288
+ if (this._skipInstall) return;
289
+
231
290
  this.log('');
232
291
  this.log('✅ 项目创建成功!');
233
292
  this.log('');
@@ -20,8 +20,15 @@ docs/
20
20
  ### 2. Layout 应用文档 (`/apps/layout/docs/`)
21
21
  ```
22
22
  apps/layout/docs/
23
- ├── feature-微前端模式.md # qiankun 微前端架构
24
- └── feature-主题色切换.md # 主题系统实现
23
+ ├── feature-微前端模式.md # qiankun 微前端架构
24
+ ├── feature-路由与菜单解耦.md # PAGES/MENUS 数据源分离、双层权限
25
+ ├── feature-菜单权限控制.md # sideMenus 白名单、认证与授权分离
26
+ ├── feature-主题色切换.md # 主题系统实现
27
+ ├── feature-404页面.md # 404 页面
28
+ ├── arch-请求模块.md # HTTP 请求层模块化设计
29
+ ├── arch-日志与常量.md # Logger 工具与常量管理
30
+ ├── common-intl.md # 国际化公共包
31
+ └── utils-timezone.md # 时区工具
25
32
  ```
26
33
 
27
34
  ### 3. 其他应用文档
@@ -33,8 +40,9 @@ apps/layout/docs/
33
40
 
34
41
  ### Step 1: 确认需求涉及的模块
35
42
  - 微前端相关 → 阅读 `apps/layout/docs/feature-微前端模式.md`
43
+ - 路由/菜单/权限相关 → 阅读 `apps/layout/docs/feature-路由与菜单解耦.md` 和 `apps/layout/docs/feature-菜单权限控制.md`
36
44
  - 主题/样式相关 → 阅读 `apps/layout/docs/feature-主题色切换.md`
37
- - 请求/认证相关 → 阅读 `src/common/request/` 和 `src/common/auth/` 源码
45
+ - 请求/认证相关 → 阅读 `apps/layout/docs/arch-请求模块.md` 和 `src/common/auth/` 源码
38
46
  - 菜单相关 → 阅读 `src/common/menu/` 源码
39
47
 
40
48
  ### Step 2: 查找相关文档
@@ -54,9 +62,11 @@ find . -name "*.md" -type f | grep -v node_modules
54
62
  | 需求类型 | 必读文档 |
55
63
  |---------|---------|
56
64
  | 新建子应用 | `apps/layout/docs/feature-微前端模式.md` |
65
+ | 路由/页面配置 | `apps/layout/docs/feature-路由与菜单解耦.md` |
66
+ | 权限控制 | `apps/layout/docs/feature-菜单权限控制.md` |
57
67
  | 主题适配 | `apps/layout/docs/feature-主题色切换.md` |
58
68
  | 提交代码 | `docs/commit-message.md` |
59
- | API 请求 | `src/common/request/index.ts` 源码注释 |
69
+ | API 请求 | `apps/layout/docs/arch-请求模块.md` |
60
70
  | 认证登录 | `src/common/auth/` 目录源码 |
61
71
  | 菜单配置 | `src/common/menu/types.ts` 类型定义 |
62
72
 
@@ -63,24 +63,27 @@ apps/layout/
63
63
  const BasicLayout: React.FC = () => {
64
64
  const location = useLocation();
65
65
 
66
- // 解析路由配置
67
- const routes = useMemo(() => {
68
- const menus = getWindowMenus();
69
- return extractRoutes(menus);
66
+ // 所有页面路由(优先 PAGES,降级 MENUS)— 用于路由匹配和渲染
67
+ const allPageRoutes = useMemo(() => {
68
+ return getDynamicRoutes();
70
69
  }, []);
71
70
 
72
- // 查找当前路由配置
71
+ // 菜单路由(从 MENUS)— 用于权限交叉引用
72
+ const allMenuRoutes = useMemo(() => {
73
+ return extractRoutes(getMenus());
74
+ }, []);
75
+
76
+ // 当前路由配置(从所有页面路由中查找)
73
77
  const currentRoute = useMemo(() => {
74
- return findRouteByPath(routes, location.pathname);
75
- }, [routes, location.pathname]);
78
+ return findRouteByPath(allPageRoutes, location.pathname);
79
+ }, [allPageRoutes, location.pathname]);
76
80
 
77
81
  // 渲染页面内容
78
82
  const renderContent = () => {
79
- // 微应用类型使用 MicroAppLoader
80
83
  if (currentRoute?.loadType === 'microapp' && currentRoute.entry) {
81
- return <MicroAppLoader entry={currentRoute.entry} name={currentRoute.path} />;
84
+ const appName = getAppNameFromEntry(currentRoute.entry);
85
+ return <MicroAppLoader entry={currentRoute.entry} name={appName} />;
82
86
  }
83
- // 内部路由使用 Outlet
84
87
  return <Outlet />;
85
88
  };
86
89
 
@@ -98,28 +101,36 @@ const BasicLayout: React.FC = () => {
98
101
  };
99
102
  ```
100
103
 
101
- ### 2. 菜单系统 (common/menu/)
104
+ ### 2. 全局数据源 (common/portal-data.ts) + 菜单系统 (common/menu/)
102
105
 
103
106
  ```typescript
104
- // 获取菜单数据
105
- const menus = getWindowMenus();
107
+ // 获取页面列表 (window.__MICO_PAGES__)
108
+ const pages = getPages();
109
+
110
+ // 获取菜单树 (window.__MICO_MENUS__)
111
+ const menus = getMenus();
106
112
 
107
- // 提取路由配置
108
- const routes = extractRoutes(menus);
113
+ // 从页面列表提取动态路由(优先数据源)
114
+ const routes = getDynamicRoutes();
109
115
 
110
- // 解析菜单项
116
+ // 从菜单树提取路由(用于权限交叉引用)
117
+ const menuRoutes = extractRoutes(menus);
118
+
119
+ // 解析菜单项(用于侧边栏渲染)
111
120
  const menuItems = parseMenuItems(menus);
112
121
 
113
122
  // 查找当前路由
114
123
  const currentRoute = findRouteByPath(routes, pathname);
124
+
125
+ // 通过 pageId 查找页面(O(1))
126
+ const page = getPageById(pageId);
115
127
  ```
116
128
 
117
129
  ### 3. 动态路由 (app.tsx)
118
130
 
119
131
  ```typescript
120
132
  export function patchClientRoutes({ routes }: { routes: any[] }) {
121
- const menus = getWindowMenus();
122
- const dynamicRoutes = extractRoutes(menus);
133
+ const dynamicRoutes = getDynamicRoutes(); // 从 __MICO_PAGES__ 获取
123
134
 
124
135
  const rootRoute = routes.find((r) => r.path === '/');
125
136
  dynamicRoutes.forEach((route) => {
@@ -128,9 +139,7 @@ export function patchClientRoutes({ routes }: { routes: any[] }) {
128
139
  name: route.name,
129
140
  meta: {
130
141
  loadType: route.loadType,
131
- htmlUrl: route.htmlUrl,
132
- jsUrls: route.jsUrls,
133
- cssUrls: route.cssUrls,
142
+ entry: route.entry,
134
143
  },
135
144
  });
136
145
  });
@@ -265,8 +274,9 @@ const { collapsed } = useModel('global');
265
274
 
266
275
  ## 注意事项
267
276
 
268
- 1. **菜单数据来源**: 通过 `window.__MICO_MENUS__` 注入
269
- 2. **路由动态生成**: 在 `patchClientRoutes` 中根据菜单生成路由
270
- 3. **微应用判断**: 根据 `htmlUrl` 或 `jsUrls` 判断是否为微应用
271
- 4. **主题初始化**: `app.tsx` 中调用 `initTheme()` 避免闪烁
272
- 5. **认证共享**: 子应用使用主应用传递的 `request` 实例
277
+ 1. **数据来源**: 页面列表通过 `window.__MICO_PAGES__` 注入,菜单树通过 `window.__MICO_MENUS__` 注入
278
+ 2. **路由动态生成**: 在 `patchClientRoutes` 中优先从 `__MICO_PAGES__` 生成路由
279
+ 3. **权限交叉引用**: 菜单路由(`__MICO_MENUS__`)用于权限判断,页面路由(`__MICO_PAGES__`)用于渲染
280
+ 4. **微应用判断**: 根据 `htmlUrl` `jsUrls` 判断是否为微应用
281
+ 5. **主题初始化**: `app.tsx` 中调用 `initTheme()` 避免闪烁
282
+ 6. **认证共享**: 子应用使用主应用传递的 `request` 实例
@@ -61,15 +61,16 @@ alwaysApply: true
61
61
  - **关键文件**:
62
62
  - `src/layouts/index.tsx` - 主布局组件
63
63
  - `src/components/MicroAppLoader/` - 微应用加载器
64
- - `src/common/menu/parser.ts` - 菜单解析与路由提取
64
+ - `src/common/menu/parser.ts` - 菜单解析、页面数据提取与路由生成
65
65
  - `src/common/request/index.ts` - 统一请求封装
66
66
  - `src/hooks/useTheme.ts` - 主题管理 Hook
67
67
 
68
68
  ### 2. 微前端架构
69
- - 菜单数据通过 `window.__MICO_MENUS__` 注入
69
+ - 路由注册优先使用 `window.__MICO_PAGES__`(页面列表),无数据时降级到 `window.__MICO_MENUS__`(菜单树)
70
70
  - 根据 `htmlUrl` 或 `jsUrls` 判断是否为微应用
71
71
  - 使用 `loadMicroApp` API 动态挂载子应用
72
72
  - 子应用共享主应用的 `request` 实例
73
+ - 权限通过菜单交叉引用 + 页面级兜底的双层策略控制
73
74
 
74
75
  ### 3. 主题系统
75
76
  - 支持亮色/暗黑主题切换
@@ -123,6 +124,8 @@ Jenkins 根据环境执行 `CICD/` 下的构建脚本:
123
124
  ## 相关文档
124
125
 
125
126
  - [微前端模式](mdc:apps/layout/docs/feature-微前端模式.md)
127
+ - [路由与菜单解耦](mdc:apps/layout/docs/feature-路由与菜单解耦.md)
128
+ - [菜单权限控制](mdc:apps/layout/docs/feature-菜单权限控制.md)
126
129
  - [主题色切换](mdc:apps/layout/docs/feature-主题色切换.md)
127
130
  - [提交规范](mdc:docs/commit-message.md)
128
131
  - [部署说明](mdc:deployDesc.md)
@@ -53,15 +53,21 @@ apps/ # Workspace 应用目录(微前端子应用)
53
53
  │ │ ├── app.tsx # Umi 运行时配置
54
54
  │ │ ├── layouts/ # 布局组件
55
55
  │ │ ├── pages/ # 路由页面
56
- │ │ ├── components/ # 公共组件
56
+ │ │ ├── components/ # 公共组件(MicroAppLoader 等)
57
57
  │ │ ├── common/ # 工具模块(auth、request、upload、menu)
58
+ │ │ ├── constants/ # 常量定义(路由、主题、时区等)
58
59
  │ │ ├── hooks/ # 自定义 Hooks
59
60
  │ │ ├── services/ # API 服务层
60
61
  │ │ ├── models/ # Umi model(全局状态)
61
- │ │ ├── locales/ # 国际化文件(zh-CN、en-US)
62
- │ │ └── styles/ # 主题变量与样式覆盖
62
+ │ │ └── locales/ # 国际化文件(zh-CN、en-US)
63
63
  │ └── config/ # Umi 配置
64
- packages/ # 共享包
64
+ ├── basis/ # 基础数据子应用(货币管理等)
65
+ │ ├── src/
66
+ │ │ ├── pages/ # 路由页面
67
+ │ │ ├── locales/ # 国际化文件
68
+ │ │ └── common/ # 工具模块
69
+ │ └── config/ # Umi 配置
70
+ packages/ # 共享包(common-intl 等)
65
71
  scripts/ # 构建与开发脚本
66
72
  CICD/ # 部署资源
67
73
  ```
@@ -97,13 +103,13 @@ refactor(auth): 重构认证服务
97
103
  API 请求配置在 `src/common/request/`(模块化拆分为 6 个文件),错误处理在 `src/requestErrorConfig.ts`。详见 [请求模块架构](./apps/layout/docs/arch-请求模块.md)。
98
104
 
99
105
  ### 日志与常量
100
- Logger 工具在 `src/common/logger.ts`,常量定义在 `src/common/constants.ts`。详见 [日志与常量](./apps/layout/docs/arch-日志与常量.md)。
106
+ Logger 工具在 `src/common/logger.ts`,常量定义在 `src/constants/index.ts`。详见 [日志与常量](./apps/layout/docs/arch-日志与常量.md)。
101
107
 
102
108
  ### 认证模块
103
109
  认证逻辑在 `src/common/auth/`,核心文件 `auth-manager.ts` 管理 Token 存储和用户信息。SSO 登录在 `getInitialState()` 中完成,确保 UI 渲染前认证已就绪。
104
110
 
105
- ### 菜单系统
106
- 菜单解析与类型定义在 `src/common/menu/`。
111
+ ### 菜单与路由系统
112
+ 菜单解析与类型定义在 `src/common/menu/`。路由注册消费 `window.__MICO_PAGES__`(页面列表),菜单栏消费 `window.__MICO_MENUS__`(菜单树),两者解耦运作。权限通过菜单交叉引用 + 页面级兜底的双层策略控制。详见 [路由与菜单解耦](./apps/layout/docs/feature-路由与菜单解耦.md) 和 [菜单权限控制](./apps/layout/docs/feature-菜单权限控制.md)。
107
113
 
108
114
  ### 微前端
109
115
  layout 应用作为 qiankun 主应用,子应用通过 Umi 配置中的 qiankun 配置注册。详见 [微前端模式](./apps/layout/docs/feature-微前端模式.md)。
@@ -115,6 +121,8 @@ layout 应用作为 qiankun 主应用,子应用通过 Umi 配置中的 qiankun
115
121
  | [README.md](./README.md) | 项目入口、技术栈、常用命令 |
116
122
  | [提交规范](./docs/commit-message.md) | Git Commit 详细规范 |
117
123
  | [微前端模式](./apps/layout/docs/feature-微前端模式.md) | qiankun 架构、MicroAppLoader、子应用配置 |
124
+ | [路由与菜单解耦](./apps/layout/docs/feature-路由与菜单解耦.md) | PAGES/MENUS 数据源分离、双层权限控制 |
125
+ | [菜单权限控制](./apps/layout/docs/feature-菜单权限控制.md) | sideMenus 白名单、认证与授权分离 |
118
126
  | [主题色切换](./apps/layout/docs/feature-主题色切换.md) | useTheme Hook、CSS 变量系统 |
119
127
  | [请求模块架构](./apps/layout/docs/arch-请求模块.md) | HTTP 请求层模块化设计 |
120
128
  | [日志与常量](./apps/layout/docs/arch-日志与常量.md) | Logger 工具与常量管理 |
@@ -55,3 +55,5 @@ build
55
55
  /openspec
56
56
 
57
57
  **/tasks/*
58
+
59
+ .pnpm-store/**