generator-mico-cli 0.2.1 → 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 (159) 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 +32 -16
  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 +5 -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/common-intl.md +372 -0
  34. 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
  35. package/generators/micro-react/templates/apps/layout/docs/feature-404/351/241/265/351/235/242.md +103 -0
  36. 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
  37. 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
  38. 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 +308 -63
  39. 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
  40. 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
  41. package/generators/micro-react/templates/apps/layout/docs/utils-timezone.md +324 -0
  42. package/generators/micro-react/templates/apps/layout/mock/api.mock.ts +81 -61
  43. package/generators/micro-react/templates/apps/layout/mock/menus.ts +114 -4
  44. package/generators/micro-react/templates/apps/layout/mock/pages.ts +86 -0
  45. package/generators/micro-react/templates/apps/layout/package.json +7 -4
  46. package/generators/micro-react/templates/apps/layout/src/app.tsx +111 -76
  47. package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +3 -0
  48. package/generators/micro-react/templates/apps/layout/src/common/helpers.ts +177 -0
  49. package/generators/micro-react/templates/apps/layout/src/common/locale.ts +22 -17
  50. package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +192 -42
  51. package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +69 -5
  52. package/generators/micro-react/templates/apps/layout/src/common/micro/index.ts +34 -0
  53. package/generators/micro-react/templates/apps/layout/src/common/micro-prefetch.ts +109 -0
  54. package/generators/micro-react/templates/apps/layout/src/common/portal-data.ts +45 -0
  55. package/generators/micro-react/templates/apps/layout/src/common/request/config.ts +72 -10
  56. package/generators/micro-react/templates/apps/layout/src/common/request/index.ts +2 -2
  57. package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +31 -3
  58. package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +29 -11
  59. package/generators/micro-react/templates/apps/layout/src/common/request/url-resolver.ts +1 -1
  60. package/generators/micro-react/templates/apps/layout/src/common/route-guard.ts +345 -0
  61. package/generators/micro-react/templates/apps/layout/src/common/theme.ts +2 -4
  62. package/generators/micro-react/templates/apps/layout/src/common/upload/oss.ts +3 -4
  63. package/generators/micro-react/templates/apps/layout/src/common/upload/types.ts +1 -1
  64. package/generators/micro-react/templates/apps/layout/src/common/uploadFiles.ts +1 -1
  65. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.less +8 -3
  66. package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +25 -8
  67. package/generators/micro-react/templates/apps/layout/src/components/HeaderDropdown/index.tsx +20 -0
  68. package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +5 -6
  69. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.less +21 -6
  70. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +83 -149
  71. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/micro-app-manager.ts +569 -0
  72. package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +383 -0
  73. package/generators/micro-react/templates/apps/layout/src/components/RightContent/avatar-dropdown.less +35 -0
  74. package/generators/micro-react/templates/apps/layout/src/components/RightContent/index.ts +2 -0
  75. package/generators/micro-react/templates/apps/layout/src/constants/index.ts +170 -6
  76. package/generators/micro-react/templates/apps/layout/src/global.less +18 -9
  77. package/generators/micro-react/templates/apps/layout/src/hooks/useMenu.ts +3 -2
  78. package/generators/micro-react/templates/apps/layout/src/hooks/useRoutePermissionRefresh.ts +72 -0
  79. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less +3 -1
  80. package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +10 -55
  81. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +34 -4
  82. package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +24 -8
  83. package/generators/micro-react/templates/apps/layout/src/layouts/index.less +84 -13
  84. package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +156 -69
  85. package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +12 -0
  86. package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +12 -0
  87. package/generators/micro-react/templates/apps/layout/src/pages/403/index.tsx +8 -2
  88. package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +78 -0
  89. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +3 -0
  90. package/generators/micro-react/templates/apps/layout/src/pages/Home/index.tsx +7 -1
  91. package/generators/micro-react/templates/apps/layout/src/pages/User/Login/index.less +1 -1
  92. package/generators/micro-react/templates/apps/layout/src/pages/User/Login/index.tsx +3 -3
  93. package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +1 -1
  94. package/generators/micro-react/templates/apps/layout/src/services/config/index.ts +63 -0
  95. package/generators/micro-react/templates/apps/layout/src/services/config/type.ts +30 -0
  96. package/generators/micro-react/templates/apps/layout/src/services/user.ts +29 -2
  97. package/generators/micro-react/templates/apps/layout/tailwind.config.js +3 -0
  98. package/generators/micro-react/templates/deployDesc.md +3 -3
  99. package/generators/micro-react/templates/dev.preset.json +14 -0
  100. package/generators/micro-react/templates/docs/dev-preset.md +130 -0
  101. package/generators/micro-react/templates/package.json +21 -6
  102. package/generators/micro-react/templates/packages/common-intl/README.md +427 -0
  103. package/generators/micro-react/templates/packages/common-intl/package.json +34 -0
  104. package/generators/micro-react/templates/packages/common-intl/src/index.ts +7 -0
  105. package/generators/micro-react/templates/packages/common-intl/src/indexedDBUtils.ts +51 -0
  106. package/generators/micro-react/templates/packages/common-intl/src/intl.ts +50 -0
  107. package/generators/micro-react/templates/packages/common-intl/src/utils.ts +482 -0
  108. package/generators/micro-react/templates/packages/common-intl/tsconfig.json +22 -0
  109. package/generators/micro-react/templates/packages/common-intl/vite.config.ts +25 -0
  110. package/generators/micro-react/templates/scripts/apply-sentry-plugin.ts +45 -0
  111. package/generators/micro-react/templates/scripts/collect-dist.js +10 -0
  112. package/generators/micro-react/templates/scripts/dev-preset.js +265 -0
  113. package/generators/micro-react/templates/scripts/dev-preset.schema.json +39 -0
  114. package/generators/micro-react/templates/turbo.json +4 -1
  115. package/generators/subapp-react/index.js +326 -40
  116. package/generators/subapp-react/meta.json +10 -0
  117. package/generators/subapp-react/templates/homepage/.env +2 -1
  118. package/generators/subapp-react/templates/homepage/README.md +3 -3
  119. package/generators/subapp-react/templates/homepage/config/config.dev.ts +14 -7
  120. package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +16 -5
  121. package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +16 -5
  122. package/generators/subapp-react/templates/homepage/config/config.prod.ts +14 -5
  123. package/generators/subapp-react/templates/homepage/config/config.ts +21 -0
  124. package/generators/subapp-react/templates/homepage/config/routes.ts +2 -2
  125. package/generators/subapp-react/templates/homepage/mock/api.mock.ts +2 -2
  126. package/generators/subapp-react/templates/homepage/package.json +7 -4
  127. package/generators/subapp-react/templates/homepage/src/app.tsx +18 -27
  128. package/generators/subapp-react/templates/homepage/src/common/request.ts +29 -2
  129. package/generators/subapp-react/templates/homepage/src/global.less +6 -5
  130. package/generators/subapp-react/templates/homepage/src/pages/index.less +3 -3
  131. package/generators/subapp-react/templates/homepage/src/pages/index.tsx +99 -60
  132. package/generators/subapp-react/templates/homepage/src/styles/theme.less +1 -1
  133. package/generators/subapp-umd/ignore-list.json +5 -0
  134. package/generators/subapp-umd/index.js +309 -0
  135. package/generators/subapp-umd/meta.json +11 -0
  136. package/generators/subapp-umd/templates/README.md +94 -0
  137. package/generators/subapp-umd/templates/package.json +35 -0
  138. package/generators/subapp-umd/templates/public/index.html +34 -0
  139. package/generators/subapp-umd/templates/src/App.less +15 -0
  140. package/generators/subapp-umd/templates/src/App.tsx +13 -0
  141. package/generators/subapp-umd/templates/src/index.ts +2 -0
  142. package/generators/subapp-umd/templates/tsconfig.json +27 -0
  143. package/generators/subapp-umd/templates/webpack.config.js +70 -0
  144. package/lib/utils.js +332 -2
  145. package/package.json +15 -2
  146. package/generators/micro-react/templates/apps/layout/mock/menus.json +0 -100
  147. package/generators/micro-react/templates/apps/layout/src/common/constants.ts +0 -38
  148. package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/container-manager.ts +0 -202
  149. package/generators/micro-react/templates/packages/shared-styles/README.md +0 -124
  150. package/generators/micro-react/templates/packages/shared-styles/arco-design-mobile-override.less +0 -91
  151. package/generators/micro-react/templates/packages/shared-styles/arco-override.less +0 -119
  152. package/generators/micro-react/templates/packages/shared-styles/index.d.ts +0 -44
  153. package/generators/micro-react/templates/packages/shared-styles/index.less +0 -13
  154. package/generators/micro-react/templates/packages/shared-styles/package.json +0 -30
  155. package/generators/micro-react/templates/packages/shared-styles/theme-inject.less +0 -10
  156. package/generators/micro-react/templates/packages/shared-styles/themes/dark/custom-var.less +0 -290
  157. package/generators/micro-react/templates/packages/shared-styles/themes/normal/custom-var.less +0 -269
  158. package/generators/micro-react/templates/packages/shared-styles/variables-only.less +0 -433
  159. package/generators/micro-react/templates/packages/shared-styles/variables.less +0 -452
@@ -1,10 +1,24 @@
1
1
  import { layoutLogger } from '@/common/logger';
2
- import { extractRoutes, filterMenuItems, 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';
14
+ import {
15
+ isAuthDisabled,
16
+ isNoLayoutRoute,
17
+ isNoPermissionRoute,
18
+ } from '@/constants';
19
+ import { useRoutePermissionRefresh } from '@/hooks/useRoutePermissionRefresh';
6
20
  import ForbiddenPage from '@/pages/403';
7
- import { Layout, Spin } from '@arco-design/web-react';
21
+ import { Layout, Spin } from '@mico-platform/ui';
8
22
  import { Outlet, useLocation, useModel } from '@umijs/max';
9
23
  import React, { Suspense, useMemo } from 'react';
10
24
  import LayoutHeader from './components/header';
@@ -13,26 +27,6 @@ import './index.less';
13
27
 
14
28
  const { Content } = Layout;
15
29
 
16
- /**
17
- * 从 entry URL 中提取微应用标识
18
- * 同一个 entry 的所有路由使用相同的标识,避免频繁卸载/重载微应用
19
- *
20
- * 注意:使用完整的 origin + pathname 作为标识,避免同一 host 上多个子应用冲突
21
- * 例如:http://localhost:8010/app1/ 和 http://localhost:8010/app2/ 会有不同的标识
22
- */
23
- const getAppNameFromEntry = (entry: string): string => {
24
- try {
25
- const url = new URL(entry, window.location.href);
26
- // 使用 origin + pathname 作为标识,确保不同路径的子应用有不同标识
27
- // 如 "localhost-8010" 或 "localhost-8010-app1"
28
- const identifier = url.host + url.pathname;
29
- return identifier.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
30
- } catch {
31
- // fallback:使用 entry 的 hash
32
- return entry.replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
33
- }
34
- };
35
-
36
30
  /**
37
31
  * 主布局组件
38
32
  * 注意:Umi Max 使用 React Router 6,应该使用 <Outlet /> 渲染子路由,而不是 children
@@ -42,6 +36,11 @@ const BasicLayout: React.FC = () => {
42
36
  const { initialState } = useModel('@@initialState');
43
37
  const currentUser = initialState?.currentUser;
44
38
 
39
+ // 路由切换时自动刷新用户权限
40
+ // isRefreshing 状态仅用于显示 loading 覆盖层
41
+ // 实际的时序协调由 loadingCoordinator 在 MicroAppManager 层面处理
42
+ const { isRefreshing } = useRoutePermissionRefresh();
43
+
45
44
  const filterOptions = useMemo(
46
45
  () => ({
47
46
  isSuperuser: currentUser?.is_superuser,
@@ -49,48 +48,94 @@ const BasicLayout: React.FC = () => {
49
48
  }),
50
49
  [currentUser?.is_superuser, currentUser?.side_menus],
51
50
  );
52
- // 所有路由(不过滤权限,用于判断路径是否存在)
53
- const allRoutes = useMemo(() => {
54
- const menus = getWindowMenus();
55
- return extractRoutes(menus);
51
+
52
+ // 所有页面路由(优先 PAGES,降级 MENUS)— 用于路由匹配和渲染
53
+ const allPageRoutes = useMemo(() => {
54
+ return getDynamicRoutes();
56
55
  }, []);
57
56
 
58
- // 有权限的路由
59
- const allowedRoutes = useMemo(() => {
60
- const menus = getWindowMenus();
61
- const filteredMenus = filterMenuItems(menus, filterOptions);
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);
62
67
  return extractRoutes(filteredMenus);
63
- }, [filterOptions]);
68
+ }, [filterOptions, allMenuRoutes]);
64
69
 
65
- // 查找当前路由配置(优先从有权限的路由中查找)
70
+ // 当前路由配置(从所有页面路由中查找)
66
71
  const currentRoute = useMemo(() => {
67
- return findRouteByPath(allowedRoutes, location.pathname);
68
- }, [allowedRoutes, location.pathname]);
72
+ return findRouteByPath(allPageRoutes, location.pathname);
73
+ }, [allPageRoutes, location.pathname]);
69
74
 
70
- // 判断是否是动态路由但无权限
75
+ // 权限判断:菜单交叉引用 + 隐藏页面级兜底
71
76
  const isForbidden = useMemo(() => {
72
- // 如果在有权限的路由中找到了,说明有权限
73
- if (currentRoute) return false;
74
- // 如果在所有路由中也找不到,说明不是动态路由,交给 Umi 处理
75
- const routeInAll = findRouteByPath(allRoutes, location.pathname);
76
- // 在所有路由中存在,但在有权限的路由中不存在 无权限
77
- const forbidden = !!routeInAll;
78
-
79
- layoutLogger.log('isForbidden check:', {
80
- pathname: location.pathname,
81
- currentRoute,
82
- routeInAll,
83
- forbidden,
84
- allRoutesCount: allRoutes.length,
85
- allowedRoutesCount: allowedRoutes.length,
86
- filterOptions,
87
- });
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;
88
101
 
89
- return forbidden;
90
- }, [currentRoute, allRoutes, allowedRoutes, location.pathname, filterOptions]);
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
+ ]);
91
136
 
92
137
  // 判断是否需要显示布局
93
- const showLayout = !NO_AUTH_ROUTE_LIST.includes(location.pathname);
138
+ const showLayout = !isNoLayoutRoute(location.pathname);
94
139
 
95
140
  // 渲染页面内容
96
141
  const renderContent = () => {
@@ -98,6 +143,9 @@ const BasicLayout: React.FC = () => {
98
143
  pathname: location.pathname,
99
144
  currentRoute,
100
145
  isForbidden,
146
+ isRefreshing,
147
+ currentUserChanged: currentUser?.is_superuser,
148
+ sideMenusCount: currentUser?.side_menus?.length,
101
149
  });
102
150
 
103
151
  // 无权限,显示 403
@@ -111,14 +159,49 @@ const BasicLayout: React.FC = () => {
111
159
  // 使用 entry 的 origin 作为微应用标识,同一个 entry 的所有路由共用一个实例
112
160
  const appName = getAppNameFromEntry(currentRoute.entry);
113
161
  return (
114
- <MicroAppLoader
115
- entry={currentRoute.entry}
116
- // 使用 entry 生成的标识,而不是 path
117
- name={appName}
118
- // 显示名称用于 loading 提示
119
- displayName={currentRoute.name}
120
- // 传递当前路由路径,让子应用进行内部路由切换
121
- 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' }}
122
205
  />
123
206
  );
124
207
  }
@@ -127,13 +210,13 @@ const BasicLayout: React.FC = () => {
127
210
  return <Outlet />;
128
211
  };
129
212
 
130
- // 不需要布局的页面
213
+ // 不需要布局的页面(仍需检查权限)
131
214
  if (!showLayout) {
132
215
  return (
133
216
  <Suspense
134
217
  fallback={<Spin dot style={{ width: '100%', marginTop: 100 }} />}
135
218
  >
136
- <Outlet />
219
+ {isForbidden ? <ForbiddenPage /> : renderContent()}
137
220
  </Suspense>
138
221
  );
139
222
  }
@@ -144,15 +227,19 @@ const BasicLayout: React.FC = () => {
144
227
  <Layout className="layout-content">
145
228
  <LayoutMenu />
146
229
  <Layout className="layout-content-right">
147
- <AppTabs />
148
- <Content className="layout-content-outlet">
230
+ <div className="layout-content-scroll-container">
231
+ <AppTabs />
232
+ <Content className="layout-content-outlet">
149
233
  <Suspense
150
- fallback={<Spin dot style={{ width: '100%', marginTop: 100 }} />}
234
+ fallback={
235
+ <Spin dot style={{ width: '100%', marginTop: 100 }} />
236
+ }
151
237
  >
152
238
  {renderContent()}
153
239
  </Suspense>
154
240
  </Content>
155
- </Layout>
241
+ </div>
242
+ </Layout>
156
243
  </Layout>
157
244
  </Layout>
158
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
  };
@@ -1,4 +1,4 @@
1
- import { Button, Result } from '@arco-design/web-react';
1
+ import { Button, Result } from '@mico-platform/ui';
2
2
  import { history } from '@umijs/max';
3
3
  import React from 'react';
4
4
 
@@ -10,13 +10,19 @@ const ForbiddenPage: React.FC = () => {
10
10
  alignItems: 'center',
11
11
  height: '100%',
12
12
  minHeight: 400,
13
+ margin: '0 auto',
13
14
  }}>
14
15
  <Result
15
16
  status="403"
16
17
  title="无权限访问"
17
18
  subTitle="抱歉,您没有权限访问此页面"
18
19
  extra={
19
- <Button type="primary" onClick={() => history.push('/')}>
20
+ <Button
21
+ type="primary"
22
+ onClick={() => {
23
+ history.push('/');
24
+ }}
25
+ >
20
26
  返回首页
21
27
  </Button>
22
28
  }
@@ -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,5 +1,5 @@
1
- import { Button, Form, Input, Message, Space, Typography } from '@arco-design/web-react';
2
- import { IconLock, IconUser } from '@arco-design/web-react/icon';
1
+ import { Button, Form, Input, Message, Space, Typography } from '@mico-platform/ui';
2
+ import { IconLock, IconUser } from '@mico-platform/ui/icon';
3
3
  import { useLocation, useModel,useNavigate } from '@umijs/max';
4
4
  import React, { useCallback, useMemo, useState } from 'react';
5
5
  import { loginStaff } from '@/services/auth';
@@ -92,7 +92,7 @@ const LoginPage: React.FC = () => {
92
92
  </svg>
93
93
  </div>
94
94
  <div className={styles.titleBlock}>
95
- <Title level={3} className={styles.title}>
95
+ <Title className={styles.title}>
96
96
  {window.__MICO_WORKSPACE__?.name}
97
97
  </Title>
98
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
  ],
@@ -55,6 +55,6 @@ git 地址: https://gitlab.example.com/your-org/<%= projectName %>
55
55
 
56
56
  # 三、CDN路径示例
57
57
 
58
- - 开发环境域名:cdn-portal-dev.micoplatform.com/<%= projectName %>/{version}/{app}
59
- - 测试环境域名:cdn-portal-test.micoplatform.com/<%= projectName %>/{version}/{app}
60
- - 正式环境域名:cdn-portal.micoplatform.com/<%= projectName %>/{version}/{app}
58
+ - 开发环境域名:cdn-portal-dev.micoplatform.com/<%= cdnPrefixPath %><%= projectName %>/{version}/{app}
59
+ - 测试环境域名:cdn-portal-test.micoplatform.com/<%= cdnPrefixPath %><%= projectName %>/{version}/{app}
60
+ - 正式环境域名:cdn-portal.micoplatform.com/<%= cdnPrefixPath %><%= projectName %>/{version}/{app}