generator-mico-cli 0.1.29 → 0.2.2-8.beta.1
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/README.md +199 -15
- package/bin/mico.js +232 -27
- package/generators/micro-react/index.js +200 -18
- package/generators/micro-react/meta.json +13 -0
- package/generators/micro-react/templates/.commitlintrc.js +1 -0
- package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +14 -4
- package/generators/micro-react/templates/.cursor/rules/cicd-deploy.mdc +10 -8
- package/generators/micro-react/templates/.cursor/rules/coding-conventions.mdc +1 -1
- package/generators/micro-react/templates/.cursor/rules/development-guide.mdc +3 -4
- package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +38 -31
- package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +7 -4
- package/generators/micro-react/templates/.cursor/rules/theme-system.mdc +10 -12
- package/generators/micro-react/templates/.eslintrc.js +25 -1
- package/generators/micro-react/templates/AGENTS.md +5 -2
- package/generators/micro-react/templates/CICD/before_build.sh +76 -0
- package/generators/micro-react/templates/CICD/start_dev.sh +27 -3
- package/generators/micro-react/templates/CICD/start_prod.sh +26 -3
- package/generators/micro-react/templates/CICD/start_test.sh +28 -3
- package/generators/micro-react/templates/CICD/wangsu_fresh_dev.sh +4 -4
- package/generators/micro-react/templates/CICD/wangsu_fresh_prod.sh +4 -4
- package/generators/micro-react/templates/CICD/wangsu_fresh_test.sh +4 -4
- package/generators/micro-react/templates/CLAUDE.md +16 -9
- package/generators/micro-react/templates/README.md +42 -4
- package/generators/micro-react/templates/_gitignore +4 -0
- package/generators/micro-react/templates/_npmrc +4 -0
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +33 -17
- package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +24 -29
- package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +25 -6
- package/generators/micro-react/templates/apps/layout/config/config.prod.ts +16 -7
- package/generators/micro-react/templates/apps/layout/config/config.ts +27 -4
- package/generators/micro-react/templates/apps/layout/config/routes.ts +10 -5
- package/generators/micro-react/templates/apps/layout/docs/arch-/346/227/245/345/277/227/344/270/216/345/270/270/351/207/217.md +2 -2
- package/generators/micro-react/templates/apps/layout/docs/arch-/350/257/267/346/261/202/346/250/241/345/235/227.md +1 -1
- package/generators/micro-react/templates/apps/layout/docs/common-intl.md +372 -0
- 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
- package/generators/micro-react/templates/apps/layout/docs/feature-404/351/241/265/351/235/242.md +103 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-/344/270/273/351/242/230/350/211/262/345/210/207/346/215/242.md +22 -26
- 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 +185 -28
- 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 +420 -0
- 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
- package/generators/micro-react/templates/apps/layout/docs/fix-SSO/346/227/240/351/231/220/351/207/215/345/256/232/345/220/221.md +88 -0
- package/generators/micro-react/templates/apps/layout/docs/utils-timezone.md +324 -0
- package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +81 -61
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +114 -4
- package/generators/micro-react/templates/apps/layout/mock/pages.ts +86 -0
- package/generators/micro-react/templates/apps/layout/package.json +7 -4
- package/generators/micro-react/templates/apps/layout/src/app.tsx +122 -83
- package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +3 -0
- package/generators/micro-react/templates/apps/layout/src/common/helpers.ts +177 -0
- package/generators/micro-react/templates/apps/layout/src/common/locale.ts +22 -17
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +283 -28
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +69 -5
- package/generators/micro-react/templates/apps/layout/src/common/micro/index.ts +34 -0
- package/generators/micro-react/templates/apps/layout/src/common/micro-prefetch.ts +109 -0
- package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +45 -0
- package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +72 -10
- package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +2 -2
- package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +31 -3
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +29 -11
- package/generators/micro-react/templates/apps/layout/src/common/request/url-resolver.ts +23 -8
- package/generators/micro-react/templates/apps/layout/src/common/route-guard.ts +345 -0
- package/generators/micro-react/templates/apps/layout/src/common/theme.ts +2 -4
- package/generators/micro-react/templates/apps/layout/src/common/upload/oss.ts +3 -4
- package/generators/micro-react/templates/apps/layout/src/common/upload/types.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/common/uploadFiles.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.less +8 -3
- package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +25 -8
- package/generators/micro-react/templates/apps/layout/src/components/HeaderDropdown/index.tsx +20 -0
- package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +5 -6
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +21 -6
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +83 -107
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/micro-app-manager.ts +569 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +383 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/avatar-dropdown.less +35 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/index.ts +2 -0
- package/generators/micro-react/templates/apps/layout/src/constants/index.ts +170 -6
- package/generators/micro-react/templates/apps/layout/src/global.less +19 -6
- package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +3 -2
- package/generators/micro-react/templates/apps/layout/src/hooks/useRoutePermissionRefresh.ts +72 -0
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less +3 -1
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +10 -55
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +34 -4
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +33 -9
- package/generators/micro-react/templates/apps/layout/src/layouts/index.less +84 -13
- package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +178 -47
- package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +12 -0
- package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +12 -0
- package/generators/micro-react/templates/apps/layout/src/pages/403/index.tsx +34 -0
- package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +78 -0
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +3 -0
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +7 -1
- package/generators/micro-react/templates/apps/layout/src/pages/User/Login/index.less +1 -1
- package/generators/micro-react/templates/apps/layout/src/pages/User/Login/index.tsx +9 -5
- package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/services/config/index.ts +63 -0
- package/generators/micro-react/templates/apps/layout/src/services/config/type.ts +30 -0
- package/generators/micro-react/templates/apps/layout/src/services/user.ts +29 -2
- package/generators/micro-react/templates/apps/layout/tailwind.config.js +3 -0
- package/generators/micro-react/templates/deployDesc.md +3 -3
- package/generators/micro-react/templates/dev.preset.json +14 -0
- package/generators/micro-react/templates/docs/dev-preset.md +130 -0
- package/generators/micro-react/templates/package.json +21 -6
- package/generators/micro-react/templates/packages/common-intl/README.md +427 -0
- package/generators/micro-react/templates/packages/common-intl/package.json +34 -0
- package/generators/micro-react/templates/packages/common-intl/src/index.ts +7 -0
- package/generators/micro-react/templates/packages/common-intl/src/indexedDBUtils.ts +51 -0
- package/generators/micro-react/templates/packages/common-intl/src/intl.ts +50 -0
- package/generators/micro-react/templates/packages/common-intl/src/utils.ts +482 -0
- package/generators/micro-react/templates/packages/common-intl/tsconfig.json +22 -0
- package/generators/micro-react/templates/packages/common-intl/vite.config.ts +25 -0
- package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +45 -0
- package/generators/micro-react/templates/scripts/collect-dist.js +10 -0
- package/generators/micro-react/templates/scripts/dev-preset.js +265 -0
- package/generators/micro-react/templates/scripts/dev-preset.schema.json +39 -0
- package/generators/micro-react/templates/turbo.json +4 -1
- package/generators/subapp-react/index.js +326 -40
- package/generators/subapp-react/meta.json +10 -0
- package/generators/subapp-react/templates/homepage/.env +2 -1
- package/generators/subapp-react/templates/homepage/README.md +3 -3
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +14 -7
- package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +16 -5
- package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +16 -5
- package/generators/subapp-react/templates/homepage/config/config.prod.ts +14 -5
- package/generators/subapp-react/templates/homepage/config/config.ts +27 -0
- package/generators/subapp-react/templates/homepage/config/routes.ts +2 -2
- package/generators/subapp-react/templates/homepage/mock/api.mock.ts +2 -2
- package/generators/subapp-react/templates/homepage/package.json +7 -4
- package/generators/subapp-react/templates/homepage/src/app.tsx +18 -27
- package/generators/subapp-react/templates/homepage/src/common/request.ts +29 -2
- package/generators/subapp-react/templates/homepage/src/global.less +6 -5
- package/generators/subapp-react/templates/homepage/src/pages/index.less +3 -3
- package/generators/subapp-react/templates/homepage/src/pages/index.tsx +99 -60
- package/generators/subapp-react/templates/homepage/src/styles/theme.less +1 -1
- package/generators/subapp-umd/ignore-list.json +5 -0
- package/generators/subapp-umd/index.js +309 -0
- package/generators/subapp-umd/meta.json +11 -0
- package/generators/subapp-umd/templates/README.md +94 -0
- package/generators/subapp-umd/templates/package.json +35 -0
- package/generators/subapp-umd/templates/public/index.html +34 -0
- package/generators/subapp-umd/templates/src/App.less +15 -0
- package/generators/subapp-umd/templates/src/App.tsx +13 -0
- package/generators/subapp-umd/templates/src/index.ts +2 -0
- package/generators/subapp-umd/templates/tsconfig.json +27 -0
- package/generators/subapp-umd/templates/webpack.config.js +70 -0
- package/lib/utils.js +332 -2
- package/package.json +15 -2
- package/generators/micro-react/templates/apps/layout/mock/menus.json +0 -100
- package/generators/micro-react/templates/apps/layout/src/common/constants.ts +0 -38
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/container-manager.ts +0 -202
- package/generators/micro-react/templates/packages/shared-styles/README.md +0 -124
- package/generators/micro-react/templates/packages/shared-styles/arco-design-mobile-override.less +0 -91
- package/generators/micro-react/templates/packages/shared-styles/arco-override.less +0 -119
- package/generators/micro-react/templates/packages/shared-styles/index.d.ts +0 -44
- package/generators/micro-react/templates/packages/shared-styles/index.less +0 -13
- package/generators/micro-react/templates/packages/shared-styles/package.json +0 -30
- package/generators/micro-react/templates/packages/shared-styles/theme-inject.less +0 -10
- package/generators/micro-react/templates/packages/shared-styles/themes/dark/custom-var.less +0 -290
- package/generators/micro-react/templates/packages/shared-styles/themes/normal/custom-var.less +0 -269
- package/generators/micro-react/templates/packages/shared-styles/variables-only.less +0 -433
- package/generators/micro-react/templates/packages/shared-styles/variables.less +0 -452
|
@@ -1,27 +1,235 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { getCurrentLocale, LOCALE } from '@/common/locale';
|
|
2
|
+
import { getMenuPage, getPages } from '@/common/portal-data';
|
|
3
|
+
import { isAuthDisabled, isNoPermissionRoute } from '@/constants';
|
|
4
|
+
import { getIntl } from '@umijs/max';
|
|
5
|
+
import type {
|
|
6
|
+
MenuItem,
|
|
7
|
+
ParsedMenuItem,
|
|
8
|
+
ParsedRoute,
|
|
9
|
+
PublicPageItem,
|
|
10
|
+
} from './types';
|
|
2
11
|
|
|
3
12
|
/**
|
|
4
|
-
*
|
|
13
|
+
* 从页面数据提取路由配置
|
|
5
14
|
*/
|
|
6
|
-
export const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
return
|
|
15
|
+
export const extractRoutesFromPages = (
|
|
16
|
+
pages: PublicPageItem[],
|
|
17
|
+
): ParsedRoute[] => {
|
|
18
|
+
return pages
|
|
19
|
+
.filter((page) => page.enabled)
|
|
20
|
+
.map((page) => {
|
|
21
|
+
const hasEntry = page.htmlUrl || (page.jsUrls && page.jsUrls.length > 0);
|
|
22
|
+
return {
|
|
23
|
+
path: page.route,
|
|
24
|
+
name: page.name,
|
|
25
|
+
base: page.base || '/',
|
|
26
|
+
icon: '',
|
|
27
|
+
loadType: (hasEntry ? 'microapp' : 'internal') as
|
|
28
|
+
| 'internal'
|
|
29
|
+
| 'microapp',
|
|
30
|
+
entry: page.htmlUrl || page.jsUrls?.[0] || undefined,
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 获取动态路由(从 __MICO_PAGES__)
|
|
37
|
+
*/
|
|
38
|
+
export const getDynamicRoutes = (): ParsedRoute[] => {
|
|
39
|
+
return extractRoutesFromPages(getPages());
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 根据路径查找对应的页面配置
|
|
44
|
+
* 匹配逻辑与 findRouteByPath 一致(精确匹配 + 通配符匹配)
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* 判断指定路径的页面是否为免认证页面
|
|
48
|
+
* 当 accessControlEnabled === false 时,跳过 SSO 认证和权限校验
|
|
49
|
+
*/
|
|
50
|
+
export const isPageAuthFree = (pathname: string): boolean => {
|
|
51
|
+
const page = findPageByPath(getPages(), pathname);
|
|
52
|
+
return page?.accessControlEnabled === false;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const findPageByPath = (
|
|
56
|
+
pages: PublicPageItem[],
|
|
57
|
+
pathname: string,
|
|
58
|
+
): PublicPageItem | undefined => {
|
|
59
|
+
let exact: PublicPageItem | undefined;
|
|
60
|
+
let bestWildcard: { page: PublicPageItem; basePath: string } | undefined;
|
|
61
|
+
|
|
62
|
+
for (const page of pages) {
|
|
63
|
+
if (!page.enabled) continue;
|
|
64
|
+
|
|
65
|
+
if (page.route === pathname) {
|
|
66
|
+
exact = page;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (page.route.endsWith('/*')) {
|
|
71
|
+
const basePath = page.route.slice(0, -2);
|
|
72
|
+
if (pathname === basePath || pathname.startsWith(`${basePath}/`)) {
|
|
73
|
+
if (!bestWildcard || basePath.length > bestWildcard.basePath.length) {
|
|
74
|
+
bestWildcard = { page, basePath };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return exact || bestWildcard?.page;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export interface MenuFilterOptions {
|
|
84
|
+
/** 是否是超级用户 */
|
|
85
|
+
isSuperuser?: boolean | number;
|
|
86
|
+
/** 允许访问的菜单路径列表(白名单) */
|
|
87
|
+
sideMenus?: string[];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export const isSuperuserUser = (value?: boolean | number): boolean => {
|
|
91
|
+
return value === true || value === 1;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const buildMenuPath = (parentPath: string, name: string): string => {
|
|
95
|
+
return parentPath ? `${parentPath}.${name}` : name;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// ===== 多语言菜单标识符工具函数 =====
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 判断当前是否为中文语言环境
|
|
102
|
+
* 基于 localStorage 的 umi_locale 配置
|
|
103
|
+
*/
|
|
104
|
+
export const isChineseLocale = (): boolean => {
|
|
105
|
+
const locale = getCurrentLocale();
|
|
106
|
+
return locale === LOCALE.ZH_CN;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* 获取菜单标识符(用于构建权限路径)
|
|
111
|
+
* - 中文格式:优先使用 name,兜底使用 nameKey
|
|
112
|
+
* - 英文格式:优先使用 nameEn,兜底使用 nameKey
|
|
113
|
+
* @param item 菜单项
|
|
114
|
+
* @param isChinese 是否中文格式
|
|
115
|
+
*/
|
|
116
|
+
export const getMenuIdentifier = (
|
|
117
|
+
item: MenuItem,
|
|
118
|
+
isChinese: boolean,
|
|
119
|
+
): string => {
|
|
120
|
+
if (isChinese) {
|
|
121
|
+
return item.name || item.nameKey || '';
|
|
122
|
+
}
|
|
123
|
+
return item.nameEn || item.nameKey || '';
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 检查菜单路径是否允许访问(白名单逻辑)
|
|
128
|
+
* - 免权限校验路由,始终允许访问
|
|
129
|
+
* - 超级用户可以访问所有菜单
|
|
130
|
+
* - 非超级用户不能访问 adminOnly 菜单
|
|
131
|
+
* - 菜单路径在 sideMenus 中,或是 sideMenus 中某项的前缀(父级菜单)
|
|
132
|
+
*/
|
|
133
|
+
const isMenuAllowed = (
|
|
134
|
+
menuPath: string,
|
|
135
|
+
item: MenuItem,
|
|
136
|
+
options: MenuFilterOptions,
|
|
137
|
+
): boolean => {
|
|
138
|
+
// 关闭权限控制时,所有菜单都允许访问
|
|
139
|
+
if (isAuthDisabled()) return true;
|
|
140
|
+
|
|
141
|
+
// 免权限校验路由,始终允许访问
|
|
142
|
+
const itemRoute = item.path ?? getMenuPage(item)?.route;
|
|
143
|
+
if (itemRoute && isNoPermissionRoute(itemRoute)) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 超级用户可以访问所有菜单
|
|
148
|
+
if (isSuperuserUser(options.isSuperuser)) return true;
|
|
149
|
+
|
|
150
|
+
// 非超级用户不能访问 adminOnly 菜单
|
|
151
|
+
if (item.adminOnly) return false;
|
|
152
|
+
|
|
153
|
+
const sideMenus = options.sideMenus || [];
|
|
154
|
+
|
|
155
|
+
// 如果没有配置 sideMenus,非超级用户没有任何菜单权限
|
|
156
|
+
if (sideMenus.length === 0) return false;
|
|
157
|
+
|
|
158
|
+
// 检查是否在白名单中(精确匹配或前缀匹配)
|
|
159
|
+
return sideMenus.some((allowedPath) => {
|
|
160
|
+
// 精确匹配:菜单路径完全等于白名单中的路径
|
|
161
|
+
if (menuPath === allowedPath) return true;
|
|
162
|
+
// 前缀匹配:白名单路径以菜单路径开头(说明菜单是父级)
|
|
163
|
+
if (allowedPath.startsWith(menuPath + '.')) return true;
|
|
164
|
+
return false;
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* 根据权限过滤菜单项(白名单逻辑)
|
|
170
|
+
*/
|
|
171
|
+
export const filterMenuItems = (
|
|
172
|
+
items: MenuItem[],
|
|
173
|
+
options: MenuFilterOptions = {},
|
|
174
|
+
parentPath = '',
|
|
175
|
+
): MenuItem[] => {
|
|
176
|
+
// 根据当前语言环境判断菜单标识符格式
|
|
177
|
+
const isChinese = isChineseLocale();
|
|
178
|
+
return items
|
|
179
|
+
.filter((item) => item.enabled)
|
|
180
|
+
.map((item) => {
|
|
181
|
+
const menuPath = buildMenuPath(
|
|
182
|
+
parentPath,
|
|
183
|
+
getMenuIdentifier(item, isChinese),
|
|
184
|
+
);
|
|
185
|
+
const isAllowed = isMenuAllowed(menuPath, item, options);
|
|
186
|
+
|
|
187
|
+
// 递归处理子菜单
|
|
188
|
+
const nextChildren = item.children?.length
|
|
189
|
+
? filterMenuItems(item.children, options, menuPath)
|
|
190
|
+
: [];
|
|
191
|
+
|
|
192
|
+
// 分组类型:如果没有子菜单,不显示
|
|
193
|
+
if (item.type === 'group' && nextChildren.length === 0) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 当前菜单不允许访问
|
|
198
|
+
if (!isAllowed) {
|
|
199
|
+
// 但如果有允许的子菜单,仍需显示当前菜单作为容器
|
|
200
|
+
if (nextChildren.length > 0) {
|
|
201
|
+
return { ...item, children: nextChildren };
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 当前菜单允许访问
|
|
207
|
+
const hasChildren = (item.children?.length || 0) > 0;
|
|
208
|
+
if (!hasChildren) {
|
|
209
|
+
return item;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 有子菜单但过滤后为空,不显示
|
|
213
|
+
if (nextChildren.length === 0) {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { ...item, children: nextChildren };
|
|
218
|
+
})
|
|
219
|
+
.filter((item): item is MenuItem => Boolean(item));
|
|
10
220
|
};
|
|
11
221
|
|
|
12
222
|
/**
|
|
13
223
|
* 判断页面的加载类型
|
|
14
224
|
* 有 htmlUrl 或 jsUrls 时使用 qiankun 微应用加载
|
|
15
225
|
*/
|
|
16
|
-
const getLoadType = (
|
|
226
|
+
const getLoadType = (
|
|
227
|
+
page: PublicPageItem | null | undefined,
|
|
228
|
+
): 'internal' | 'microapp' => {
|
|
17
229
|
if (!page) return 'internal';
|
|
18
|
-
|
|
19
|
-
// 有 htmlUrl 或 jsUrls 使用 qiankun 微应用加载
|
|
20
230
|
if (page.htmlUrl || (page.jsUrls && page.jsUrls.length > 0)) {
|
|
21
231
|
return 'microapp';
|
|
22
232
|
}
|
|
23
|
-
|
|
24
|
-
// 否则使用内部路由
|
|
25
233
|
return 'internal';
|
|
26
234
|
};
|
|
27
235
|
|
|
@@ -29,60 +237,102 @@ const getLoadType = (page: MenuItem['page']): 'internal' | 'microapp' => {
|
|
|
29
237
|
* 获取微应用入口 URL
|
|
30
238
|
* 优先使用 htmlUrl,其次使用 jsUrls[0]
|
|
31
239
|
*/
|
|
32
|
-
const getEntry = (
|
|
240
|
+
const getEntry = (
|
|
241
|
+
page: PublicPageItem | null | undefined,
|
|
242
|
+
): string | undefined => {
|
|
33
243
|
if (!page) return undefined;
|
|
34
244
|
return page.htmlUrl || page.jsUrls?.[0] || undefined;
|
|
35
245
|
};
|
|
36
246
|
|
|
247
|
+
/**
|
|
248
|
+
* 获取菜单标识符(默认版本,用于不需要权限匹配的场景)
|
|
249
|
+
* 优先使用 nameKey,兜底使用 name
|
|
250
|
+
*/
|
|
251
|
+
export const getMenuIdentifierDefault = (item: MenuItem): string => {
|
|
252
|
+
return item.nameKey || item.name;
|
|
253
|
+
};
|
|
254
|
+
|
|
37
255
|
/**
|
|
38
256
|
* 递归提取所有路由配置
|
|
39
257
|
*/
|
|
40
258
|
export const extractRoutes = (
|
|
41
259
|
items: MenuItem[],
|
|
42
260
|
routes: ParsedRoute[] = [],
|
|
261
|
+
parentPath = '',
|
|
43
262
|
): ParsedRoute[] => {
|
|
44
263
|
for (const item of items) {
|
|
45
264
|
if (!item.enabled) continue;
|
|
46
265
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
266
|
+
const menuPath = buildMenuPath(parentPath, getMenuIdentifierDefault(item));
|
|
267
|
+
|
|
268
|
+
if (item.type === 'page' && item.pageId) {
|
|
269
|
+
const page = getMenuPage(item);
|
|
270
|
+
if (page && page.enabled) {
|
|
271
|
+
routes.push({
|
|
272
|
+
path: page.route,
|
|
273
|
+
base: page.base || '/',
|
|
274
|
+
name: item.name,
|
|
275
|
+
nameEn: item.nameEn,
|
|
276
|
+
icon: item.icon,
|
|
277
|
+
loadType: getLoadType(page),
|
|
278
|
+
entry: getEntry(page),
|
|
279
|
+
pageConfig: page,
|
|
280
|
+
});
|
|
281
|
+
}
|
|
57
282
|
}
|
|
58
283
|
|
|
59
284
|
if (item.children && item.children.length > 0) {
|
|
60
|
-
extractRoutes(item.children, routes);
|
|
285
|
+
extractRoutes(item.children, routes, menuPath);
|
|
61
286
|
}
|
|
62
287
|
}
|
|
63
288
|
|
|
64
289
|
return routes;
|
|
65
290
|
};
|
|
66
291
|
|
|
292
|
+
/**
|
|
293
|
+
* 获取菜单项的显示名称
|
|
294
|
+
* 优先级:name/nameEn > nameKey(走 intl 国际化) > 兜底 name
|
|
295
|
+
*/
|
|
296
|
+
export const getMenuLabel = (
|
|
297
|
+
fields: { name?: string; nameEn?: string; nameKey?: string },
|
|
298
|
+
isChinese: boolean,
|
|
299
|
+
): string => {
|
|
300
|
+
const directName = isChinese ? fields.name : fields.nameEn;
|
|
301
|
+
if (directName) return directName;
|
|
302
|
+
|
|
303
|
+
if (fields.nameKey) {
|
|
304
|
+
const intl = getIntl();
|
|
305
|
+
return intl.formatMessage({
|
|
306
|
+
id: fields.nameKey,
|
|
307
|
+
defaultMessage: fields.nameKey,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return fields.name || '';
|
|
312
|
+
};
|
|
313
|
+
|
|
67
314
|
/**
|
|
68
315
|
* 将菜单数据转换为菜单组件需要的格式
|
|
69
316
|
*/
|
|
70
317
|
export const parseMenuItems = (items: MenuItem[]): ParsedMenuItem[] => {
|
|
318
|
+
const isChinese = isChineseLocale();
|
|
319
|
+
|
|
71
320
|
return items
|
|
321
|
+
|
|
72
322
|
.filter((item) => item.enabled)
|
|
73
323
|
.sort((a, b) => a.sortOrder - b.sortOrder)
|
|
74
324
|
.map((item) => {
|
|
75
325
|
const parsed: ParsedMenuItem = {
|
|
76
326
|
key: String(item.id),
|
|
77
|
-
label: item
|
|
327
|
+
label: getMenuLabel(item, isChinese),
|
|
78
328
|
icon: item.icon,
|
|
79
329
|
type: item.type,
|
|
80
330
|
};
|
|
81
331
|
|
|
82
332
|
if (item.type === 'link' && item.path) {
|
|
83
333
|
parsed.path = item.path;
|
|
84
|
-
} else if (item.type === 'page'
|
|
85
|
-
parsed.path = item.
|
|
334
|
+
} else if (item.type === 'page') {
|
|
335
|
+
parsed.path = item.path ?? getMenuPage(item)?.route;
|
|
86
336
|
}
|
|
87
337
|
|
|
88
338
|
if (item.children && item.children.length > 0) {
|
|
@@ -93,6 +343,10 @@ export const parseMenuItems = (items: MenuItem[]): ParsedMenuItem[] => {
|
|
|
93
343
|
});
|
|
94
344
|
};
|
|
95
345
|
|
|
346
|
+
/** 去除尾部斜杠(根路径 "/" 保持不变) */
|
|
347
|
+
const stripTrailingSlash = (path: string): string =>
|
|
348
|
+
path.length > 1 && path.endsWith('/') ? path.slice(0, -1) : path;
|
|
349
|
+
|
|
96
350
|
/**
|
|
97
351
|
* 根据路径查找对应的路由配置
|
|
98
352
|
*/
|
|
@@ -100,18 +354,19 @@ export const findRouteByPath = (
|
|
|
100
354
|
routes: ParsedRoute[],
|
|
101
355
|
pathname: string,
|
|
102
356
|
): ParsedRoute | undefined => {
|
|
357
|
+
const normalizedPathname = stripTrailingSlash(pathname);
|
|
103
358
|
let exact: ParsedRoute | undefined;
|
|
104
359
|
let bestWildcard: { route: ParsedRoute; basePath: string } | undefined;
|
|
105
360
|
|
|
106
361
|
for (const route of routes) {
|
|
107
|
-
if (route.path ===
|
|
362
|
+
if (stripTrailingSlash(route.path) === normalizedPathname) {
|
|
108
363
|
exact = route;
|
|
109
364
|
continue;
|
|
110
365
|
}
|
|
111
366
|
|
|
112
367
|
if (route.path.endsWith('/*')) {
|
|
113
368
|
const basePath = route.path.slice(0, -2);
|
|
114
|
-
if (
|
|
369
|
+
if (normalizedPathname === basePath || normalizedPathname.startsWith(`${basePath}/`)) {
|
|
115
370
|
if (!bestWildcard || basePath.length > bestWildcard.basePath.length) {
|
|
116
371
|
bestWildcard = { route, basePath };
|
|
117
372
|
}
|
|
@@ -10,8 +10,14 @@ export interface PageConfig {
|
|
|
10
10
|
id: number;
|
|
11
11
|
/** 页面名称 */
|
|
12
12
|
name: string;
|
|
13
|
+
/** 英文名称(用于英文环境权限匹配) */
|
|
14
|
+
nameEn?: string;
|
|
15
|
+
/** 菜单唯一标识符(用于权限匹配的兜底) */
|
|
16
|
+
nameKey?: string;
|
|
13
17
|
/** 路由路径 */
|
|
14
18
|
route: string;
|
|
19
|
+
/** 路由前缀路径 */
|
|
20
|
+
base: string;
|
|
15
21
|
/** 是否启用 */
|
|
16
22
|
enabled: boolean;
|
|
17
23
|
/** 微应用 HTML 入口 URL (用于 qiankun 加载) */
|
|
@@ -24,6 +30,12 @@ export interface PageConfig {
|
|
|
24
30
|
prefixPath: string;
|
|
25
31
|
/** 路由匹配模式 */
|
|
26
32
|
routeMode: PageRouteMode;
|
|
33
|
+
/** 是否仅超级管理员可见 */
|
|
34
|
+
adminOnly?: boolean;
|
|
35
|
+
/** 是否开启权限控制 */
|
|
36
|
+
accessControlEnabled: boolean;
|
|
37
|
+
/** 路由权限标识(用于匹配 sideMenus) */
|
|
38
|
+
routeKey: string | null;
|
|
27
39
|
/** 关联的主文档 ID */
|
|
28
40
|
mainDocumentId: number;
|
|
29
41
|
/** 所属工作空间子域名 */
|
|
@@ -40,6 +52,15 @@ export interface PageConfig {
|
|
|
40
52
|
updatedAt: string;
|
|
41
53
|
}
|
|
42
54
|
|
|
55
|
+
/**
|
|
56
|
+
* 公共页面配置 - 从 window.__MICO_PAGES__ 获取
|
|
57
|
+
* PageConfig 的子集,用于动态路由注册和页面级权限控制
|
|
58
|
+
*/
|
|
59
|
+
export type PublicPageItem = Omit<
|
|
60
|
+
PageConfig,
|
|
61
|
+
'workspaceSubdomain' | 'publishedBy' | 'versions' | 'createdAt' | 'updatedAt'
|
|
62
|
+
>;
|
|
63
|
+
|
|
43
64
|
/**
|
|
44
65
|
* 菜单项类型
|
|
45
66
|
*/
|
|
@@ -51,6 +72,8 @@ export type MenuItemType = 'group' | 'page' | 'link';
|
|
|
51
72
|
export interface MenuItem {
|
|
52
73
|
id: number;
|
|
53
74
|
name: string;
|
|
75
|
+
nameEn?: string;
|
|
76
|
+
nameKey?: string;
|
|
54
77
|
type: MenuItemType;
|
|
55
78
|
/** 路由路径或外链地址 */
|
|
56
79
|
path: string | null;
|
|
@@ -59,8 +82,8 @@ export interface MenuItem {
|
|
|
59
82
|
enabled: boolean;
|
|
60
83
|
sortOrder: number;
|
|
61
84
|
pageId: number | null;
|
|
62
|
-
/**
|
|
63
|
-
|
|
85
|
+
/** 是否仅超级管理员可见 */
|
|
86
|
+
adminOnly?: boolean;
|
|
64
87
|
children: MenuItem[];
|
|
65
88
|
}
|
|
66
89
|
|
|
@@ -69,15 +92,21 @@ export interface MenuItem {
|
|
|
69
92
|
*/
|
|
70
93
|
export interface ParsedRoute {
|
|
71
94
|
path: string;
|
|
72
|
-
/**
|
|
95
|
+
/** 微应用在主应用中的挂载前缀(来自页面配置 base,可与 path 不同) */
|
|
96
|
+
base: string;
|
|
97
|
+
/** 菜单显示名称(中文) */
|
|
73
98
|
name: string;
|
|
99
|
+
/** 菜单显示名称(英文) */
|
|
100
|
+
nameEn?: string;
|
|
101
|
+
/** 国际化 key */
|
|
102
|
+
nameKey?: string;
|
|
74
103
|
icon: string;
|
|
75
104
|
/** 加载类型: internal(内部路由) | microapp(qiankun微应用) */
|
|
76
105
|
loadType: 'internal' | 'microapp';
|
|
77
106
|
/** 微应用入口 URL (htmlUrl 或 jsUrls[0]) */
|
|
78
107
|
entry?: string;
|
|
79
108
|
/** 原始页面配置 */
|
|
80
|
-
pageConfig?:
|
|
109
|
+
pageConfig?: PublicPageItem;
|
|
81
110
|
}
|
|
82
111
|
|
|
83
112
|
/**
|
|
@@ -97,7 +126,7 @@ export interface ParsedMenuItem {
|
|
|
97
126
|
*/
|
|
98
127
|
export interface MicroAppProps {
|
|
99
128
|
/** 主应用标识 */
|
|
100
|
-
mainApp: '
|
|
129
|
+
mainApp: 'common-web';
|
|
101
130
|
/** 共享的 request 实例,子应用可直接使用 */
|
|
102
131
|
request: <T = any>(url: string, options?: any) => Promise<T>;
|
|
103
132
|
}
|
|
@@ -118,6 +147,8 @@ export type WorkspaceStatus = 'active' | 'inactive' | 'pending';
|
|
|
118
147
|
export interface WorkspaceConfig {
|
|
119
148
|
/** 关联的应用 ID(可能为空) */
|
|
120
149
|
appId: number | null;
|
|
150
|
+
/** CAS 服务端登录 URL,用于 SSO 外部登录跳转 */
|
|
151
|
+
casServerLoginUrl?: string;
|
|
121
152
|
/** 创建时间 */
|
|
122
153
|
createdAt: string;
|
|
123
154
|
/** 创建者 ID */
|
|
@@ -151,10 +182,43 @@ export interface WorkspaceConfig {
|
|
|
151
182
|
*/
|
|
152
183
|
declare global {
|
|
153
184
|
interface Window {
|
|
185
|
+
__MICO_PAGES__?: PublicPageItem[];
|
|
154
186
|
__MICO_MENUS__?: MenuItem[];
|
|
155
187
|
__MICO_CONFIG__?: {
|
|
156
188
|
appName?: string;
|
|
189
|
+
/** 站点/产品标题,用于登录页标题文案、logo alt 等 */
|
|
190
|
+
title?: string;
|
|
191
|
+
/** 登录页 logo 图片地址,不配置时使用应用内置 logo */
|
|
192
|
+
logo?: string;
|
|
193
|
+
appId?: string;
|
|
157
194
|
apiBaseUrl?: string;
|
|
195
|
+
proxySuffix?: string;
|
|
196
|
+
loginEndpoint?: string;
|
|
197
|
+
refreshEndpoint?: string;
|
|
198
|
+
externalLoginPath?: string;
|
|
199
|
+
/** 请求客户端配置覆盖,与 config 中 defaultClientOptions 同名字段 */
|
|
200
|
+
defaultClientOptions?: Partial<
|
|
201
|
+
import('../request/types').RequestClientOptions
|
|
202
|
+
>;
|
|
203
|
+
/** 默认重定向路径,访问 "/" 时自动跳转到此路径 */
|
|
204
|
+
defaultPath?: string;
|
|
205
|
+
/**
|
|
206
|
+
* 免认证路由列表(跳过 SSO 登录)
|
|
207
|
+
* 支持精确匹配和前缀匹配(以 /* 结尾)
|
|
208
|
+
*/
|
|
209
|
+
noAuthRouteList?: string[];
|
|
210
|
+
/**
|
|
211
|
+
* 免权限校验路由列表(跳过菜单权限检查)
|
|
212
|
+
* 支持精确匹配和前缀匹配(以 /* 结尾)
|
|
213
|
+
* 注意:如果同时需要跳过 SSO,需同时配置 noAuthRouteList
|
|
214
|
+
*/
|
|
215
|
+
noPermissionRouteList?: string[];
|
|
216
|
+
/** 动态配置的不显示布局路由列表,支持精确匹配和前缀匹配(以 /* 结尾) */
|
|
217
|
+
noLayoutRouteList?: string[];
|
|
218
|
+
/** 关闭权限控制(菜单全部显示,路由不校验权限) */
|
|
219
|
+
disableAuth?: boolean;
|
|
220
|
+
/** 获取时区列表的 API 地址 */
|
|
221
|
+
timezoneListUrl?: string;
|
|
158
222
|
[key: string]: unknown;
|
|
159
223
|
};
|
|
160
224
|
__MICO_WORKSPACE__?: WorkspaceConfig | null;
|
|
@@ -32,6 +32,40 @@ export type {
|
|
|
32
32
|
MicroAppErrorSource,
|
|
33
33
|
} from './types';
|
|
34
34
|
|
|
35
|
+
|
|
36
|
+
// ============================================
|
|
37
|
+
// 微应用名称生成
|
|
38
|
+
// ============================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 从 entry URL 中提取微应用标识
|
|
42
|
+
* 同一个 entry 的所有路由使用相同的标识,避免频繁卸载/重载微应用
|
|
43
|
+
*
|
|
44
|
+
* 注意:使用完整的 origin + pathname 作为标识,避免同一 host 上多个子应用冲突
|
|
45
|
+
* 例如:http://localhost:8010/app1/ 和 http://localhost:8010/app2/ 会有不同的标识
|
|
46
|
+
*
|
|
47
|
+
* @param entry 微应用入口 URL
|
|
48
|
+
* @returns 微应用标识(字母数字和连字符组成)
|
|
49
|
+
*/
|
|
50
|
+
export const getAppNameFromEntry = (entry: string): string => {
|
|
51
|
+
try {
|
|
52
|
+
const url = new URL(entry, window.location.href);
|
|
53
|
+
// 使用 origin + pathname 作为标识,确保不同路径的子应用有不同标识
|
|
54
|
+
// 如 "localhost-8010" 或 "localhost-8010-app1"
|
|
55
|
+
const identifier = url.host + url.pathname;
|
|
56
|
+
return identifier
|
|
57
|
+
.replace(/[^a-zA-Z0-9]/g, '-')
|
|
58
|
+
.replace(/-+/g, '-')
|
|
59
|
+
.replace(/^-|-$/g, '');
|
|
60
|
+
} catch {
|
|
61
|
+
// fallback:使用 entry 的 hash
|
|
62
|
+
return entry
|
|
63
|
+
.replace(/[^a-zA-Z0-9]/g, '-')
|
|
64
|
+
.replace(/-+/g, '-')
|
|
65
|
+
.replace(/^-|-$/g, '');
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
35
69
|
// ============================================
|
|
36
70
|
// 环境检测
|
|
37
71
|
// ============================================
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 微应用预加载管理
|
|
3
|
+
*
|
|
4
|
+
* 策略:当前子应用挂载成功后,再逐个预加载其他子应用
|
|
5
|
+
* 避免与当前加载的子应用竞争网络带宽
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { prefetchApps } from 'qiankun';
|
|
9
|
+
import { extractRoutes } from './menu';
|
|
10
|
+
import { getMenus } from './portal-data';
|
|
11
|
+
import { getAppNameFromEntry } from './micro';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 是否启用微应用预加载
|
|
15
|
+
* - 可通过 URL 参数 ?prefetch=false 禁用(方便调试加载时序问题)
|
|
16
|
+
* - 可通过 localStorage.setItem('DISABLE_MICRO_APP_PREFETCH', 'true') 禁用
|
|
17
|
+
* - 默认启用
|
|
18
|
+
*/
|
|
19
|
+
export const isPrefetchEnabled = (): boolean => {
|
|
20
|
+
if (typeof window === 'undefined') return false;
|
|
21
|
+
|
|
22
|
+
// URL 参数优先级最高
|
|
23
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
24
|
+
const prefetchParam = urlParams.get('prefetch');
|
|
25
|
+
if (prefetchParam === 'false') {
|
|
26
|
+
console.log('[Prefetch] Disabled via URL parameter');
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// localStorage 开关
|
|
31
|
+
if (localStorage.getItem('DISABLE_MICRO_APP_PREFETCH') === 'true') {
|
|
32
|
+
console.log('[Prefetch] Disabled via localStorage');
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return true;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** 已预加载的应用 entry 集合 */
|
|
40
|
+
const prefetchedApps = new Set<string>();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 获取所有需要预加载的微应用(排除已预加载的)
|
|
44
|
+
*/
|
|
45
|
+
export const getMicroAppsForPrefetch = (
|
|
46
|
+
excludeEntry?: string,
|
|
47
|
+
): Array<{ name: string; entry: string }> => {
|
|
48
|
+
try {
|
|
49
|
+
const menus = getMenus();
|
|
50
|
+
const routes = extractRoutes(menus);
|
|
51
|
+
|
|
52
|
+
return routes
|
|
53
|
+
.filter(
|
|
54
|
+
(route) =>
|
|
55
|
+
route.loadType === 'microapp' &&
|
|
56
|
+
route.entry &&
|
|
57
|
+
route.entry !== excludeEntry &&
|
|
58
|
+
!prefetchedApps.has(route.entry),
|
|
59
|
+
)
|
|
60
|
+
.map((route) => ({
|
|
61
|
+
name: getAppNameFromEntry(route.entry!),
|
|
62
|
+
entry: route.entry!,
|
|
63
|
+
}));
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn('[Prefetch] Failed to get micro apps:', error);
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 预加载微应用(低优先级,在浏览器空闲时并发预加载)
|
|
72
|
+
* @param currentEntry 当前正在加载的应用 entry,会被排除
|
|
73
|
+
*/
|
|
74
|
+
export const prefetchMicroAppsLowPriority = (currentEntry?: string): void => {
|
|
75
|
+
if (!isPrefetchEnabled()) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const apps = getMicroAppsForPrefetch(currentEntry);
|
|
80
|
+
if (apps.length === 0) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 标记为已预加载,避免重复
|
|
85
|
+
apps.forEach((app) => prefetchedApps.add(app.entry));
|
|
86
|
+
|
|
87
|
+
console.log(
|
|
88
|
+
'[Prefetch] Will prefetch micro apps:',
|
|
89
|
+
apps.map((a) => a.name),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// 使用 requestIdleCallback 确保在浏览器空闲时执行,不阻塞当前渲染
|
|
93
|
+
const doPrefetch = () => {
|
|
94
|
+
prefetchApps(apps); // qiankun 会并发预加载所有应用
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if ('requestIdleCallback' in window) {
|
|
98
|
+
window.requestIdleCallback(doPrefetch, { timeout: 5000 });
|
|
99
|
+
} else {
|
|
100
|
+
setTimeout(doPrefetch, 100);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 标记应用已加载(避免重复预加载)
|
|
106
|
+
*/
|
|
107
|
+
export const markAppAsPrefetched = (entry: string): void => {
|
|
108
|
+
prefetchedApps.add(entry);
|
|
109
|
+
};
|