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
@@ -3,6 +3,7 @@
3
3
  "private": true,
4
4
  "author": "Easton <easton@micous.com>",
5
5
  "scripts": {
6
+ "check-types": "tsc --noEmit",
6
7
  "build": "npm run build:production",
7
8
  "build:development": "cross-env UMI_ENV=development max build",
8
9
  "build:production": "cross-env UMI_ENV=production max build",
@@ -21,11 +22,9 @@
21
22
  "test": "cross-env UMI_ENV=test max dev"
22
23
  },
23
24
  "dependencies": {
24
- "@arco-design/web-react": "^2.66.6",
25
- "<%= packageScope %>/shared-styles": "workspace:*",
25
+ "@mico-platform/ui": "<%= micoUiVersion %>",
26
+ "@mico-platform/theme": "<%= themeVersion %>",
26
27
  "@umijs/max": "^4.6.15",
27
- "babel-plugin-dynamic-import-node": "^2.3.3",
28
- "cross-env": "^10.1.0",
29
28
  "dayjs": "^1.11.19",
30
29
  "qiankun": "^2.10.16",
31
30
  "react": "^18.2.0",
@@ -35,6 +34,10 @@
35
34
  "devDependencies": {
36
35
  "@types/react": "^18.0.33",
37
36
  "@types/react-dom": "^18.0.11",
37
+ "@types/spark-md5": "^3.0.5",
38
+ "babel-plugin-dynamic-import-node": "^2.3.3",
39
+ "babel-plugin-import": "^1.13.8",
40
+ "cross-env": "^10.1.0",
38
41
  "lint-staged": "^13.2.0",
39
42
  "prettier": "^2.8.7",
40
43
  "prettier-plugin-organize-imports": "^3.2.2",
@@ -1,93 +1,85 @@
1
1
  import { history, type RequestConfig } from '@umijs/max';
2
- import { prefetchApps } from 'qiankun';
2
+ import { addGlobalUncaughtErrorHandler } from 'qiankun';
3
+ import { SentryErrorBoundary } from '@common-web/sentry';
3
4
  import { errorConfig } from './requestErrorConfig';
4
- // 解决「React19 中无法使用 Message/Notification」的问题。 @see https://github.com/arco-design/arco-design/issues/2900#issuecomment-2796571653
5
- import * as arco from '@arco-design/web-react';
6
- import '@arco-design/web-react/dist/css/arco.css';
7
- import '@arco-design/web-react/es/_util/react-19-adapter';
5
+ // @mico-platform/ui 暴露到 window,供子应用 externals 使用
6
+ import * as micoUI from '@mico-platform/ui';
8
7
  import React from 'react';
9
- import ReactDOM from 'react-dom/client';
8
+ import ReactDOM from 'react-dom';
10
9
 
11
10
  import { getStoredAuthToken } from './common/auth/auth-manager';
12
11
  import type { IUserInfo } from './common/auth/type';
13
12
  import { fetchUserInfo } from './services/user';
14
- import { extractRoutes, getWindowMenus } from './common/menu';
15
- import { ensureSsoSession } from './common/request/sso';
13
+ import { getDynamicRoutes, isPageAuthFree } from './common/menu';
16
14
  import {
17
15
  clearMicroAppProps,
18
16
  type IMicroAppProps,
19
17
  setMicroAppProps,
20
18
  } from './common/micro';
19
+ import {
20
+ clearRedirectCount,
21
+ ensureSsoSession,
22
+ handleAuthFailureRedirect,
23
+ } from './common/request/sso';
21
24
  import { initTheme } from './common/theme';
22
25
  import MicroAppLoader from './components/MicroAppLoader';
23
- import { NO_AUTH_ROUTE_LIST } from './constants';
26
+ import { isNoAuthRoute } from '@/constants';
27
+ import { getCurrentLocale } from '@/common/locale';
28
+ import '@mico-platform/theme/dist/css/theme.css';
24
29
  import './global.less';
25
30
 
26
- // ==================== 微应用预加载 ====================
27
- // 预加载所有微应用资源,避免快速切换时的竞态条件
28
- // 当资源已预加载时,loadMicroApp 的异步加载会快速完成,减少容器不存在的错误
31
+ // ==================== qiankun 全局错误处理 ====================
32
+ // 捕获子应用运行时未捕获的异常,防止页面崩溃
29
33
 
30
34
  /**
31
- * 是否启用微应用预加载
32
- * - 可通过 URL 参数 ?prefetch=false 禁用(方便调试加载时序问题)
33
- * - 可通过 localStorage.setItem('DISABLE_MICRO_APP_PREFETCH', 'true') 禁用
34
- * - 默认启用
35
+ * 注册 qiankun 全局未捕获错误处理器
36
+ * 处理以下场景:
37
+ * 1. 子应用 JS 运行时错误
38
+ * 2. 子应用生命周期钩子抛出的异常
39
+ * 3. 子应用资源加载失败
35
40
  */
36
- const isPrefetchEnabled = (): boolean => {
37
- if (typeof window === 'undefined') return false;
38
-
39
- // URL 参数优先级最高
40
- const urlParams = new URLSearchParams(window.location.search);
41
- const prefetchParam = urlParams.get('prefetch');
42
- if (prefetchParam === 'false') {
43
- console.log('[App] Prefetch disabled via URL parameter');
44
- return false;
45
- }
41
+ if (typeof window !== 'undefined') {
42
+ addGlobalUncaughtErrorHandler((event: Event | string) => {
43
+ // 提取错误信息
44
+ const error =
45
+ event instanceof ErrorEvent
46
+ ? event.error
47
+ : event instanceof PromiseRejectionEvent
48
+ ? event.reason
49
+ : event;
46
50
 
47
- // localStorage 开关
48
- if (localStorage.getItem('DISABLE_MICRO_APP_PREFETCH') === 'true') {
49
- console.log('[App] Prefetch disabled via localStorage');
50
- return false;
51
- }
51
+ const errorMessage =
52
+ error instanceof Error
53
+ ? error.message
54
+ : typeof error === 'string'
55
+ ? error
56
+ : 'Unknown micro-app error';
52
57
 
53
- return true;
54
- };
58
+ console.error('[qiankun] Global uncaught error:', {
59
+ error,
60
+ message: errorMessage,
61
+ type: event instanceof Event ? event.type : 'unknown',
62
+ });
55
63
 
56
- const prefetchMicroApps = () => {
57
- if (!isPrefetchEnabled()) {
58
- return;
59
- }
64
+ // 检查是否是子应用加载失败(资源 404 等)
65
+ // 只匹配明确的关键词,避免误判业务错误(如 "upload failed")
66
+ const isLoadError =
67
+ errorMessage.includes('Failed to fetch') ||
68
+ errorMessage.includes('Loading chunk') ||
69
+ errorMessage.includes('ChunkLoadError') ||
70
+ errorMessage.includes('Script error') ||
71
+ errorMessage.includes('Loading CSS chunk') ||
72
+ (event instanceof ErrorEvent && event.type === 'error');
60
73
 
61
- try {
62
- const menus = getWindowMenus();
63
- const routes = extractRoutes(menus);
64
-
65
- // 筛选出所有微应用路由
66
- const microApps = routes
67
- .filter((route) => route.loadType === 'microapp' && route.entry)
68
- .map((route) => ({
69
- name: route.path,
70
- entry: route.entry!,
71
- }));
72
-
73
- if (microApps.length > 0) {
74
- console.log('[App] Prefetching micro apps:', microApps);
75
- prefetchApps(microApps);
74
+ if (isLoadError) {
75
+ console.error(
76
+ '[qiankun] Micro-app resource loading failed. Please check if the micro-app server is running.',
77
+ );
76
78
  }
77
- } catch (error) {
78
- console.warn('[App] Failed to prefetch micro apps:', error);
79
- }
80
- };
81
79
 
82
- // 在页面加载后预加载微应用
83
- if (typeof window !== 'undefined') {
84
- // 使用 requestIdleCallback 在浏览器空闲时预加载,避免影响首屏渲染
85
- if ('requestIdleCallback' in window) {
86
- window.requestIdleCallback(() => prefetchMicroApps(), { timeout: 3000 });
87
- } else {
88
- // 降级方案:延迟 1 秒后预加载
89
- setTimeout(prefetchMicroApps, 1000);
90
- }
80
+ // 注意:这里不阻止错误冒泡,让控制台仍能显示原始错误
81
+ // 如果需要阻止,可以 return true
82
+ });
91
83
  }
92
84
 
93
85
  // ==================== 微前端共享依赖 ====================
@@ -97,12 +89,24 @@ if (typeof window !== 'undefined') {
97
89
  const win = window as unknown as Record<string, unknown>;
98
90
  win.React = React;
99
91
  win.ReactDOM = ReactDOM;
100
- win.arco = arco;
92
+ win.micoUI = micoUI;
101
93
  }
102
94
 
103
95
  // 初始化主题(在页面加载时立即执行,避免闪烁)
104
96
  initTheme();
105
97
 
98
+ // ==================== 国际化运行时配置 ====================
99
+
100
+ /**
101
+ * 运行时国际化配置
102
+ * @see https://umijs.org/docs/max/i18n#%E8%BF%90%E8%A1%8C%E6%97%B6%E9%85%8D%E7%BD%AE
103
+ */
104
+ export const locale = {
105
+ getLocale() {
106
+ return getCurrentLocale();
107
+ },
108
+ };
109
+
106
110
  /**
107
111
  * @see https://umijs.org/docs/api/runtime-config#getinitialstate
108
112
  */
@@ -124,29 +128,41 @@ export async function getInitialState(): Promise<{
124
128
  }
125
129
  };
126
130
 
127
- // 如果不是登录页面,执行
128
131
  const { location } = history;
129
- if (!NO_AUTH_ROUTE_LIST.includes(location.pathname)) {
130
- // 先确保 SSO 会话完成(如果 URL 中有 ticket 参数,会换取 token 并存储用户信息)
132
+ const noAuthRoute = isNoAuthRoute(location.pathname);
133
+ const skipAuth = noAuthRoute || isPageAuthFree(location.pathname);
134
+
135
+ // 非免认证路由:走 SSO 流程
136
+ if (!skipAuth) {
131
137
  await ensureSsoSession();
138
+ }
132
139
 
133
- // 有 token 时,获取用户信息
134
- if (getStoredAuthToken()) {
135
- const userInfo = await fetchUserInfoFn();
136
- if (userInfo) {
137
- return {
138
- fetchUserInfo: fetchUserInfoFn,
139
- currentUser: userInfo,
140
- };
141
- }
140
+ // 有 token 就获取用户信息(无论在哪个页面,支持登录后 refresh 场景)
141
+ // 当页面免认证时,不获取用户信息
142
+ if (getStoredAuthToken() && !skipAuth) {
143
+ const userInfo = await fetchUserInfoFn();
144
+ if (userInfo) {
145
+ clearRedirectCount();
146
+ return {
147
+ fetchUserInfo: fetchUserInfoFn,
148
+ currentUser: userInfo,
149
+ };
142
150
  }
143
151
  }
144
152
 
153
+ // 非免认证路由且没有 token,跳转到 SSO 登录
154
+ if (!skipAuth) {
155
+ handleAuthFailureRedirect();
156
+ // 返回空状态,页面会被重定向
157
+ return {
158
+ fetchUserInfo: fetchUserInfoFn,
159
+ };
160
+ }
161
+
145
162
  return {
146
163
  fetchUserInfo: fetchUserInfoFn,
147
164
  };
148
165
  }
149
-
150
166
  /**
151
167
  * @name request 配置,可以配置错误处理
152
168
  * 它基于 axios 和 ahooks 的 useRequest 提供了一套统一的网络请求和错误处理方案。
@@ -158,6 +174,10 @@ export const request: RequestConfig = {
158
174
  ...errorConfig,
159
175
  };
160
176
 
177
+ export function rootContainer(container: React.ReactNode) {
178
+ return <SentryErrorBoundary>{container}</SentryErrorBoundary>;
179
+ }
180
+
161
181
  /**
162
182
  * Umi 路由类型定义
163
183
  */
@@ -172,14 +192,16 @@ interface UmiRoute {
172
192
 
173
193
  /**
174
194
  * @name 动态路由配置
175
- * window.__MICO_MENUS__ 获取菜单数据,生成动态路由
195
+ * 优先从 window.__MICO_PAGES__ 获取页面数据,无数据时降级到 window.__MICO_MENUS__
176
196
  * @doc https://umijs.org/docs/max/router#%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1
177
197
  */
178
198
  export function patchClientRoutes({ routes }: { routes: UmiRoute[] }) {
179
- const menus = getWindowMenus();
180
- const dynamicRoutes = extractRoutes(menus);
199
+ const dynamicRoutes = getDynamicRoutes();
181
200
 
182
- console.log('[app.tsx] patchClientRoutes:', { menus, dynamicRoutes, routes });
201
+ console.log('[app.tsx] patchClientRoutes:', {
202
+ dynamicRoutes,
203
+ routes,
204
+ });
183
205
 
184
206
  // 找到根路由(全局布局)
185
207
  const rootRoute = routes.find((r) => r.path === '/');
@@ -205,6 +227,7 @@ export function patchClientRoutes({ routes }: { routes: UmiRoute[] }) {
205
227
  ? React.createElement(MicroAppLoader, {
206
228
  entry: route.entry,
207
229
  name: route.path,
230
+ base: route.base,
208
231
  displayName: route.name,
209
232
  })
210
233
  : undefined;
@@ -222,6 +245,22 @@ export function patchClientRoutes({ routes }: { routes: UmiRoute[] }) {
222
245
  });
223
246
  }
224
247
 
248
+ /**
249
+ * @name 路由变化处理
250
+ * @description 处理 defaultPath 重定向:访问 "/" 时自动跳转到配置的默认路径
251
+ * @doc https://umijs.org/docs/api/runtime-config#onroutechange
252
+ */
253
+ export function onRouteChange({
254
+ location,
255
+ }: {
256
+ location: { pathname: string };
257
+ }) {
258
+ const defaultPath = window.__MICO_CONFIG__?.defaultPath;
259
+ if (location.pathname === '/' && defaultPath && defaultPath !== '/') {
260
+ history.replace(defaultPath);
261
+ }
262
+ }
263
+
225
264
  /**
226
265
  * @name qiankun 微前端子应用生命周期
227
266
  * @description 当应用作为 qiankun 子应用运行时,这些生命周期会被调用
@@ -3,6 +3,9 @@ import { checkStaffAuth } from '@/services/auth';
3
3
  import { handleAuthFailureRedirect } from '../request';
4
4
  import { clearStoredTokens, getAuthInfo } from './auth-manager';
5
5
 
6
+ // 导出 STORAGE_KEYS 常量
7
+ export { STORAGE_KEYS } from '../../constants';
8
+
6
9
  export function logout(): void {
7
10
  clearStoredTokens();
8
11
  }
@@ -1,11 +1,188 @@
1
1
  import dayjs from 'dayjs';
2
2
  import timezone from 'dayjs/plugin/timezone';
3
3
  import utc from 'dayjs/plugin/utc';
4
+ import { STORAGE_KEYS } from '../constants';
4
5
 
5
6
  // 扩展 dayjs
6
7
  dayjs.extend(utc);
7
8
  dayjs.extend(timezone);
8
9
 
10
+ // ==================== 时区常量 ====================
11
+
12
+ /**
13
+ * UTC 偏移量键的枚举映射,覆盖 UTC-12 到 UTC+14
14
+ */
15
+ export const TIMEZONE = {
16
+ 'UTC-12': 'UTC-12',
17
+ 'UTC-11': 'UTC-11',
18
+ 'UTC-10': 'UTC-10',
19
+ 'UTC-9': 'UTC-9',
20
+ 'UTC-8': 'UTC-8',
21
+ 'UTC-7': 'UTC-7',
22
+ 'UTC-6': 'UTC-6',
23
+ 'UTC-5': 'UTC-5',
24
+ 'UTC-4': 'UTC-4',
25
+ 'UTC-3': 'UTC-3',
26
+ 'UTC-2': 'UTC-2',
27
+ 'UTC-1': 'UTC-1',
28
+ 'UTC+0': 'UTC+0',
29
+ 'UTC+1': 'UTC+1',
30
+ 'UTC+2': 'UTC+2',
31
+ 'UTC+3': 'UTC+3',
32
+ 'UTC+4': 'UTC+4',
33
+ 'UTC+5': 'UTC+5',
34
+ 'UTC+6': 'UTC+6',
35
+ 'UTC+7': 'UTC+7',
36
+ 'UTC+8': 'UTC+8',
37
+ 'UTC+9': 'UTC+9',
38
+ 'UTC+10': 'UTC+10',
39
+ 'UTC+11': 'UTC+11',
40
+ 'UTC+12': 'UTC+12',
41
+ 'UTC+13': 'UTC+13',
42
+ 'UTC+14': 'UTC+14',
43
+ } as const;
44
+
45
+ export type TTimezone = keyof typeof TIMEZONE;
46
+
47
+ /**
48
+ * UTC 偏移量到 IANA 时区标识的映射表
49
+ * 每个整数偏移值对应一个具有常住人口的代表性城市/地区
50
+ */
51
+ export const TIMEZONE_HASH: Record<TTimezone, string> = {
52
+ [TIMEZONE['UTC-12']]: 'Etc/GMT+12', // 无居住区(公海/无人岛)
53
+ [TIMEZONE['UTC-11']]: 'Pacific/Pago_Pago', // 帕果帕果,美属萨摩亚
54
+ [TIMEZONE['UTC-10']]: 'Pacific/Honolulu', // 檀香山,夏威夷州
55
+ [TIMEZONE['UTC-9']]: 'America/Anchorage', // 安克雷奇,阿拉斯加州
56
+ [TIMEZONE['UTC-8']]: 'America/Los_Angeles', // 洛杉矶,加利福尼亚州
57
+ [TIMEZONE['UTC-7']]: 'America/Denver', // 丹佛,科罗拉多州
58
+ [TIMEZONE['UTC-6']]: 'America/Chicago', // 芝加哥,伊利诺伊州
59
+ [TIMEZONE['UTC-5']]: 'America/New_York', // 纽约,纽约州
60
+ [TIMEZONE['UTC-4']]: 'America/Puerto_Rico', // 圣胡安,波多黎各
61
+ [TIMEZONE['UTC-3']]: 'America/Sao_Paulo', // 圣保罗,巴西
62
+ [TIMEZONE['UTC-2']]: 'America/Noronha', // 费尔南多岛,巴西
63
+ [TIMEZONE['UTC-1']]: 'Atlantic/Azores', // 亚速尔群岛,葡萄牙
64
+ [TIMEZONE['UTC+0']]: 'Europe/London', // 伦敦,英国
65
+ [TIMEZONE['UTC+1']]: 'Europe/Paris', // 巴黎,法国
66
+ [TIMEZONE['UTC+2']]: 'Europe/Kyiv', // 基辅,乌克兰
67
+ [TIMEZONE['UTC+3']]: 'Europe/Moscow', // 莫斯科,俄罗斯
68
+ [TIMEZONE['UTC+4']]: 'Asia/Dubai', // 迪拜,阿联酋
69
+ [TIMEZONE['UTC+5']]: 'Asia/Tashkent', // 塔什干,乌兹别克斯坦
70
+ [TIMEZONE['UTC+6']]: 'Asia/Dhaka', // 达卡,孟加拉国
71
+ [TIMEZONE['UTC+7']]: 'Asia/Bangkok', // 曼谷,泰国
72
+ [TIMEZONE['UTC+8']]: 'Asia/Shanghai', // 上海,中国(默认)
73
+ [TIMEZONE['UTC+9']]: 'Asia/Tokyo', // 东京,日本
74
+ [TIMEZONE['UTC+10']]: 'Australia/Sydney', // 悉尼,澳大利亚
75
+ [TIMEZONE['UTC+11']]: 'Pacific/Noumea', // 努美阿,新喀里多尼亚
76
+ [TIMEZONE['UTC+12']]: 'Pacific/Auckland', // 奥克兰,新西兰
77
+ [TIMEZONE['UTC+13']]: 'Pacific/Tongatapu', // 努库阿洛法,汤加
78
+ [TIMEZONE['UTC+14']]: 'Pacific/Kiritimati', // 圣诞岛,基里巴斯
79
+ };
80
+
81
+ // ==================== 时区工具函数 ====================
82
+
83
+ /**
84
+ * 将 UTC offset 数字转换为 UTC+N 格式的时区字符串
85
+ * @param utcOffset UTC 偏移量(数字),如 8 表示 UTC+8,-5 表示 UTC-5
86
+ * @returns 时区字符串,如 'UTC+8' 或 'UTC-5'
87
+ */
88
+ export const utcOffsetToTimezone = (utcOffset: number): string => {
89
+ if (utcOffset >= 0) {
90
+ return `UTC+${utcOffset}`;
91
+ }
92
+ return `UTC${utcOffset}`; // 负数会自动包含负号
93
+ };
94
+
95
+ /**
96
+ * 将 UTC 偏移量转换为 IANA 时区标识
97
+ * @param utcOffset UTC 偏移量
98
+ * @returns IANA 时区标识(如 'Asia/Shanghai')
99
+ */
100
+ export const utcOffsetToTimeZoneStr = (utcOffset: number): string => {
101
+ const timezoneKey = utcOffsetToTimezone(utcOffset) as TTimezone;
102
+ return (
103
+ TIMEZONE_HASH[timezoneKey] ??
104
+ dayjs.tz.guess() ??
105
+ TIMEZONE_HASH[TIMEZONE['UTC+8']]
106
+ );
107
+ };
108
+
109
+ /**
110
+ * 从 localStorage 获取时区偏移量数字
111
+ * @returns 偏移量数字,默认 8(UTC+8)
112
+ */
113
+ export const getTimezoneOffset = (): number => {
114
+ return Number(getFromStorage(STORAGE_KEYS.TIMEZONE)) || 8;
115
+ };
116
+
117
+ /**
118
+ * 获取当前时区的 IANA 标识
119
+ * @returns IANA 时区标识(如 'Asia/Shanghai')
120
+ */
121
+ export const getTimezone = (): string => {
122
+ const storedTimezone = getFromStorage(STORAGE_KEYS.TIMEZONE);
123
+ let timezoneKey: TTimezone | undefined;
124
+
125
+ if (storedTimezone) {
126
+ // 如果存储的是数字字符串(UTC offset),转换为 UTC+N 格式
127
+ const offsetNum = Number(storedTimezone);
128
+ if (!Number.isNaN(offsetNum) && storedTimezone.trim() !== '') {
129
+ timezoneKey = utcOffsetToTimezone(offsetNum) as TTimezone;
130
+ } else {
131
+ // 如果已经是 UTC+N 格式,直接使用
132
+ timezoneKey = storedTimezone as TTimezone;
133
+ }
134
+ }
135
+
136
+ return (
137
+ (timezoneKey && TIMEZONE_HASH[timezoneKey]) ??
138
+ dayjs.tz.guess() ??
139
+ TIMEZONE_HASH[TIMEZONE['UTC+8']]
140
+ );
141
+ };
142
+
143
+ /**
144
+ * 获取当前时区(从 localStorage 读取)
145
+ * @returns 时区字符串,如 'UTC+8',如果未设置则返回 'UTC+8'
146
+ */
147
+ export const getCurrentTimezone = (): string => {
148
+ const stored = getFromStorage(STORAGE_KEYS.TIMEZONE);
149
+ if (stored) {
150
+ // 如果存储的是数字字符串(UTC offset),转换为 UTC+N 格式
151
+ const offsetNum = Number(stored);
152
+ if (!Number.isNaN(offsetNum) && stored.trim() !== '') {
153
+ return utcOffsetToTimezone(offsetNum);
154
+ }
155
+ // 如果已经是 UTC+N 格式,直接返回
156
+ return stored;
157
+ }
158
+ // 默认返回 UTC+8
159
+ return TIMEZONE['UTC+8'];
160
+ };
161
+
162
+ /**
163
+ * 设置时区到 localStorage
164
+ * @param timezone 时区字符串,如 'UTC+8' 或偏移量数字字符串如 '8'
165
+ */
166
+ export const setTimezone = (timezone: string): void => {
167
+ setStorage(STORAGE_KEYS.TIMEZONE, timezone);
168
+ };
169
+
170
+ /**
171
+ * 获取时区地区(IANA 标识)
172
+ * @returns 时区地区字符串,如 'Asia/Shanghai',如果未设置则返回空字符串
173
+ */
174
+ export const getTimezoneRegion = (): string => {
175
+ return getFromStorage(STORAGE_KEYS.TIMEZONE_REGION) || '';
176
+ };
177
+
178
+ /**
179
+ * 设置时区地区到 localStorage
180
+ * @param region 时区地区字符串,如 'Asia/Shanghai'
181
+ */
182
+ export const setTimezoneRegion = (region: string): void => {
183
+ setStorage(STORAGE_KEYS.TIMEZONE_REGION, region);
184
+ };
185
+
9
186
  /**
10
187
  * 获取静态资源的完整 URL
11
188
  * 用于 public 目录下的静态资源,自动拼接 publicPath 前缀
@@ -12,11 +12,19 @@ const LOCALE_STORAGE_KEY = 'umi_locale';
12
12
  const URL_PARAM_KEY = 'lang';
13
13
 
14
14
  /**
15
- * 支持的语言列表
15
+ * 支持的语言列表(umi 格式)
16
16
  */
17
- export const SUPPORTED_LOCALES = ['zh-CN', 'en-US', 'ar-SA'] as const;
17
+ export const SUPPORTED_LOCALES = ['zh-CN', 'en-US'] as const;
18
18
  export type SupportedLocale = (typeof SUPPORTED_LOCALES)[number];
19
19
 
20
+ /**
21
+ * 语言枚举常量
22
+ */
23
+ export const LOCALE = {
24
+ ZH_CN: 'zh-CN',
25
+ EN_US: 'en-US',
26
+ } as const;
27
+
20
28
  /**
21
29
  * 默认语言
22
30
  */
@@ -50,20 +58,19 @@ function getLocaleFromStorage(): string | null {
50
58
  /**
51
59
  * 获取浏览器默认语言
52
60
  */
53
- function getBrowserLocale(): string {
61
+ function getBrowserLocale(): SupportedLocale {
54
62
  if (typeof window === 'undefined') {
55
63
  return DEFAULT_LOCALE;
56
64
  }
57
65
  const browserLang = navigator.language || (navigator as any).userLanguage;
58
- // 检查是否支持该语言
59
- if (SUPPORTED_LOCALES.includes(browserLang as SupportedLocale)) {
60
- return browserLang;
66
+ const locales = [...SUPPORTED_LOCALES];
67
+ // 检查是否直接支持该语言
68
+ if (locales.includes(browserLang as SupportedLocale)) {
69
+ return browserLang as SupportedLocale;
61
70
  }
62
- // 检查语言前缀(如 zh-TW -> zh-CN)
71
+ // 检查语言前缀(如 zh-TW -> zh-CN, en-GB -> en-US
63
72
  const langPrefix = browserLang.split('-')[0];
64
- const matchedLocale = SUPPORTED_LOCALES.find((locale) =>
65
- locale.startsWith(langPrefix),
66
- );
73
+ const matchedLocale = locales.find((locale) => locale.startsWith(langPrefix));
67
74
  return matchedLocale || DEFAULT_LOCALE;
68
75
  }
69
76
 
@@ -74,7 +81,7 @@ function isValidLocale(locale: string | null): locale is SupportedLocale {
74
81
  if (!locale) {
75
82
  return false;
76
83
  }
77
- return SUPPORTED_LOCALES.includes(locale as SupportedLocale);
84
+ return ([...SUPPORTED_LOCALES] as string[]).includes(locale);
78
85
  }
79
86
 
80
87
  /**
@@ -95,7 +102,7 @@ export function getCurrentLocale(): SupportedLocale {
95
102
  }
96
103
 
97
104
  // 3. 使用浏览器默认语言
98
- return getBrowserLocale() as SupportedLocale;
105
+ return getBrowserLocale();
99
106
  }
100
107
 
101
108
  /**
@@ -115,9 +122,7 @@ export function setLocaleToStorage(locale: SupportedLocale): void {
115
122
  /**
116
123
  * 根据语言获取图标库路径
117
124
  */
118
- export function getIconFontPath(locale: SupportedLocale): string {
119
- if (locale === 'ar-SA') {
120
- return withPublicPath('/font/ar-SA.js');
121
- }
122
- return withPublicPath('/font/default.js');
125
+ export function getIconFontPath(/* locale: SupportedLocale */): string {
126
+ // 如果后续需要支持 RTL 语言(如阿拉伯语),可在此扩展
127
+ return withPublicPath("/font/default.js");
123
128
  }