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
@@ -0,0 +1,383 @@
1
+ import { logout, STORAGE_KEYS } from '@/common/auth';
2
+ import type { IUserInfo } from '@/common/auth/type';
3
+ import {
4
+ getCurrentTimezone,
5
+ getTimezoneRegion,
6
+ setTimezoneRegion as setTimezoneRegionToStorage,
7
+ setTimezone as setTimezoneToStorage,
8
+ utcOffsetToTimezone,
9
+ } from '@/common/helpers';
10
+ import {
11
+ getCurrentLocale,
12
+ LOCALE,
13
+ setLocaleToStorage,
14
+ type SupportedLocale,
15
+ } from '@/common/locale';
16
+ import IconFont from '@/components/IconFont';
17
+ import { useTheme } from '@/hooks/useTheme';
18
+ import { getTimezoneList, type ITimezone } from '@/services/config';
19
+ import { Avatar, Menu } from '@mico-platform/ui';
20
+ import { history, useIntl, useModel } from '@umijs/max';
21
+ import React, { useEffect, useState, useTransition } from 'react';
22
+ import { flushSync } from 'react-dom';
23
+ import HeaderDropdown from '../HeaderDropdown';
24
+ import './avatar-dropdown.less';
25
+
26
+ const MenuItem = Menu.Item;
27
+ const SubMenu = Menu.SubMenu;
28
+
29
+ export type GlobalHeaderRightProps = {
30
+ menu?: boolean;
31
+ children?: React.ReactNode;
32
+ };
33
+
34
+ // 菜单项类型定义
35
+ interface IMenuItem {
36
+ key: string;
37
+ label: string;
38
+ icon?: React.ReactNode;
39
+ path?: string;
40
+ children?: IMenuItem[];
41
+ }
42
+
43
+ // TODO: 临时假数据,后续接入真实登录态
44
+ const MOCK_USER: Partial<IUserInfo> = {
45
+ user_name: 'Test User',
46
+ username: 'test@micous.com',
47
+ };
48
+
49
+ export const AvatarName: React.FC<{ userName?: string }> = ({ userName }) => {
50
+ return <span className="ml-2 mr-2">{userName}</span>;
51
+ };
52
+
53
+ const languageLabelMap: Record<string, string> = {
54
+ [LOCALE.ZH_CN]: '中文',
55
+ [LOCALE.EN_US]: 'English',
56
+ };
57
+
58
+ export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
59
+ const { setInitialState, initialState } = useModel('@@initialState');
60
+ const { formatMessage } = useIntl();
61
+ // TODO: 临时使用假数据,后续接入真实登录态
62
+ const currentUser = initialState?.currentUser || MOCK_USER;
63
+ const { theme, setTheme } = useTheme();
64
+ const [timezoneLoading, startTransition] = useTransition();
65
+
66
+ // 时区相关状态
67
+ const [timezoneList, setTimezoneList] = useState<ITimezone[]>([]);
68
+ const [currentTimezone, setCurrentTimezone] = useState<string>(
69
+ getCurrentTimezone(),
70
+ );
71
+
72
+ // 获取当前时区的显示名称
73
+ const getCurrentTimezoneLabel = (): string => {
74
+ const offsetNum = Number(
75
+ currentTimezone.replace('UTC', '').replace('+', ''),
76
+ );
77
+ const currentRegion = getTimezoneRegion();
78
+
79
+ // 从 timezoneList 中找到匹配的时区(同时匹配 region 和 utc_offset)
80
+ const matchedTimezone = timezoneList.find(
81
+ (tz) => tz.utc_offset === offsetNum && tz.region === currentRegion,
82
+ );
83
+
84
+ if (matchedTimezone) {
85
+ return `${matchedTimezone.region} (${utcOffsetToTimezone(matchedTimezone.utc_offset)})`;
86
+ }
87
+
88
+ // 回退:只匹配 utc_offset
89
+ const fallbackTimezone = timezoneList.find(
90
+ (tz) => tz.utc_offset === offsetNum,
91
+ );
92
+ if (fallbackTimezone) {
93
+ return `${fallbackTimezone.region} (${utcOffsetToTimezone(fallbackTimezone.utc_offset)})`;
94
+ }
95
+
96
+ return utcOffsetToTimezone(offsetNum);
97
+ };
98
+
99
+ // 管理头像 URL
100
+ const [avatarSrc, setAvatarSrc] = useState<string>(
101
+ currentUser?.avatar || '',
102
+ );
103
+
104
+ useEffect(() => {
105
+ setAvatarSrc(currentUser?.avatar || '');
106
+ }, [currentUser?.avatar]);
107
+
108
+ // 加载时区列表
109
+ useEffect(() => {
110
+ const loadTimezoneList = async () => {
111
+ try {
112
+ const response = await getTimezoneList();
113
+ if (response?.timezone_list) {
114
+ setTimezoneList(response.timezone_list);
115
+ }
116
+ } catch (error) {
117
+ console.error('加载时区列表失败:', error);
118
+ }
119
+ };
120
+
121
+ startTransition(loadTimezoneList as any);
122
+ }, []);
123
+
124
+ // 监听 localStorage 中时区的变化
125
+ useEffect(() => {
126
+ const handleStorageChange = (e: StorageEvent) => {
127
+ if (e.key === STORAGE_KEYS.TIMEZONE && e.newValue) {
128
+ const offsetNum = Number(e.newValue);
129
+ if (!Number.isNaN(offsetNum) && e.newValue.trim() !== '') {
130
+ setCurrentTimezone(utcOffsetToTimezone(offsetNum));
131
+ } else {
132
+ setCurrentTimezone(e.newValue);
133
+ }
134
+ }
135
+ };
136
+
137
+ window.addEventListener('storage', handleStorageChange);
138
+ return () => {
139
+ window.removeEventListener('storage', handleStorageChange);
140
+ };
141
+ }, []);
142
+
143
+ const loginOut = async () => {
144
+ logout();
145
+ const { search, pathname } = window.location;
146
+ const urlParams = new URL(window.location.href).searchParams;
147
+ const searchParams = new URLSearchParams({
148
+ redirect: pathname + search,
149
+ });
150
+ const redirect = urlParams.get('redirect');
151
+ if (window.location.pathname !== '/user/login' && !redirect) {
152
+ history.replace({
153
+ pathname: '/user/login',
154
+ search: searchParams.toString(),
155
+ });
156
+ }
157
+ };
158
+
159
+ const onMenuClick = (key: string, path?: string) => {
160
+ if (key === 'logout') {
161
+ flushSync(() => {
162
+ setInitialState((s) => ({ ...s, currentUser: undefined }));
163
+ });
164
+ loginOut();
165
+ return;
166
+ }
167
+
168
+ // 处理语言切换
169
+ if (key.startsWith('language-')) {
170
+ const newLocale = key.replace('language-', '') as SupportedLocale;
171
+ const currentLocale = getCurrentLocale();
172
+ if (newLocale === currentLocale) {
173
+ return;
174
+ }
175
+ setLocaleToStorage(newLocale);
176
+ window.location.reload();
177
+ return;
178
+ }
179
+
180
+ // 处理时区切换
181
+ if (key.includes('/')) {
182
+ const lastSlashIndex = key.lastIndexOf('/');
183
+ const region = key.substring(0, lastSlashIndex);
184
+ const utcOffsetStr = key.substring(lastSlashIndex + 1);
185
+ const utcOffsetNum = Number(utcOffsetStr);
186
+ const timezoneStr = utcOffsetToTimezone(utcOffsetNum);
187
+ const currentRegion = getTimezoneRegion();
188
+
189
+ if (timezoneStr === currentTimezone && region === currentRegion) {
190
+ return;
191
+ }
192
+
193
+ setTimezoneToStorage(utcOffsetStr);
194
+ setTimezoneRegionToStorage(region);
195
+ setCurrentTimezone(timezoneStr);
196
+ window.location.reload();
197
+ return;
198
+ }
199
+
200
+ // 处理主题切换
201
+ if (key.startsWith('theme-')) {
202
+ const themeMode = key.replace('theme-', '') as 'light' | 'dark';
203
+ // 如果切换的主题和当前主题一致,则无需切换
204
+ if (themeMode === theme) {
205
+ return;
206
+ }
207
+ // 切换主题
208
+ setTheme(themeMode);
209
+ return;
210
+ }
211
+
212
+ if (path) {
213
+ history.push(path);
214
+ } else {
215
+ history.push(`/account/${key}`);
216
+ }
217
+ };
218
+
219
+ // TODO: 临时关闭加载逻辑,使用假数据
220
+ // const loading = <Spin size={16} className="ml-2 mr-2" />;
221
+ // if (!initialState) {
222
+ // return loading;
223
+ // }
224
+ // if (!currentUser || !currentUser.user_name) {
225
+ // return loading;
226
+ // }
227
+
228
+ // 菜单配置
229
+ const menuItems: IMenuItem[] = [
230
+ ...(menu
231
+ ? [
232
+ {
233
+ key: 'language',
234
+ label: formatMessage({ id: 'avatar.language' }),
235
+ children: [
236
+ {
237
+ key: `language-${LOCALE.ZH_CN}`,
238
+ label: formatMessage({ id: 'avatar.language.zh_CN' }),
239
+ },
240
+ {
241
+ key: `language-${LOCALE.EN_US}`,
242
+ label: formatMessage({ id: 'avatar.language.en' }),
243
+ },
244
+ ],
245
+ },
246
+ {
247
+ key: 'timezone',
248
+ label: getCurrentTimezoneLabel(),
249
+ children:
250
+ timezoneLoading || timezoneList.length === 0
251
+ ? []
252
+ : timezoneList.map((tz) => ({
253
+ key: `${tz.region}/${tz.utc_offset}`,
254
+ label: `${tz.region} (${utcOffsetToTimezone(tz.utc_offset)})`,
255
+ })),
256
+ },
257
+ {
258
+ key: 'theme',
259
+ label: formatMessage({ id: 'avatar.theme' }),
260
+ children: [
261
+ {
262
+ key: 'theme-light',
263
+ label: formatMessage({ id: 'avatar.theme.light' }),
264
+ },
265
+ {
266
+ key: 'theme-dark',
267
+ label: formatMessage({ id: 'avatar.theme.dark' }),
268
+ },
269
+ ],
270
+ },
271
+ ]
272
+ : []),
273
+ {
274
+ key: 'logout',
275
+ label: formatMessage({ id: 'avatar.logout' }),
276
+ },
277
+ ];
278
+
279
+ // 递归渲染菜单项
280
+ const renderMenuItems = (items: IMenuItem[]): React.ReactNode[] => {
281
+ return items.map((item) => {
282
+ if (item.children && item.children.length > 0) {
283
+ const subMenuTitle = (
284
+ <>
285
+ {item.icon && <span className="mr-2">{item.icon}</span>}
286
+ <span>{item.label}</span>
287
+ </>
288
+ );
289
+ return (
290
+ <SubMenu key={item.key} title={subMenuTitle}>
291
+ {renderMenuItems(item.children)}
292
+ </SubMenu>
293
+ );
294
+ }
295
+
296
+ let displayLabel: string | React.ReactNode = item.label;
297
+
298
+ // 否则渲染为普通菜单项
299
+ // 检查是否是主题菜单项,如果是则显示当前使用状态
300
+ if (item.key.startsWith('theme-')) {
301
+ const themeMode = item.key.replace('theme-', '') as 'light' | 'dark';
302
+ if (themeMode === theme) {
303
+ displayLabel = <span className="selected-label">{item.label}</span>;
304
+ }
305
+ }
306
+
307
+ // 语言菜单项选中状态
308
+ if (item.key.startsWith('language-')) {
309
+ const locale = item.key.replace('language-', '') as SupportedLocale;
310
+ const currentLocale = getCurrentLocale();
311
+ if (locale === currentLocale) {
312
+ displayLabel = (
313
+ <>
314
+ <span className="selected-label">
315
+ {languageLabelMap[locale] || ''}
316
+ </span>{' '}
317
+ - <span className="label-name">{item.label}</span>
318
+ </>
319
+ );
320
+ } else {
321
+ displayLabel = (
322
+ <>
323
+ <span>{languageLabelMap[locale] || ''}</span> -{' '}
324
+ <span className="label-name">{item.label}</span>
325
+ </>
326
+ );
327
+ }
328
+ }
329
+
330
+ // 时区菜单项选中状态
331
+ if (item.key.includes('/')) {
332
+ const lastSlashIndex = item.key.lastIndexOf('/');
333
+ const region = item.key.substring(0, lastSlashIndex);
334
+ const utcOffsetStr = item.key.substring(lastSlashIndex + 1);
335
+ const utcOffsetNum = Number(utcOffsetStr);
336
+ const timezoneStr = utcOffsetToTimezone(utcOffsetNum);
337
+ const currentRegion = getTimezoneRegion();
338
+
339
+ if (timezoneStr === currentTimezone && region === currentRegion) {
340
+ displayLabel = <span className="selected-label">{item.label}</span>;
341
+ }
342
+ }
343
+
344
+ return (
345
+ <MenuItem
346
+ key={item.key}
347
+ onClick={() => onMenuClick(item.key, item.path)}
348
+ >
349
+ {item.icon && <span className="mr-2">{item.icon}</span>}
350
+ <span>{displayLabel}</span>
351
+ </MenuItem>
352
+ );
353
+ });
354
+ };
355
+
356
+ return (
357
+ <HeaderDropdown
358
+ position="br"
359
+ triggerProps={{ popupAlign: { bottom: [0, 20] } }}
360
+ droplist={
361
+ <Menu className="avatar-dropdown-menu">
362
+ {renderMenuItems(menuItems)}
363
+ </Menu>
364
+ }
365
+ >
366
+ <div className="flex items-center avatar-dropdown-trigger">
367
+ {avatarSrc && (
368
+ <div className="flex items-center">
369
+ <Avatar size={24} shape="circle">
370
+ <img src={avatarSrc} alt="avatar" />
371
+ </Avatar>
372
+ </div>
373
+ )}
374
+ <AvatarName userName={currentUser?.user_name} />
375
+ <IconFont
376
+ type="webcs-outline_down1"
377
+ className="avatar-dropdown-icon"
378
+ fontSize={12}
379
+ />
380
+ </div>
381
+ </HeaderDropdown>
382
+ );
383
+ };
@@ -0,0 +1,35 @@
1
+ @import '@mico-platform/theme/variables';
2
+
3
+ .avatar-dropdown-trigger {
4
+ background-color: @color-fill-2;
5
+ border-radius: @border-radius-button;
6
+ height: 32px;
7
+ padding: 1px 10px;
8
+ color: @color-text-1;
9
+ cursor: pointer;
10
+ }
11
+
12
+ .avatar-dropdown-trigger:hover {
13
+ background: @color-fill-3;
14
+ }
15
+
16
+ .avatar-dropdown-trigger.arco-dropdown-popup-visible .arco-icon-down {
17
+ transform: rotate(180deg);
18
+ }
19
+
20
+ .avatar-dropdown-menu.arco-dropdown-menu {
21
+ width: 154px;
22
+ }
23
+
24
+ .avatar-dropdown-icon {
25
+ margin-left: 4px;
26
+ transition: transform 0.2s;
27
+ }
28
+
29
+ .selected-label {
30
+ color: @Brand1-6;
31
+ }
32
+
33
+ .label-name {
34
+ color: @color-text-3;
35
+ }
@@ -0,0 +1,2 @@
1
+ export { AvatarDropdown, AvatarName } from './AvatarDropdown';
2
+ export type { GlobalHeaderRightProps } from './AvatarDropdown';
@@ -1,15 +1,179 @@
1
- export const DEFAULT_NAME = 'Umi Max';
2
-
3
1
  /**
4
- * 无需认证的路由路径
2
+ * 应用常量定义
3
+ */
4
+ export const DEFAULT_NAME = '<%= projectName %>';
5
+ /**
6
+ * 路由路径常量
5
7
  */
6
- export const NO_AUTH_ROUTES = {
8
+ export const ROUTES = {
9
+ /** 登录页 */
7
10
  LOGIN: '/user/login',
11
+ /** 注册页 */
12
+ REGISTER: '/user/register',
13
+ /** 注册结果页 */
14
+ REGISTER_RESULT: '/user/register-result',
15
+ /** 403 无权限页 */
8
16
  FORBIDDEN: '/403',
17
+ /** 404 未找到页 */
9
18
  NOT_FOUND: '/404',
10
19
  } as const;
11
20
 
12
21
  /**
13
- * 无需认证的路由列表
22
+ * 无需认证的路由列表(静态配置)
23
+ */
24
+ export const NO_AUTH_ROUTE_LIST: string[] = [
25
+ ROUTES.LOGIN,
26
+ ROUTES.REGISTER,
27
+ ROUTES.REGISTER_RESULT,
28
+ ROUTES.FORBIDDEN,
29
+ ROUTES.NOT_FOUND,
30
+ ];
31
+
32
+ /**
33
+ * 获取合并后的免鉴权路由列表
34
+ * 合并静态常量 + window.__MICO_CONFIG__.noAuthRouteList
35
+ */
36
+ export const getNoAuthRouteList = (): string[] => {
37
+ const dynamicRoutes = window.__MICO_CONFIG__?.noAuthRouteList ?? [];
38
+ return [...new Set([...NO_AUTH_ROUTE_LIST, ...dynamicRoutes])];
39
+ };
40
+
41
+ /**
42
+ * 判断路径是否匹配路由列表(支持精确匹配和前缀匹配)
43
+ * @param pathname - 当前路由路径
44
+ * @param routes - 路由列表
45
+ * @returns 是否匹配
46
+ */
47
+ const matchRouteList = (pathname: string, routes: string[]): boolean => {
48
+ return routes.some((route) => {
49
+ // 前缀匹配:/public/* 匹配 /public/xxx
50
+ if (route.endsWith('/*')) {
51
+ const prefix = route.slice(0, -1); // 去掉末尾的 *,保留 /
52
+ return pathname.startsWith(prefix) || pathname === prefix.slice(0, -1);
53
+ }
54
+ // 精确匹配
55
+ return pathname === route;
56
+ });
57
+ };
58
+
59
+ /**
60
+ * 判断指定路径是否为免认证路由(跳过 SSO 登录)
61
+ * 支持精确匹配和前缀匹配(以 /* 结尾的模式)
62
+ * @param pathname - 当前路由路径
63
+ * @returns 是否免认证
64
+ */
65
+ export const isNoAuthRoute = (pathname: string): boolean => {
66
+ return matchRouteList(pathname, getNoAuthRouteList());
67
+ };
68
+
69
+ /**
70
+ * 免权限校验的路由列表(静态配置)
71
+ * 这些路由不检查菜单权限,但仍可能需要登录
72
+ */
73
+ export const NO_PERMISSION_ROUTE_LIST: string[] = [
74
+ ROUTES.FORBIDDEN,
75
+ ROUTES.NOT_FOUND,
76
+ ];
77
+
78
+ /**
79
+ * 获取合并后的免权限校验路由列表
80
+ * 合并静态常量 + window.__MICO_CONFIG__.noPermissionRouteList
81
+ */
82
+ export const getNoPermissionRouteList = (): string[] => {
83
+ const dynamicRoutes = window.__MICO_CONFIG__?.noPermissionRouteList ?? [];
84
+ return [...new Set([...NO_PERMISSION_ROUTE_LIST, ...dynamicRoutes])];
85
+ };
86
+
87
+ /**
88
+ * 判断指定路径是否为免权限校验路由(跳过菜单权限检查)
89
+ * 支持精确匹配和前缀匹配(以 /* 结尾的模式)
90
+ * @param pathname - 当前路由路径
91
+ * @returns 是否免权限校验
92
+ */
93
+ export const isNoPermissionRoute = (pathname: string): boolean => {
94
+ return matchRouteList(pathname, getNoPermissionRouteList());
95
+ };
96
+
97
+ /**
98
+ * 不显示布局的路由列表(静态配置)
99
+ * 注意:403/404 保留布局,方便用户通过导航返回
100
+ */
101
+ export const NO_LAYOUT_ROUTE_LIST: string[] = [
102
+ ROUTES.LOGIN,
103
+ ROUTES.REGISTER,
104
+ ROUTES.REGISTER_RESULT,
105
+ ];
106
+
107
+ /**
108
+ * 获取合并后的不显示布局路由列表
109
+ * 合并静态常量 + window.__MICO_CONFIG__.noLayoutRouteList
110
+ */
111
+ export const getNoLayoutRouteList = (): string[] => {
112
+ const dynamicRoutes = window.__MICO_CONFIG__?.noLayoutRouteList ?? [];
113
+ return [...new Set([...NO_LAYOUT_ROUTE_LIST, ...dynamicRoutes])];
114
+ };
115
+
116
+ /**
117
+ * 判断指定路径是否不显示布局
118
+ * 支持精确匹配和前缀匹配(以 /* 结尾的模式)
119
+ * @param pathname - 当前路由路径
120
+ * @returns 是否不显示布局
121
+ */
122
+ export const isNoLayoutRoute = (pathname: string): boolean => {
123
+ return matchRouteList(pathname, getNoLayoutRouteList());
124
+ };
125
+
126
+ /**
127
+ * 判断是否关闭权限控制
128
+ * 关闭后:菜单全部显示,路由不校验权限
129
+ * @returns 是否关闭权限控制
130
+ */
131
+ export const isAuthDisabled = (): boolean => {
132
+ return window.__MICO_CONFIG__?.disableAuth === true;
133
+ };
134
+
135
+ /**
136
+ * 主题相关常量
137
+ */
138
+ export const THEME = {
139
+ /** localStorage 存储键 */
140
+ STORAGE_KEY: '<%= ProjectName %>-theme',
141
+ /** 默认主题 */
142
+ DEFAULT: 'light' as const,
143
+ /** 可选主题值 */
144
+ VALUES: ['light', 'dark'] as const,
145
+ } as const;
146
+
147
+ /**
148
+ * 时区相关常量
149
+ */
150
+ export const TIMEZONE = {
151
+ /** localStorage 存储键(IANA 时区,如 Asia/Shanghai) */
152
+ STORAGE_KEY: '<%= ProjectName %>-timezone',
153
+ /** localStorage 存储键(用于展示的地区/名称,可选) */
154
+ REGION_STORAGE_KEY: '<%= ProjectName %>-timezone-region',
155
+ } as const;
156
+
157
+ /**
158
+ * 在线状态相关常量
159
+ */
160
+ export const PRESENCE = {
161
+ /** localStorage 存储键 */
162
+ STORAGE_KEY: '<%= ProjectName %>-presence-status',
163
+ } as const;
164
+
165
+ /**
166
+ * 存储键常量
167
+ */
168
+ export const STORAGE_KEYS = {
169
+ APP_INFO: 'appInfo',
170
+ IS_SUPERUSER: 'is_superuser',
171
+ GROUPS: 'groups',
172
+ TIMEZONE: 'mico-cs-timezone',
173
+ TIMEZONE_REGION: 'mico-cs-timezone-region',
174
+ } as const;
175
+
176
+ /**
177
+ * OSS 上传目录分类
14
178
  */
15
- export const NO_AUTH_ROUTE_LIST = Object.values(NO_AUTH_ROUTES);
179
+ export type TDirCategory = number;
@@ -1,13 +1,26 @@
1
- // ==================== 全局样式 ====================
2
- // 导入共享主题变量(CSS Variables + Less Variables)
3
- @import '<%= packageScope %>/shared-styles';
1
+
2
+ // 再导入 @mico-platform/theme 主题变量(CSS Variables + Less Variables)
3
+ // 这样项目的变量定义会在 UI 库之后,可以正确覆盖
4
+ @import '@mico-platform/theme/variables';
4
5
 
5
6
  * {
6
7
  box-sizing: border-box;
7
8
  }
8
9
 
9
- #<%= projectName %> {
10
- height: 100vh;
11
- min-height: 800px;
10
+ // 禁用 Mac 触摸板双指左右滑动触发的浏览器前进/后退手势
11
+ html {
12
+ overscroll-behavior-x: none;
13
+ height: 100%;
14
+ }
15
+
16
+ body {
17
+ overscroll-behavior-x: none;
18
+ height: 100%;
19
+ background-color: @color-fill-1;
20
+ }
21
+
22
+ #root {
23
+ min-height: max(100%, 800px);
24
+ // 这里是为了让子容器撑满高度,正常的height: 100%并不能撑满
12
25
  display: flex;
13
26
  }
@@ -1,5 +1,6 @@
1
1
  import type { MenuItem, ParsedMenuItem, ParsedRoute } from '@/common/menu';
2
- import { extractRoutes, getWindowMenus, parseMenuItems } from '@/common/menu';
2
+ import { extractRoutes, parseMenuItems } from '@/common/menu';
3
+ import { getMenus } from '@/common/portal-data';
3
4
  import { useMemo } from 'react';
4
5
 
5
6
  /**
@@ -8,7 +9,7 @@ import { useMemo } from 'react';
8
9
  export const useMenu = () => {
9
10
  // 获取原始菜单数据
10
11
  const rawMenus = useMemo<MenuItem[]>(() => {
11
- return getWindowMenus();
12
+ return getMenus();
12
13
  }, []);
13
14
 
14
15
  // 解析后的菜单项(用于渲染菜单组件)