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,10 +1,25 @@
1
1
  import { layoutLogger } from '@/common/logger';
2
- import { extractRoutes, findRouteByPath, getWindowMenus } from '@/common/menu';
2
+ import {
3
+ extractRoutes,
4
+ filterMenuItems,
5
+ findPageByPath,
6
+ findRouteByPath,
7
+ getDynamicRoutes,
8
+ isSuperuserUser,
9
+ } from '@/common/menu';
10
+ import { getMenus, getPages, hasPages } from '@/common/portal-data';
11
+ import { getAppNameFromEntry } from '@/common/micro';
3
12
  import AppTabs from '@/components/AppTabs';
4
13
  import MicroAppLoader from '@/components/MicroAppLoader';
5
- import { NO_AUTH_ROUTE_LIST } from '@/constants';
6
- import { Layout, Spin } from '@arco-design/web-react';
7
- import { Outlet, useLocation } from '@umijs/max';
14
+ import {
15
+ isAuthDisabled,
16
+ isNoLayoutRoute,
17
+ isNoPermissionRoute,
18
+ } from '@/constants';
19
+ import { useRoutePermissionRefresh } from '@/hooks/useRoutePermissionRefresh';
20
+ import ForbiddenPage from '@/pages/403';
21
+ import { Layout, Spin } from '@mico-platform/ui';
22
+ import { Outlet, useLocation, useModel } from '@umijs/max';
8
23
  import React, { Suspense, useMemo } from 'react';
9
24
  import LayoutHeader from './components/header';
10
25
  import LayoutMenu from './components/menu';
@@ -12,69 +27,181 @@ import './index.less';
12
27
 
13
28
  const { Content } = Layout;
14
29
 
15
- /**
16
- * 从 entry URL 中提取微应用标识
17
- * 同一个 entry 的所有路由使用相同的标识,避免频繁卸载/重载微应用
18
- *
19
- * 注意:使用完整的 origin + pathname 作为标识,避免同一 host 上多个子应用冲突
20
- * 例如:http://localhost:8010/app1/ 和 http://localhost:8010/app2/ 会有不同的标识
21
- */
22
- const getAppNameFromEntry = (entry: string): string => {
23
- try {
24
- const url = new URL(entry, window.location.href);
25
- // 使用 origin + pathname 作为标识,确保不同路径的子应用有不同标识
26
- // 如 "localhost-8010" 或 "localhost-8010-app1"
27
- const identifier = url.host + url.pathname;
28
- return identifier.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
29
- } catch {
30
- // fallback:使用 entry 的 hash
31
- return entry.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
32
- }
33
- };
34
-
35
30
  /**
36
31
  * 主布局组件
37
32
  * 注意:Umi Max 使用 React Router 6,应该使用 <Outlet /> 渲染子路由,而不是 children
38
33
  */
39
34
  const BasicLayout: React.FC = () => {
40
35
  const location = useLocation();
36
+ const { initialState } = useModel('@@initialState');
37
+ const currentUser = initialState?.currentUser;
38
+
39
+ // 路由切换时自动刷新用户权限
40
+ // isRefreshing 状态仅用于显示 loading 覆盖层
41
+ // 实际的时序协调由 loadingCoordinator 在 MicroAppManager 层面处理
42
+ const { isRefreshing } = useRoutePermissionRefresh();
43
+
44
+ const filterOptions = useMemo(
45
+ () => ({
46
+ isSuperuser: currentUser?.is_superuser,
47
+ sideMenus: (currentUser?.side_menus || []) as string[],
48
+ }),
49
+ [currentUser?.is_superuser, currentUser?.side_menus],
50
+ );
41
51
 
42
- // 解析路由配置
43
- const routes = useMemo(() => {
44
- const menus = getWindowMenus();
45
- return extractRoutes(menus);
52
+ // 所有页面路由(优先 PAGES,降级 MENUS)— 用于路由匹配和渲染
53
+ const allPageRoutes = useMemo(() => {
54
+ return getDynamicRoutes();
46
55
  }, []);
47
56
 
48
- // 查找当前路由配置
57
+ // 菜单路由(从 MENUS)— 用于权限交叉引用
58
+ const allMenuRoutes = useMemo(() => {
59
+ return extractRoutes(getMenus());
60
+ }, []);
61
+
62
+ const allowedMenuRoutes = useMemo(() => {
63
+ if (isAuthDisabled()) {
64
+ return allMenuRoutes;
65
+ }
66
+ const filteredMenus = filterMenuItems(getMenus(), filterOptions);
67
+ return extractRoutes(filteredMenus);
68
+ }, [filterOptions, allMenuRoutes]);
69
+
70
+ // 当前路由配置(从所有页面路由中查找)
49
71
  const currentRoute = useMemo(() => {
50
- return findRouteByPath(routes, location.pathname);
51
- }, [routes, location.pathname]);
72
+ return findRouteByPath(allPageRoutes, location.pathname);
73
+ }, [allPageRoutes, location.pathname]);
74
+
75
+ // 权限判断:菜单交叉引用 + 隐藏页面级兜底
76
+ const isForbidden = useMemo(() => {
77
+ if (isAuthDisabled()) return false;
78
+ if (isNoPermissionRoute(location.pathname)) return false;
79
+ // 非动态路由,交给 Umi 处理(404 等)
80
+ if (!currentRoute) return false;
81
+ if (isSuperuserUser(currentUser?.is_superuser)) return false;
82
+
83
+ // Tier 1: 菜单权限交叉引用
84
+ const inAllMenu = findRouteByPath(allMenuRoutes, location.pathname);
85
+ if (inAllMenu) {
86
+ const inAllowed = findRouteByPath(allowedMenuRoutes, location.pathname);
87
+ const forbidden = !inAllowed;
88
+
89
+ layoutLogger.log('isForbidden (menu check):', {
90
+ pathname: location.pathname,
91
+ inAllMenu: true,
92
+ inAllowed: !!inAllowed,
93
+ forbidden,
94
+ });
95
+
96
+ return forbidden;
97
+ }
98
+
99
+ // Tier 2: 隐藏页面级权限(仅在 PAGES 数据可用时生效)
100
+ if (!hasPages()) return false;
101
+
102
+ const page = findPageByPath(getPages(), location.pathname);
103
+ if (!page) return false;
104
+
105
+ if (page.adminOnly) {
106
+ layoutLogger.log('isForbidden (hidden page adminOnly):', {
107
+ pathname: location.pathname,
108
+ pageId: page.id,
109
+ });
110
+ return true;
111
+ }
112
+
113
+ if (page.accessControlEnabled) {
114
+ const sideMenus = (currentUser?.side_menus || []) as string[];
115
+ const forbidden = !page.routeKey || !sideMenus.includes(page.routeKey);
116
+
117
+ layoutLogger.log('isForbidden (hidden page accessControl):', {
118
+ pathname: location.pathname,
119
+ pageId: page.id,
120
+ routeKey: page.routeKey,
121
+ hasSideMenuMatch: !forbidden,
122
+ });
123
+
124
+ return forbidden;
125
+ }
126
+
127
+ return false;
128
+ }, [
129
+ currentRoute,
130
+ allMenuRoutes,
131
+ allowedMenuRoutes,
132
+ location.pathname,
133
+ currentUser?.is_superuser,
134
+ currentUser?.side_menus,
135
+ ]);
52
136
 
53
137
  // 判断是否需要显示布局
54
- const showLayout = !NO_AUTH_ROUTE_LIST.includes(location.pathname);
138
+ const showLayout = !isNoLayoutRoute(location.pathname);
55
139
 
56
140
  // 渲染页面内容
57
141
  const renderContent = () => {
58
142
  layoutLogger.log('renderContent:', {
59
143
  pathname: location.pathname,
60
144
  currentRoute,
61
- routes,
145
+ isForbidden,
146
+ isRefreshing,
147
+ currentUserChanged: currentUser?.is_superuser,
148
+ sideMenusCount: currentUser?.side_menus?.length,
62
149
  });
63
150
 
151
+ // 无权限,显示 403
152
+ if (isForbidden) {
153
+ return <ForbiddenPage />;
154
+ }
155
+
64
156
  // 如果有匹配的动态路由配置且需要加载微应用
65
157
  if (currentRoute?.loadType === 'microapp' && currentRoute.entry) {
66
158
  layoutLogger.log('Loading microapp:', currentRoute);
67
159
  // 使用 entry 的 origin 作为微应用标识,同一个 entry 的所有路由共用一个实例
68
160
  const appName = getAppNameFromEntry(currentRoute.entry);
69
161
  return (
70
- <MicroAppLoader
71
- entry={currentRoute.entry}
72
- // 使用 entry 生成的标识,而不是 path
73
- name={appName}
74
- // 显示名称用于 loading 提示
75
- displayName={currentRoute.name}
76
- // 传递当前路由路径,让子应用进行内部路由切换
77
- routePath={currentRoute.path}
162
+ <>
163
+ {/* 权限刷新中显示覆盖层,但不卸载 MicroAppLoader,避免子应用被中断 */}
164
+ {isRefreshing && (
165
+ <div
166
+ style={{
167
+ position: 'absolute',
168
+ top: 0,
169
+ left: 0,
170
+ right: 0,
171
+ bottom: 0,
172
+ display: 'flex',
173
+ alignItems: 'center',
174
+ justifyContent: 'center',
175
+ background: 'var(--color-bg-1)',
176
+ zIndex: 100,
177
+ }}
178
+ >
179
+ <Spin dot />
180
+ </div>
181
+ )}
182
+ <MicroAppLoader
183
+ // 使用 appName 作为 key,确保不同微应用使用不同的组件实例
184
+ // 同一个微应用的不同路由共用同一个实例
185
+ key={appName}
186
+ entry={currentRoute.entry}
187
+ base={currentRoute.base}
188
+ // 使用 entry 生成的标识,而不是 path
189
+ name={appName}
190
+ // 显示名称用于 loading 提示
191
+ displayName={currentRoute.name}
192
+ // 传递当前路由路径,让子应用进行内部路由切换
193
+ routePath={currentRoute.path}
194
+ />
195
+ </>
196
+ );
197
+ }
198
+
199
+ // 权限刷新中,显示 loading(仅对非微应用路由)
200
+ if (isRefreshing) {
201
+ return (
202
+ <Spin
203
+ dot
204
+ style={{ width: '100%', marginTop: 100, textAlign: 'center' }}
78
205
  />
79
206
  );
80
207
  }
@@ -83,13 +210,13 @@ const BasicLayout: React.FC = () => {
83
210
  return <Outlet />;
84
211
  };
85
212
 
86
- // 不需要布局的页面
213
+ // 不需要布局的页面(仍需检查权限)
87
214
  if (!showLayout) {
88
215
  return (
89
216
  <Suspense
90
217
  fallback={<Spin dot style={{ width: '100%', marginTop: 100 }} />}
91
218
  >
92
- <Outlet />
219
+ {isForbidden ? <ForbiddenPage /> : renderContent()}
93
220
  </Suspense>
94
221
  );
95
222
  }
@@ -100,15 +227,19 @@ const BasicLayout: React.FC = () => {
100
227
  <Layout className="layout-content">
101
228
  <LayoutMenu />
102
229
  <Layout className="layout-content-right">
103
- <AppTabs />
104
- <Content className="layout-content-outlet">
230
+ <div className="layout-content-scroll-container">
231
+ <AppTabs />
232
+ <Content className="layout-content-outlet">
105
233
  <Suspense
106
- fallback={<Spin dot style={{ width: '100%', marginTop: 100 }} />}
234
+ fallback={
235
+ <Spin dot style={{ width: '100%', marginTop: 100 }} />
236
+ }
107
237
  >
108
238
  {renderContent()}
109
239
  </Suspense>
110
240
  </Content>
111
- </Layout>
241
+ </div>
242
+ </Layout>
112
243
  </Layout>
113
244
  </Layout>
114
245
  );
@@ -17,4 +17,16 @@ export default {
17
17
 
18
18
  'loading.text': 'Loading...',
19
19
  'loading.error': 'Failed to load',
20
+
21
+ // Page titles
22
+ 'page.home.title': 'Home',
23
+
24
+ // AvatarDropdown
25
+ 'avatar.language': 'Language',
26
+ 'avatar.language.zh_CN': 'Simplified Chinese',
27
+ 'avatar.language.en': 'English',
28
+ 'avatar.theme': 'Theme',
29
+ 'avatar.theme.light': 'Light Mode',
30
+ 'avatar.theme.dark': 'Dark Mode',
31
+ 'avatar.logout': 'Logout',
20
32
  };
@@ -16,4 +16,16 @@ export default {
16
16
 
17
17
  'loading.text': '加载中...',
18
18
  'loading.error': '加载失败',
19
+
20
+ // Page titles
21
+ 'page.home.title': '首页',
22
+
23
+ // AvatarDropdown
24
+ 'avatar.language': '语言',
25
+ 'avatar.language.zh_CN': '简体中文',
26
+ 'avatar.language.en': 'English',
27
+ 'avatar.theme': '主题',
28
+ 'avatar.theme.light': '浅色模式',
29
+ 'avatar.theme.dark': '深色模式',
30
+ 'avatar.logout': '退出登录',
19
31
  };
@@ -0,0 +1,34 @@
1
+ import { Button, Result } from '@mico-platform/ui';
2
+ import { history } from '@umijs/max';
3
+ import React from 'react';
4
+
5
+ const ForbiddenPage: React.FC = () => {
6
+ return (
7
+ <div style={{
8
+ display: 'flex',
9
+ justifyContent: 'center',
10
+ alignItems: 'center',
11
+ height: '100%',
12
+ minHeight: 400,
13
+ margin: '0 auto',
14
+ }}>
15
+ <Result
16
+ status="403"
17
+ title="无权限访问"
18
+ subTitle="抱歉,您没有权限访问此页面"
19
+ extra={
20
+ <Button
21
+ type="primary"
22
+ onClick={() => {
23
+ history.push('/');
24
+ }}
25
+ >
26
+ 返回首页
27
+ </Button>
28
+ }
29
+ />
30
+ </div>
31
+ );
32
+ };
33
+
34
+ export default ForbiddenPage;
@@ -0,0 +1,78 @@
1
+ import { extractRoutes, filterMenuItems, type MenuFilterOptions } from '@/common/menu';
2
+ import { getMenus } from '@/common/portal-data';
3
+ import { isAuthDisabled } from '@/constants';
4
+ import { Button, Result, Space } from '@mico-platform/ui';
5
+ import { history, useModel } from '@umijs/max';
6
+ import React, { useMemo } from 'react';
7
+
8
+ /**
9
+ * 获取第一个可访问的路由路径
10
+ */
11
+ const getFirstAvailablePath = (filterOptions: MenuFilterOptions): string => {
12
+ const menus = getMenus();
13
+
14
+ // 根据权限过滤菜单
15
+ const filteredMenus = isAuthDisabled()
16
+ ? menus
17
+ : filterMenuItems(menus, filterOptions);
18
+
19
+ // 提取路由
20
+ const routes = extractRoutes(filteredMenus);
21
+
22
+ // 返回第一个有效路由,默认返回首页
23
+ return routes[0]?.path || '/';
24
+ };
25
+
26
+ const NotFoundPage: React.FC = () => {
27
+ const { initialState } = useModel('@@initialState');
28
+ const currentUser = initialState?.currentUser;
29
+
30
+ const filterOptions = useMemo<MenuFilterOptions>(
31
+ () => ({
32
+ isSuperuser: currentUser?.is_superuser,
33
+ sideMenus: (currentUser?.side_menus || []) as string[],
34
+ }),
35
+ [currentUser?.is_superuser, currentUser?.side_menus],
36
+ );
37
+
38
+ const firstAvailablePath = useMemo(
39
+ () => getFirstAvailablePath(filterOptions),
40
+ [filterOptions],
41
+ );
42
+
43
+ const handleGoBack = () => {
44
+ history.back();
45
+ };
46
+
47
+ const handleGoFirst = () => {
48
+ history.push(firstAvailablePath);
49
+ };
50
+
51
+ return (
52
+ <div
53
+ style={{
54
+ display: 'flex',
55
+ justifyContent: 'center',
56
+ alignItems: 'center',
57
+ height: '100%',
58
+ minHeight: 400,
59
+ }}
60
+ >
61
+ <Result
62
+ status="404"
63
+ title="页面不存在"
64
+ subTitle="抱歉,您访问的页面不存在"
65
+ extra={
66
+ <Space>
67
+ <Button onClick={handleGoBack}>返回上页</Button>
68
+ <Button type="primary" onClick={handleGoFirst}>
69
+ 前往首页
70
+ </Button>
71
+ </Space>
72
+ }
73
+ />
74
+ </div>
75
+ );
76
+ };
77
+
78
+ export default NotFoundPage;
@@ -1,3 +1,6 @@
1
+ @import '@mico-platform/theme/variables';
2
+
1
3
  .container {
2
4
  padding-top: 30px;
5
+ color: @color-text-1;
3
6
  }
@@ -1,7 +1,13 @@
1
+ import { useIntl } from '@umijs/max';
1
2
  import styles from './index.less';
2
3
 
3
4
  const HomePage: React.FC = () => {
4
- return <div className={styles.container}>首页2</div>;
5
+ const intl = useIntl();
6
+ return (
7
+ <div className={styles.container}>
8
+ {intl.formatMessage({ id: 'page.home.title' })}
9
+ </div>
10
+ );
5
11
  };
6
12
 
7
13
  export default HomePage;
@@ -1,4 +1,4 @@
1
- @import '<%= packageScope %>/shared-styles/variables-only';
1
+ @import '@mico-platform/theme/variables';
2
2
 
3
3
  .container {
4
4
  display: flex;
@@ -1,6 +1,6 @@
1
- import { Button, Form, Input, Message, Space, Typography } from '@arco-design/web-react';
2
- import { IconLock, IconUser } from '@arco-design/web-react/icon';
3
- import { useLocation, useNavigate } from '@umijs/max';
1
+ import { Button, Form, Input, Message, Space, Typography } from '@mico-platform/ui';
2
+ import { IconLock, IconUser } from '@mico-platform/ui/icon';
3
+ import { useLocation, useModel,useNavigate } from '@umijs/max';
4
4
  import React, { useCallback, useMemo, useState } from 'react';
5
5
  import { loginStaff } from '@/services/auth';
6
6
  import { maybePersistTokens } from '@/common/auth/auth-manager';
@@ -18,6 +18,8 @@ const LoginPage: React.FC = () => {
18
18
  const navigate = useNavigate();
19
19
  const location = useLocation();
20
20
  const [form] = Form.useForm<LoginFormValues>();
21
+ const { refresh } = useModel('@@initialState');
22
+
21
23
 
22
24
  const redirectPath = useMemo(() => {
23
25
  const params = new URLSearchParams(location.search);
@@ -39,6 +41,8 @@ const LoginPage: React.FC = () => {
39
41
  if (response?.code === 200) {
40
42
  maybePersistTokens(response);
41
43
  Message.success('登录成功');
44
+ // 刷新 initialState,获取用户信息
45
+ await refresh();
42
46
  navigate(redirectPath, { replace: true });
43
47
  return;
44
48
  }
@@ -50,7 +54,7 @@ const LoginPage: React.FC = () => {
50
54
  setLoading(false);
51
55
  }
52
56
  },
53
- [navigate, redirectPath],
57
+ [navigate, redirectPath, refresh],
54
58
  );
55
59
 
56
60
  return (
@@ -88,7 +92,7 @@ const LoginPage: React.FC = () => {
88
92
  </svg>
89
93
  </div>
90
94
  <div className={styles.titleBlock}>
91
- <Title level={3} className={styles.title}>
95
+ <Title className={styles.title}>
92
96
  {window.__MICO_WORKSPACE__?.name}
93
97
  </Title>
94
98
  <Text className={styles.subTitle}><%= ProjectName %> Management Center</Text>
@@ -3,7 +3,7 @@ import {
3
3
  createBusinessError,
4
4
  notifyHostError,
5
5
  } from '@/common/micro';
6
- import { Message, Notification } from '@arco-design/web-react';
6
+ import { Message, Notification } from '@mico-platform/ui';
7
7
  import type { RequestConfig } from '@umijs/max';
8
8
 
9
9
  // 错误处理方案: 错误类型
@@ -0,0 +1,63 @@
1
+ import { request } from '@/common/request';
2
+ import type {
3
+ IGetTimezoneListRequest,
4
+ IGetTimezoneListResponse,
5
+ ITimezone,
6
+ } from './type';
7
+
8
+ /**
9
+ * 静态时区列表(兜底配置)
10
+ * 当未配置 timezoneListUrl 时使用此静态列表
11
+ * 覆盖 UTC-12 到 UTC+14 共 27 个时区
12
+ */
13
+ const STATIC_TIMEZONE_LIST: ITimezone[] = [
14
+ { region: 'AZ', utc_offset: 3 },
15
+ { region: 'BR', utc_offset: -3 },
16
+ { region: 'DE', utc_offset: 1 },
17
+ { region: 'FJ', utc_offset: 15 },
18
+ { region: 'FR', utc_offset: 1 },
19
+ { region: 'Global', utc_offset: 8 },
20
+ { region: 'ID', utc_offset: 7 },
21
+ { region: 'IN', utc_offset: 5 },
22
+ { region: 'JP', utc_offset: 9 },
23
+ { region: 'MECA', utc_offset: 3 },
24
+ { region: 'MY', utc_offset: 8 },
25
+ { region: 'PH', utc_offset: 8 },
26
+ { region: 'PK', utc_offset: 5 },
27
+ { region: 'TH', utc_offset: 7 },
28
+ { region: 'TR', utc_offset: 3 },
29
+ { region: 'TW', utc_offset: 8 },
30
+ { region: 'US', utc_offset: -5 },
31
+ { region: 'UU', utc_offset: -8 },
32
+ { region: 'VN', utc_offset: 7 },
33
+ ];
34
+
35
+ /**
36
+ * 获取时区配置
37
+ * 获取系统支持的时区配置列表
38
+ *
39
+ * - 如果配置了 window.__MICO_CONFIG__.timezoneListUrl,则请求该 API
40
+ * - 如果未配置,则返回静态时区列表
41
+ */
42
+ export async function getTimezoneList(
43
+ payload: IGetTimezoneListRequest = {},
44
+ ): Promise<IGetTimezoneListResponse> {
45
+ const timezoneListUrl = window.__MICO_CONFIG__?.timezoneListUrl;
46
+
47
+ // 未配置 API 地址时,返回静态时区列表
48
+ if (!timezoneListUrl) {
49
+ return {
50
+ result: { code: 0 },
51
+ timezone_list: STATIC_TIMEZONE_LIST,
52
+ };
53
+ }
54
+
55
+ // 配置了 API 地址时,请求接口
56
+ return await request<IGetTimezoneListResponse>(timezoneListUrl, {
57
+ method: 'POST',
58
+ data: payload,
59
+ });
60
+ }
61
+
62
+ // 导出类型
63
+ export type { ITimezone, IGetTimezoneListResponse } from './type';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * 配置管理相关类型定义
3
+ */
4
+
5
+ /**
6
+ * 时区信息
7
+ */
8
+ export interface ITimezone {
9
+ /** UTC偏移量 */
10
+ utc_offset: number;
11
+ /** 时区地区 */
12
+ region: string;
13
+ }
14
+
15
+ /**
16
+ * 获取时区配置请求(空请求体)
17
+ */
18
+ export type IGetTimezoneListRequest = Record<string, never>;
19
+
20
+ /**
21
+ * 获取时区配置响应
22
+ */
23
+ export interface IGetTimezoneListResponse {
24
+ result: {
25
+ code: number;
26
+ message?: string;
27
+ };
28
+ /** 时区列表 */
29
+ timezone_list?: ITimezone[];
30
+ }
@@ -1,8 +1,27 @@
1
1
  import { request } from '@/common/request';
2
2
  import type { IUserInfo } from '@/common/auth/type';
3
+ import { getCurrentLocale, type SupportedLocale } from '@/common/locale';
3
4
 
4
5
  const USER_INFO_API = '/api/user/info';
5
6
 
7
+ /**
8
+ * 语言代码到接口 lang 参数的映射
9
+ * - zh_CN: 1 (中文)
10
+ * - en: 2 (英文)
11
+ */
12
+ const LOCALE_TO_LANG: Partial<Record<SupportedLocale, number>> = {
13
+ 'zh-CN': 1,
14
+ 'en-US': 2,
15
+ };
16
+
17
+ /**
18
+ * 获取当前语言对应的 API lang 参数
19
+ */
20
+ function getApiLangParam(): number {
21
+ const locale = getCurrentLocale();
22
+ return LOCALE_TO_LANG[locale] ?? 1;
23
+ }
24
+
6
25
  export interface IUserInfoResponse {
7
26
  code: number;
8
27
  data: IUserInfo;
@@ -15,11 +34,19 @@ export interface IUserInfoResponse {
15
34
  * @throws 当接口返回非 200 code 时抛出错误
16
35
  */
17
36
  export async function fetchUserInfo(): Promise<IUserInfo> {
18
- const response = await request<IUserInfoResponse>(USER_INFO_API, {
37
+ const lang = getApiLangParam();
38
+ const url = `${USER_INFO_API}?lang=${lang}`;
39
+
40
+ const response = await request<IUserInfoResponse>(url, {
19
41
  method: 'GET',
42
+ skipProxy: true,
20
43
  });
21
44
  if (response.code === 200 && response.data) {
22
- return response.data;
45
+ const data = response.data;
46
+ if (!data.user_name) {
47
+ data.user_name = data.username || '';
48
+ }
49
+ return data;
23
50
  }
24
51
  throw new Error(response.msg || '获取用户信息失败');
25
52
  }
@@ -1,6 +1,9 @@
1
1
  module.exports = {
2
2
  content: [
3
3
  './src/**/*.{js,jsx,ts,tsx,less,css}',
4
+ // 排除 .umi 目录,避免 Tailwind 重建时触发自己的 watch,导致无限循环
5
+ '!./src/.umi/**',
6
+ '!./src/.umi-production/**',
4
7
  './README.md',
5
8
  './docs/**/*.{md,mdx}',
6
9
  ],