generator-mico-cli 0.2.5 → 0.2.7
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.
- package/generators/micro-react/templates/.cursor/rules/development-guide.mdc +1 -2
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +2 -6
- package/generators/micro-react/templates/apps/layout/config/config.ts +1 -0
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +126 -4
- package/generators/micro-react/templates/apps/layout/src/app.tsx +1 -1
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/micro-app-manager.ts +74 -22
- package/package.json +1 -1
- package/generators/micro-react/templates/apps/layout/mock/menus.json +0 -100
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
// https://umijs.org/config/
|
|
2
2
|
|
|
3
3
|
import { defineConfig } from '@umijs/max';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
import path from 'path';
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
fs.readFileSync(path.join(__dirname, '../mock/menus.json'), 'utf-8'),
|
|
10
|
-
);
|
|
5
|
+
|
|
6
|
+
import mockMenus from '../mock/menus';
|
|
11
7
|
|
|
12
8
|
const config: ReturnType<typeof defineConfig> = {
|
|
13
9
|
publicPath: '/',
|
|
@@ -2,10 +2,132 @@
|
|
|
2
2
|
* Mock 菜单数据
|
|
3
3
|
* 用于开发环境测试
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 菜单结构说明:
|
|
6
|
+
* - id: 菜单唯一标识
|
|
7
|
+
* - name: 菜单名称
|
|
8
|
+
* - type: 'page' | 'group' (page: 页面, group: 分组)
|
|
9
|
+
* - path: 路由路径 (group 类型为 null)
|
|
10
|
+
* - icon: 图标名称
|
|
11
|
+
* - enabled: 是否启用
|
|
12
|
+
* - sortOrder: 排序权重
|
|
13
|
+
* - pageId: 关联的页面 ID
|
|
14
|
+
* - page: 页面配置 (微前端入口)
|
|
15
|
+
* - htmlUrl: 子应用入口地址
|
|
16
|
+
* - jsUrls: 额外 JS 资源
|
|
17
|
+
* - cssUrls: 额外 CSS 资源
|
|
18
|
+
* - children: 子菜单数组
|
|
7
19
|
*/
|
|
8
|
-
import mockMenusData from './menus.json';
|
|
9
20
|
|
|
10
|
-
|
|
21
|
+
import type { MenuItem, PageConfig } from '@/common/menu/types';
|
|
22
|
+
|
|
23
|
+
/** Mock 页面配置 - 只需要核心字段 */
|
|
24
|
+
type MockPageConfig = Pick<PageConfig, 'id' | 'name' | 'route' | 'enabled' | 'htmlUrl' | 'jsUrls' | 'cssUrls'>;
|
|
25
|
+
|
|
26
|
+
/** Mock 菜单项 - page 字段使用简化类型 */
|
|
27
|
+
type MockMenuItem = Omit<MenuItem, 'page' | 'children'> & {
|
|
28
|
+
page: MockPageConfig | null;
|
|
29
|
+
children: MockMenuItem[];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const mockMenus: MockMenuItem[] = [
|
|
33
|
+
{
|
|
34
|
+
"id": 1,
|
|
35
|
+
"name": "首页",
|
|
36
|
+
"type": "page",
|
|
37
|
+
"path": null,
|
|
38
|
+
"icon": "Home",
|
|
39
|
+
"enabled": true,
|
|
40
|
+
"sortOrder": 0,
|
|
41
|
+
"pageId": 1,
|
|
42
|
+
"page": {
|
|
43
|
+
"id": 1,
|
|
44
|
+
"name": "home",
|
|
45
|
+
"route": "/",
|
|
46
|
+
"enabled": true,
|
|
47
|
+
"htmlUrl": "",
|
|
48
|
+
"jsUrls": [],
|
|
49
|
+
"cssUrls": []
|
|
50
|
+
},
|
|
51
|
+
"children": []
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": 2,
|
|
55
|
+
"name": "示例模块",
|
|
56
|
+
"type": "group",
|
|
57
|
+
"path": null,
|
|
58
|
+
"icon": "List",
|
|
59
|
+
"enabled": true,
|
|
60
|
+
"sortOrder": 1,
|
|
61
|
+
"pageId": null,
|
|
62
|
+
"page": null,
|
|
63
|
+
"children": [
|
|
64
|
+
{
|
|
65
|
+
"id": 3,
|
|
66
|
+
"name": "示例页面",
|
|
67
|
+
"type": "page",
|
|
68
|
+
"path": "/example/page",
|
|
69
|
+
"icon": "File",
|
|
70
|
+
"enabled": true,
|
|
71
|
+
"sortOrder": 1,
|
|
72
|
+
"pageId": 45,
|
|
73
|
+
"page": {
|
|
74
|
+
"id": 45,
|
|
75
|
+
"name": "example-page",
|
|
76
|
+
"route": "/example/page",
|
|
77
|
+
"enabled": true,
|
|
78
|
+
"htmlUrl": "",
|
|
79
|
+
"jsUrls": [],
|
|
80
|
+
"cssUrls": []
|
|
81
|
+
},
|
|
82
|
+
"children": []
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": 5,
|
|
88
|
+
"name": "微应用示例",
|
|
89
|
+
"type": "group",
|
|
90
|
+
"path": null,
|
|
91
|
+
"icon": "Apps",
|
|
92
|
+
"enabled": true,
|
|
93
|
+
"sortOrder": 2,
|
|
94
|
+
"pageId": null,
|
|
95
|
+
"page": null,
|
|
96
|
+
"children": [
|
|
97
|
+
{
|
|
98
|
+
"id": 6,
|
|
99
|
+
"name": "子应用页面",
|
|
100
|
+
"type": "page",
|
|
101
|
+
"path": null,
|
|
102
|
+
"icon": "Desktop",
|
|
103
|
+
"enabled": true,
|
|
104
|
+
"sortOrder": 1,
|
|
105
|
+
"pageId": 55,
|
|
106
|
+
"page": {
|
|
107
|
+
"id": 55,
|
|
108
|
+
"name": "subapp-example",
|
|
109
|
+
"route": "/subapp",
|
|
110
|
+
"enabled": true,
|
|
111
|
+
"htmlUrl": "//localhost:8010",
|
|
112
|
+
"jsUrls": [],
|
|
113
|
+
"cssUrls": []
|
|
114
|
+
},
|
|
115
|
+
"children": []
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"id": 7,
|
|
121
|
+
"name": "外部链接",
|
|
122
|
+
"type": "link",
|
|
123
|
+
"path": "https://github.com",
|
|
124
|
+
"icon": "Link",
|
|
125
|
+
"enabled": true,
|
|
126
|
+
"sortOrder": 3,
|
|
127
|
+
"pageId": null,
|
|
128
|
+
"page": null,
|
|
129
|
+
"children": []
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
|
|
11
133
|
export default mockMenus;
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
} from './common/micro';
|
|
19
19
|
import { initTheme } from './common/theme';
|
|
20
20
|
import MicroAppLoader from './components/MicroAppLoader';
|
|
21
|
-
import { NO_AUTH_ROUTE_LIST } from '
|
|
21
|
+
import { NO_AUTH_ROUTE_LIST } from '@/constants';
|
|
22
22
|
import './global.less';
|
|
23
23
|
|
|
24
24
|
// ==================== qiankun 全局错误处理 ====================
|
|
@@ -18,7 +18,6 @@ import { loadMicroApp } from 'qiankun';
|
|
|
18
18
|
import { microAppLogger } from '@/common/logger';
|
|
19
19
|
// 导入路由守卫(会自动初始化)
|
|
20
20
|
import { refreshUserIntent, setUserIntent } from '@/common/route-guard';
|
|
21
|
-
|
|
22
21
|
// 导入低优先级预加载函数
|
|
23
22
|
import { markAppAsPrefetched, prefetchMicroAppsLowPriority } from '@/common/micro-prefetch';
|
|
24
23
|
|
|
@@ -99,6 +98,27 @@ function activateContainer(container: HTMLElement, target: HTMLElement): void {
|
|
|
99
98
|
container.style.cssText = 'display: block; width: 100%; height: 100%;';
|
|
100
99
|
}
|
|
101
100
|
|
|
101
|
+
/**
|
|
102
|
+
* 安全地更新微应用 props
|
|
103
|
+
* 避免 single-spa 错误 #32(Cannot update parcel because it is not mounted)
|
|
104
|
+
* @see https://single-spa.js.org/error/?code=32
|
|
105
|
+
*/
|
|
106
|
+
async function safeUpdate(microApp: MicroApp, props: Record<string, unknown>): Promise<void> {
|
|
107
|
+
try {
|
|
108
|
+
const status = microApp.getStatus();
|
|
109
|
+
if (status !== 'MOUNTED') {
|
|
110
|
+
microAppLogger.log('safeUpdate: skipped, status =', status);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// update 可能返回 Promise,需要 await 以捕获异步错误
|
|
114
|
+
await microApp.update?.(props);
|
|
115
|
+
} catch (err) {
|
|
116
|
+
// 捕获错误但不抛出,避免 unhandled rejection 导致页面崩溃
|
|
117
|
+
// 使用 error 级别确保异常可见,便于排查问题
|
|
118
|
+
microAppLogger.error('safeUpdate: caught error:', err);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
102
122
|
function deactivateContainer(container: HTMLElement): void {
|
|
103
123
|
document.body.appendChild(container);
|
|
104
124
|
container.classList.remove(CSS_CLASS.active);
|
|
@@ -170,7 +190,7 @@ class MicroAppManager {
|
|
|
170
190
|
microAppLogger.log('Already mounted, updating props only');
|
|
171
191
|
const cached = this.appCache.get(config.name);
|
|
172
192
|
if (cached && cached.microApp.getStatus() === 'MOUNTED') {
|
|
173
|
-
cached.microApp
|
|
193
|
+
safeUpdate(cached.microApp, config.props);
|
|
174
194
|
if (cached.container.parentElement !== config.target) {
|
|
175
195
|
activateContainer(cached.container, config.target);
|
|
176
196
|
}
|
|
@@ -200,7 +220,7 @@ class MicroAppManager {
|
|
|
200
220
|
if (this.currentAppName && this.state === 'mounted') {
|
|
201
221
|
const cached = this.appCache.get(this.currentAppName);
|
|
202
222
|
if (cached && cached.microApp.getStatus() === 'MOUNTED') {
|
|
203
|
-
cached.microApp
|
|
223
|
+
safeUpdate(cached.microApp, props);
|
|
204
224
|
}
|
|
205
225
|
}
|
|
206
226
|
}
|
|
@@ -217,7 +237,7 @@ class MicroAppManager {
|
|
|
217
237
|
await this.safeUnmount(instance.microApp);
|
|
218
238
|
instance.container.remove();
|
|
219
239
|
} catch (err) {
|
|
220
|
-
microAppLogger.
|
|
240
|
+
microAppLogger.error('Clear cache error for', name, err);
|
|
221
241
|
}
|
|
222
242
|
}
|
|
223
243
|
this.appCache.clear();
|
|
@@ -278,7 +298,6 @@ class MicroAppManager {
|
|
|
278
298
|
this.updateState({ loading: true, error: null, mounted: false });
|
|
279
299
|
|
|
280
300
|
try {
|
|
281
|
-
|
|
282
301
|
if (this.shouldAbort(request.name, mySeq)) {
|
|
283
302
|
console.log('🔍[路由调试] ⚠️ Aborted before load', { name: request.name, mySeq, operationSeq: this.operationSeq });
|
|
284
303
|
this.state = 'idle';
|
|
@@ -314,25 +333,19 @@ class MicroAppManager {
|
|
|
314
333
|
console.log('🔍[路由调试] 等待缓存实例 mountPromise...', { name: request.name });
|
|
315
334
|
await withTimeout(appInstance.microApp.mountPromise, MOUNT_TIMEOUT, '子应用挂载超时');
|
|
316
335
|
console.log('🔍[路由调试] 缓存实例 mountPromise 完成', { name: request.name, status: appInstance.microApp.getStatus() });
|
|
317
|
-
// 重要:mount 完成后立即更新 props,确保子应用使用最新的 routePath
|
|
318
|
-
// qiankun 的 mount() 使用的是创建实例时的原始 props,可能包含过期的 routePath
|
|
319
|
-
appInstance.microApp.update?.(request.props);
|
|
320
|
-
console.log('🔍[路由调试] 缓存实例 props 已更新(BOOTSTRAPPING 后)', { name: request.name });
|
|
321
336
|
} else if (status === 'NOT_MOUNTED') {
|
|
322
337
|
// 实例之前被 unmount 过,需要重新 mount
|
|
323
338
|
console.log('🔍[路由调试] 开始 mount 缓存实例', { name: request.name });
|
|
324
339
|
await withTimeout(appInstance.microApp.mount(), MOUNT_TIMEOUT, '子应用挂载超时');
|
|
325
340
|
console.log('🔍[路由调试] 缓存实例 mount 完成', { name: request.name, status: appInstance.microApp.getStatus() });
|
|
326
|
-
// 重要:mount 完成后立即更新 props,确保子应用使用最新的 routePath
|
|
327
|
-
// qiankun 的 mount() 使用的是创建实例时的原始 props,可能包含过期的 routePath
|
|
328
|
-
appInstance.microApp.update?.(request.props);
|
|
329
|
-
console.log('🔍[路由调试] 缓存实例 props 已更新(NOT_MOUNTED 后)', { name: request.name });
|
|
330
341
|
}
|
|
331
342
|
// 如果 status === 'MOUNTED',则无需操作(已在 switchTo 入口处处理)
|
|
332
343
|
|
|
333
344
|
// 刷新意图,保护 mount 成功后的短暂窗口期
|
|
334
345
|
refreshUserIntent();
|
|
335
346
|
|
|
347
|
+
// 关键:在调用 update 之前检查是否需要 abort
|
|
348
|
+
// 这可以避免在即将被 unmount 的应用上调用 update,从而防止 single-spa error #32
|
|
336
349
|
if (this.shouldAbort(request.name, mySeq)) {
|
|
337
350
|
console.log('🔍[路由调试] ⚠️ Aborted (cached) after mount', { name: request.name });
|
|
338
351
|
await this.safeUnmount(appInstance.microApp);
|
|
@@ -342,6 +355,13 @@ class MicroAppManager {
|
|
|
342
355
|
return;
|
|
343
356
|
}
|
|
344
357
|
|
|
358
|
+
// mount 完成且不需要 abort,安全地调用 update 同步路由
|
|
359
|
+
// qiankun 的 mount() 使用的是创建实例时的原始 props,可能包含过期的 routePath
|
|
360
|
+
if (status === 'BOOTSTRAPPING' || status === 'NOT_MOUNTED') {
|
|
361
|
+
await safeUpdate(appInstance.microApp, request.props);
|
|
362
|
+
console.log('🔍[路由调试] 缓存实例 props 已更新', { name: request.name });
|
|
363
|
+
}
|
|
364
|
+
|
|
345
365
|
this.currentAppName = request.name;
|
|
346
366
|
this.state = 'mounted';
|
|
347
367
|
// 不立即清除意图,让它自然过期(5秒)
|
|
@@ -398,7 +418,17 @@ class MicroAppManager {
|
|
|
398
418
|
|
|
399
419
|
if (this.shouldAbort(request.name, mySeq)) {
|
|
400
420
|
console.log('🔍[路由调试] ⚠️ Aborted after loadPromise', { name: request.name, mySeq, operationSeq: this.operationSeq });
|
|
401
|
-
//
|
|
421
|
+
// 关键修复:loadPromise 完成后 qiankun 会自动开始 mount
|
|
422
|
+
// 必须等待 mountPromise 完成后再 unmount,否则会触发 single-spa 错误 #32
|
|
423
|
+
// 参考:https://github.com/single-spa/single-spa/issues/1184
|
|
424
|
+
try {
|
|
425
|
+
console.log('🔍[路由调试] Abort: 等待 mountPromise 完成...', { status: microApp.getStatus() });
|
|
426
|
+
await withTimeout(microApp.mountPromise, MOUNT_TIMEOUT, 'Mount timeout during abort');
|
|
427
|
+
console.log('🔍[路由调试] Abort: mountPromise 完成,执行 unmount', { status: microApp.getStatus() });
|
|
428
|
+
await this.safeUnmount(microApp);
|
|
429
|
+
} catch (abortErr) {
|
|
430
|
+
microAppLogger.error('Abort cleanup error:', abortErr);
|
|
431
|
+
}
|
|
402
432
|
deactivateContainer(container);
|
|
403
433
|
this.state = 'idle';
|
|
404
434
|
if (this.pendingRequest) this.processRequest();
|
|
@@ -409,14 +439,11 @@ class MicroAppManager {
|
|
|
409
439
|
await withTimeout(microApp.mountPromise, MOUNT_TIMEOUT, '子应用挂载超时');
|
|
410
440
|
console.log('🔍[路由调试] mountPromise 完成', { name: request.name, qiankunName, status: microApp.getStatus() });
|
|
411
441
|
|
|
412
|
-
// 重要:mount 完成后调用 update,触发子应用的路由同步
|
|
413
|
-
// 由于子应用 mount 生命周期不再调用 syncRoute,需要通过 update 来同步路由
|
|
414
|
-
microApp.update?.(request.props);
|
|
415
|
-
console.log('🔍[路由调试] 新实例 props 已更新', { name: request.name });
|
|
416
|
-
|
|
417
442
|
// 刷新意图,保护 mount 成功后的短暂窗口期
|
|
418
443
|
refreshUserIntent();
|
|
419
444
|
|
|
445
|
+
// 关键:在调用 update 之前检查是否需要 abort
|
|
446
|
+
// 这可以避免在即将被 unmount 的应用上调用 update,从而防止 single-spa error #32
|
|
420
447
|
if (this.shouldAbort(request.name, mySeq)) {
|
|
421
448
|
console.log('🔍[路由调试] ⚠️ Aborted after mountPromise', { name: request.name, mySeq, operationSeq: this.operationSeq });
|
|
422
449
|
// 实例已在 loadPromise 后缓存,这里只需 unmount 和 deactivate
|
|
@@ -427,6 +454,11 @@ class MicroAppManager {
|
|
|
427
454
|
return;
|
|
428
455
|
}
|
|
429
456
|
|
|
457
|
+
// mount 完成且不需要 abort,安全地调用 update 同步路由
|
|
458
|
+
// 由于子应用 mount 生命周期不再调用 syncRoute,需要通过 update 来同步路由
|
|
459
|
+
await safeUpdate(microApp, request.props);
|
|
460
|
+
console.log('🔍[路由调试] 新实例 props 已更新', { name: request.name });
|
|
461
|
+
|
|
430
462
|
// 实例已在 loadPromise 后缓存,这里不需要重复缓存
|
|
431
463
|
|
|
432
464
|
this.currentAppName = request.name;
|
|
@@ -483,7 +515,7 @@ class MicroAppManager {
|
|
|
483
515
|
await this.safeUnmount(appInstance.microApp);
|
|
484
516
|
deactivateContainer(appInstance.container);
|
|
485
517
|
} catch (err) {
|
|
486
|
-
microAppLogger.
|
|
518
|
+
microAppLogger.error('Deactivate error:', err);
|
|
487
519
|
}
|
|
488
520
|
}
|
|
489
521
|
|
|
@@ -494,11 +526,31 @@ class MicroAppManager {
|
|
|
494
526
|
|
|
495
527
|
private async safeUnmount(microApp: MicroApp): Promise<void> {
|
|
496
528
|
try {
|
|
497
|
-
|
|
529
|
+
const status = microApp.getStatus();
|
|
530
|
+
microAppLogger.log('safeUnmount: current status =', status);
|
|
531
|
+
|
|
532
|
+
// 处理正在挂载的情况:等待 mountPromise 完成后再 unmount
|
|
533
|
+
// 这避免了 single-spa 错误 #32(unmount 一个正在 mounting 的应用)
|
|
534
|
+
if (status === 'MOUNTING' || status === 'BOOTSTRAPPING') {
|
|
535
|
+
microAppLogger.log('safeUnmount: waiting for mountPromise...');
|
|
536
|
+
try {
|
|
537
|
+
await withTimeout(microApp.mountPromise, MOUNT_TIMEOUT, 'Mount timeout during unmount');
|
|
538
|
+
} catch (mountErr) {
|
|
539
|
+
// 如果等待 mount 超时或失败,记录错误但继续尝试 unmount
|
|
540
|
+
microAppLogger.error('safeUnmount: mountPromise failed, continuing unmount:', mountErr);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// 重新检查状态,因为等待 mountPromise 后状态可能已变化
|
|
545
|
+
const currentStatus = microApp.getStatus();
|
|
546
|
+
if (currentStatus === 'MOUNTED') {
|
|
498
547
|
await withTimeout(microApp.unmount(), UNMOUNT_TIMEOUT, 'Unmount timeout');
|
|
548
|
+
microAppLogger.log('safeUnmount: unmount completed');
|
|
549
|
+
} else {
|
|
550
|
+
microAppLogger.log('safeUnmount: skipped unmount, status =', currentStatus);
|
|
499
551
|
}
|
|
500
552
|
} catch (err) {
|
|
501
|
-
microAppLogger.
|
|
553
|
+
microAppLogger.error('safeUnmount error:', err);
|
|
502
554
|
}
|
|
503
555
|
}
|
|
504
556
|
}
|
package/package.json
CHANGED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"id": 1,
|
|
4
|
-
"name": "首页",
|
|
5
|
-
"type": "page",
|
|
6
|
-
"path": null,
|
|
7
|
-
"icon": "Home",
|
|
8
|
-
"enabled": true,
|
|
9
|
-
"sortOrder": 0,
|
|
10
|
-
"pageId": 1,
|
|
11
|
-
"page": {
|
|
12
|
-
"id": 1,
|
|
13
|
-
"name": "home",
|
|
14
|
-
"route": "/",
|
|
15
|
-
"enabled": true,
|
|
16
|
-
"htmlUrl": null,
|
|
17
|
-
"jsUrls": [],
|
|
18
|
-
"cssUrls": []
|
|
19
|
-
},
|
|
20
|
-
"children": []
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
"id": 2,
|
|
24
|
-
"name": "示例模块",
|
|
25
|
-
"type": "group",
|
|
26
|
-
"path": null,
|
|
27
|
-
"icon": "List",
|
|
28
|
-
"enabled": true,
|
|
29
|
-
"sortOrder": 1,
|
|
30
|
-
"pageId": null,
|
|
31
|
-
"page": null,
|
|
32
|
-
"children": [
|
|
33
|
-
{
|
|
34
|
-
"id": 3,
|
|
35
|
-
"name": "示例页面",
|
|
36
|
-
"type": "page",
|
|
37
|
-
"path": "/example/page",
|
|
38
|
-
"icon": "File",
|
|
39
|
-
"enabled": true,
|
|
40
|
-
"sortOrder": 1,
|
|
41
|
-
"pageId": 45,
|
|
42
|
-
"page": {
|
|
43
|
-
"id": 45,
|
|
44
|
-
"name": "example-page",
|
|
45
|
-
"route": "/example/page",
|
|
46
|
-
"enabled": true,
|
|
47
|
-
"htmlUrl": null,
|
|
48
|
-
"jsUrls": [],
|
|
49
|
-
"cssUrls": []
|
|
50
|
-
},
|
|
51
|
-
"children": []
|
|
52
|
-
}
|
|
53
|
-
]
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
"id": 5,
|
|
57
|
-
"name": "微应用示例",
|
|
58
|
-
"type": "group",
|
|
59
|
-
"path": null,
|
|
60
|
-
"icon": "Apps",
|
|
61
|
-
"enabled": true,
|
|
62
|
-
"sortOrder": 2,
|
|
63
|
-
"pageId": null,
|
|
64
|
-
"page": null,
|
|
65
|
-
"children": [
|
|
66
|
-
{
|
|
67
|
-
"id": 6,
|
|
68
|
-
"name": "子应用页面",
|
|
69
|
-
"type": "page",
|
|
70
|
-
"path": null,
|
|
71
|
-
"icon": "Desktop",
|
|
72
|
-
"enabled": true,
|
|
73
|
-
"sortOrder": 1,
|
|
74
|
-
"pageId": 55,
|
|
75
|
-
"page": {
|
|
76
|
-
"id": 55,
|
|
77
|
-
"name": "subapp-example",
|
|
78
|
-
"route": "/subapp",
|
|
79
|
-
"enabled": true,
|
|
80
|
-
"htmlUrl": "//localhost:8010",
|
|
81
|
-
"jsUrls": [],
|
|
82
|
-
"cssUrls": []
|
|
83
|
-
},
|
|
84
|
-
"children": []
|
|
85
|
-
}
|
|
86
|
-
]
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
"id": 7,
|
|
90
|
-
"name": "外部链接",
|
|
91
|
-
"type": "link",
|
|
92
|
-
"path": "https://github.com",
|
|
93
|
-
"icon": "Link",
|
|
94
|
-
"enabled": true,
|
|
95
|
-
"sortOrder": 3,
|
|
96
|
-
"pageId": null,
|
|
97
|
-
"page": null,
|
|
98
|
-
"children": []
|
|
99
|
-
}
|
|
100
|
-
]
|