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.
Files changed (160) hide show
  1. package/README.md +199 -15
  2. package/bin/mico.js +232 -27
  3. package/generators/micro-react/index.js +200 -18
  4. package/generators/micro-react/meta.json +13 -0
  5. package/generators/micro-react/templates/.commitlintrc.js +1 -0
  6. package/generators/micro-react/templates/.cursor/rules/always-read-docs.mdc +14 -4
  7. package/generators/micro-react/templates/.cursor/rules/cicd-deploy.mdc +10 -8
  8. package/generators/micro-react/templates/.cursor/rules/coding-conventions.mdc +1 -1
  9. package/generators/micro-react/templates/.cursor/rules/development-guide.mdc +3 -4
  10. package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +38 -31
  11. package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +7 -4
  12. package/generators/micro-react/templates/.cursor/rules/theme-system.mdc +10 -12
  13. package/generators/micro-react/templates/.eslintrc.js +25 -1
  14. package/generators/micro-react/templates/AGENTS.md +5 -2
  15. package/generators/micro-react/templates/CICD/before_build.sh +76 -0
  16. package/generators/micro-react/templates/CICD/start_dev.sh +27 -3
  17. package/generators/micro-react/templates/CICD/start_prod.sh +26 -3
  18. package/generators/micro-react/templates/CICD/start_test.sh +28 -3
  19. package/generators/micro-react/templates/CICD/wangsu_fresh_dev.sh +4 -4
  20. package/generators/micro-react/templates/CICD/wangsu_fresh_prod.sh +4 -4
  21. package/generators/micro-react/templates/CICD/wangsu_fresh_test.sh +4 -4
  22. package/generators/micro-react/templates/CLAUDE.md +16 -9
  23. package/generators/micro-react/templates/README.md +42 -4
  24. package/generators/micro-react/templates/_gitignore +4 -0
  25. package/generators/micro-react/templates/_npmrc +4 -0
  26. package/generators/micro-react/templates/apps/layout/config/config.dev.ts +33 -17
  27. package/generators/micro-react/templates/apps/layout/config/config.prod.development.ts +24 -29
  28. package/generators/micro-react/templates/apps/layout/config/config.prod.testing.ts +25 -6
  29. package/generators/micro-react/templates/apps/layout/config/config.prod.ts +16 -7
  30. package/generators/micro-react/templates/apps/layout/config/config.ts +27 -4
  31. package/generators/micro-react/templates/apps/layout/config/routes.ts +10 -5
  32. 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
  33. package/generators/micro-react/templates/apps/layout/docs/arch-/350/257/267/346/261/202/346/250/241/345/235/227.md +1 -1
  34. package/generators/micro-react/templates/apps/layout/docs/common-intl.md +372 -0
  35. 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
  36. package/generators/micro-react/templates/apps/layout/docs/feature-404/351/241/265/351/235/242.md +103 -0
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. package/generators/micro-react/templates/apps/layout/docs/utils-timezone.md +324 -0
  43. package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +81 -61
  44. package/generators/micro-react/templates/apps/layout/mock/menus.ts +114 -4
  45. package/generators/micro-react/templates/apps/layout/mock/pages.ts +86 -0
  46. package/generators/micro-react/templates/apps/layout/package.json +7 -4
  47. package/generators/micro-react/templates/apps/layout/src/app.tsx +122 -83
  48. package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +3 -0
  49. package/generators/micro-react/templates/apps/layout/src/common/helpers.ts +177 -0
  50. package/generators/micro-react/templates/apps/layout/src/common/locale.ts +22 -17
  51. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +283 -28
  52. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +69 -5
  53. package/generators/micro-react/templates/apps/layout/src/common/micro/index.ts +34 -0
  54. package/generators/micro-react/templates/apps/layout/src/common/micro-prefetch.ts +109 -0
  55. package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +45 -0
  56. package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +72 -10
  57. package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +2 -2
  58. package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +31 -3
  59. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +29 -11
  60. package/generators/micro-react/templates/apps/layout/src/common/request/url-resolver.ts +23 -8
  61. package/generators/micro-react/templates/apps/layout/src/common/route-guard.ts +345 -0
  62. package/generators/micro-react/templates/apps/layout/src/common/theme.ts +2 -4
  63. package/generators/micro-react/templates/apps/layout/src/common/upload/oss.ts +3 -4
  64. package/generators/micro-react/templates/apps/layout/src/common/upload/types.ts +1 -1
  65. package/generators/micro-react/templates/apps/layout/src/common/uploadFiles.ts +1 -1
  66. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.less +8 -3
  67. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +25 -8
  68. package/generators/micro-react/templates/apps/layout/src/components/HeaderDropdown/index.tsx +20 -0
  69. package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +5 -6
  70. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +21 -6
  71. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +83 -107
  72. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/micro-app-manager.ts +569 -0
  73. package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +383 -0
  74. package/generators/micro-react/templates/apps/layout/src/components/RightContent/avatar-dropdown.less +35 -0
  75. package/generators/micro-react/templates/apps/layout/src/components/RightContent/index.ts +2 -0
  76. package/generators/micro-react/templates/apps/layout/src/constants/index.ts +170 -6
  77. package/generators/micro-react/templates/apps/layout/src/global.less +19 -6
  78. package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +3 -2
  79. package/generators/micro-react/templates/apps/layout/src/hooks/useRoutePermissionRefresh.ts +72 -0
  80. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less +3 -1
  81. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +10 -55
  82. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +34 -4
  83. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +33 -9
  84. package/generators/micro-react/templates/apps/layout/src/layouts/index.less +84 -13
  85. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +178 -47
  86. package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +12 -0
  87. package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +12 -0
  88. package/generators/micro-react/templates/apps/layout/src/pages/403/index.tsx +34 -0
  89. package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +78 -0
  90. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +3 -0
  91. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +7 -1
  92. package/generators/micro-react/templates/apps/layout/src/pages/User/Login/index.less +1 -1
  93. package/generators/micro-react/templates/apps/layout/src/pages/User/Login/index.tsx +9 -5
  94. package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +1 -1
  95. package/generators/micro-react/templates/apps/layout/src/services/config/index.ts +63 -0
  96. package/generators/micro-react/templates/apps/layout/src/services/config/type.ts +30 -0
  97. package/generators/micro-react/templates/apps/layout/src/services/user.ts +29 -2
  98. package/generators/micro-react/templates/apps/layout/tailwind.config.js +3 -0
  99. package/generators/micro-react/templates/deployDesc.md +3 -3
  100. package/generators/micro-react/templates/dev.preset.json +14 -0
  101. package/generators/micro-react/templates/docs/dev-preset.md +130 -0
  102. package/generators/micro-react/templates/package.json +21 -6
  103. package/generators/micro-react/templates/packages/common-intl/README.md +427 -0
  104. package/generators/micro-react/templates/packages/common-intl/package.json +34 -0
  105. package/generators/micro-react/templates/packages/common-intl/src/index.ts +7 -0
  106. package/generators/micro-react/templates/packages/common-intl/src/indexedDBUtils.ts +51 -0
  107. package/generators/micro-react/templates/packages/common-intl/src/intl.ts +50 -0
  108. package/generators/micro-react/templates/packages/common-intl/src/utils.ts +482 -0
  109. package/generators/micro-react/templates/packages/common-intl/tsconfig.json +22 -0
  110. package/generators/micro-react/templates/packages/common-intl/vite.config.ts +25 -0
  111. package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +45 -0
  112. package/generators/micro-react/templates/scripts/collect-dist.js +10 -0
  113. package/generators/micro-react/templates/scripts/dev-preset.js +265 -0
  114. package/generators/micro-react/templates/scripts/dev-preset.schema.json +39 -0
  115. package/generators/micro-react/templates/turbo.json +4 -1
  116. package/generators/subapp-react/index.js +326 -40
  117. package/generators/subapp-react/meta.json +10 -0
  118. package/generators/subapp-react/templates/homepage/.env +2 -1
  119. package/generators/subapp-react/templates/homepage/README.md +3 -3
  120. package/generators/subapp-react/templates/homepage/config/config.dev.ts +14 -7
  121. package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +16 -5
  122. package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +16 -5
  123. package/generators/subapp-react/templates/homepage/config/config.prod.ts +14 -5
  124. package/generators/subapp-react/templates/homepage/config/config.ts +27 -0
  125. package/generators/subapp-react/templates/homepage/config/routes.ts +2 -2
  126. package/generators/subapp-react/templates/homepage/mock/api.mock.ts +2 -2
  127. package/generators/subapp-react/templates/homepage/package.json +7 -4
  128. package/generators/subapp-react/templates/homepage/src/app.tsx +18 -27
  129. package/generators/subapp-react/templates/homepage/src/common/request.ts +29 -2
  130. package/generators/subapp-react/templates/homepage/src/global.less +6 -5
  131. package/generators/subapp-react/templates/homepage/src/pages/index.less +3 -3
  132. package/generators/subapp-react/templates/homepage/src/pages/index.tsx +99 -60
  133. package/generators/subapp-react/templates/homepage/src/styles/theme.less +1 -1
  134. package/generators/subapp-umd/ignore-list.json +5 -0
  135. package/generators/subapp-umd/index.js +309 -0
  136. package/generators/subapp-umd/meta.json +11 -0
  137. package/generators/subapp-umd/templates/README.md +94 -0
  138. package/generators/subapp-umd/templates/package.json +35 -0
  139. package/generators/subapp-umd/templates/public/index.html +34 -0
  140. package/generators/subapp-umd/templates/src/App.less +15 -0
  141. package/generators/subapp-umd/templates/src/App.tsx +13 -0
  142. package/generators/subapp-umd/templates/src/index.ts +2 -0
  143. package/generators/subapp-umd/templates/tsconfig.json +27 -0
  144. package/generators/subapp-umd/templates/webpack.config.js +70 -0
  145. package/lib/utils.js +332 -2
  146. package/package.json +15 -2
  147. package/generators/micro-react/templates/apps/layout/mock/menus.json +0 -100
  148. package/generators/micro-react/templates/apps/layout/src/common/constants.ts +0 -38
  149. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/container-manager.ts +0 -202
  150. package/generators/micro-react/templates/packages/shared-styles/README.md +0 -124
  151. package/generators/micro-react/templates/packages/shared-styles/arco-design-mobile-override.less +0 -91
  152. package/generators/micro-react/templates/packages/shared-styles/arco-override.less +0 -119
  153. package/generators/micro-react/templates/packages/shared-styles/index.d.ts +0 -44
  154. package/generators/micro-react/templates/packages/shared-styles/index.less +0 -13
  155. package/generators/micro-react/templates/packages/shared-styles/package.json +0 -30
  156. package/generators/micro-react/templates/packages/shared-styles/theme-inject.less +0 -10
  157. package/generators/micro-react/templates/packages/shared-styles/themes/dark/custom-var.less +0 -290
  158. package/generators/micro-react/templates/packages/shared-styles/themes/normal/custom-var.less +0 -269
  159. package/generators/micro-react/templates/packages/shared-styles/variables-only.less +0 -433
  160. package/generators/micro-react/templates/packages/shared-styles/variables.less +0 -452
@@ -1,27 +1,235 @@
1
- import type { MenuItem, ParsedMenuItem, ParsedRoute } from './types';
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
- * 获取 window 上挂载的菜单数据
13
+ * 从页面数据提取路由配置
5
14
  */
6
- export const getWindowMenus = (): MenuItem[] => {
7
- if (typeof window === 'undefined') return [];
8
- const menus = window.__MICO_MENUS__;
9
- return Array.isArray(menus) ? menus : [];
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 = (page: MenuItem['page']): 'internal' | 'microapp' => {
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 = (page: MenuItem['page']): string | undefined => {
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
- if (item.type === 'page' && item.page && item.page.enabled) {
48
- const loadType = getLoadType(item.page);
49
- routes.push({
50
- path: item.page.route,
51
- name: item.name,
52
- icon: item.icon,
53
- loadType,
54
- entry: getEntry(item.page),
55
- pageConfig: item.page,
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.name,
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' && item.page) {
85
- parsed.path = item.page.route;
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 === pathname) {
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 (pathname === basePath || pathname.startsWith(`${basePath}/`)) {
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
- /** 页面配置(type=page 时存在) */
63
- page: PageConfig | null;
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?: 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: '<%= projectName %>';
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
+ };