generator-mico-cli 0.2.14 → 0.2.16

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.
@@ -37,7 +37,7 @@ pnpm run build:development
37
37
 
38
38
  # 只有在 CI 环境时才写入版本号文件(覆盖写入)
39
39
  if [ "${CI}" = "true" ]; then
40
- echo "VERSION=$VERSION" > .env_x_portal-web
40
+ echo "VERSION=$VERSION" > .env_x_<%= projectName %>portal-web
41
41
  fi
42
42
 
43
43
 
@@ -37,6 +37,6 @@ pnpm run build:production
37
37
 
38
38
  # 只有在 CI 环境时才写入版本号文件(覆盖写入)
39
39
  if [ "${CI}" = "true" ]; then
40
- echo "VERSION=$VERSION" > .env_x_portal-web
40
+ echo "VERSION=$VERSION" > .env_x_<%= projectName %>
41
41
  fi
42
42
 
@@ -38,6 +38,6 @@ pnpm run build:testing
38
38
 
39
39
  # 只有在 CI 环境时才写入版本号文件(覆盖写入)
40
40
  if [ "${CI}" = "true" ]; then
41
- echo "VERSION=$VERSION" > .env_x_portal-web
41
+ echo "VERSION=$VERSION" > .env_x_<%= projectName %>
42
42
  fi
43
43
 
@@ -27,7 +27,7 @@ const config: ReturnType<typeof defineConfig> = {
27
27
  // 不显示布局的路由(全屏页面)
28
28
  // noLayoutRouteList: ['/task-query/*'],
29
29
  // 关闭权限控制(调试用)
30
- disableAuth: false,
30
+ disableAuth: true,
31
31
  };
32
32
  `,
33
33
  },
@@ -65,7 +65,7 @@ import {
65
65
  initIntl,
66
66
  type ILang,
67
67
  } from "@portal-web/common-intl";
68
- import { Message } from "@arco-design/web-react";
68
+ import { Message } from "@mico-platform/ui";
69
69
  import { request } from "@umijs/max";
70
70
 
71
71
  // 初始化国际化模块
@@ -143,6 +143,28 @@ const mockMenus: MockMenuItem[] = [
143
143
  "pageId": null,
144
144
  "page": null,
145
145
  "children": []
146
+ },
147
+ {
148
+ "id": 8,
149
+ "name": "权限管理",
150
+ "nameEn": "Permission Management",
151
+ "nameKey": "cs_web_menu_permission_management",
152
+ "type": "page",
153
+ "path": "/permission",
154
+ "icon": "Permission",
155
+ "enabled": true,
156
+ "sortOrder": 3,
157
+ "pageId": null,
158
+ "page": {
159
+ "id": 8,
160
+ "name": "permission",
161
+ "route": "/permission",
162
+ "enabled": true,
163
+ "htmlUrl": "//localhost:8010",
164
+ "jsUrls": [],
165
+ "cssUrls": []
166
+ },
167
+ "children": []
146
168
  }
147
169
  ]
148
170
 
@@ -1,4 +1,4 @@
1
- import { Dropdown, type DropdownProps } from '@arco-design/web-react';
1
+ import { Dropdown, type DropdownProps } from '@mico-platform/ui';
2
2
  import React from 'react';
3
3
 
4
4
  export type HeaderDropdownProps = {
@@ -1,21 +1,21 @@
1
1
  import { logout, STORAGE_KEYS } from '@/common/auth';
2
2
  import {
3
- getCurrentTimezone,
4
- getTimezoneRegion,
5
- setTimezoneRegion as setTimezoneRegionToStorage,
6
- setTimezone as setTimezoneToStorage,
7
- utcOffsetToTimezone,
3
+ getCurrentTimezone,
4
+ getTimezoneRegion,
5
+ setTimezoneRegion as setTimezoneRegionToStorage,
6
+ setTimezone as setTimezoneToStorage,
7
+ utcOffsetToTimezone,
8
8
  } from '@/common/helpers';
9
9
  import {
10
- getCurrentLocale,
11
- LOCALE,
12
- setLocaleToStorage,
13
- type TLocale,
10
+ getCurrentLocale,
11
+ LOCALE,
12
+ setLocaleToStorage,
13
+ type TLocale,
14
14
  } from '<%= packageScope %>/common-intl';
15
15
  import IconFont from '@/components/IconFont';
16
16
  import { useTheme } from '@/hooks/useTheme';
17
17
  import { getTimezoneList, type ITimezone } from '@/services/config';
18
- import { Avatar, Menu } from '@arco-design/web-react';
18
+ import { Avatar, Menu } from '@mico-platform/ui';
19
19
  import { history, useIntl, useModel } from '@umijs/max';
20
20
  import React, { useEffect, useState, useTransition } from 'react';
21
21
  import { flushSync } from 'react-dom';
@@ -26,363 +26,363 @@ const MenuItem = Menu.Item;
26
26
  const SubMenu = Menu.SubMenu;
27
27
 
28
28
  export type GlobalHeaderRightProps = {
29
- menu?: boolean;
30
- children?: React.ReactNode;
29
+ menu?: boolean;
30
+ children?: React.ReactNode;
31
31
  };
32
32
 
33
33
  // 菜单项类型定义
34
34
  interface IMenuItem {
35
- key: string;
36
- label: string;
37
- icon?: React.ReactNode;
38
- path?: string;
39
- children?: IMenuItem[];
35
+ key: string;
36
+ label: string;
37
+ icon?: React.ReactNode;
38
+ path?: string;
39
+ children?: IMenuItem[];
40
40
  }
41
41
 
42
42
  const DEFAULT_AVATAR =
43
- 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png';
43
+ 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png';
44
44
 
45
45
  // TODO: 临时假数据,后续接入真实登录态
46
46
  const MOCK_USER = {
47
- user_name: 'Test User',
48
- avatar: DEFAULT_AVATAR,
47
+ user_name: 'Test User',
48
+ avatar: DEFAULT_AVATAR,
49
49
  };
50
50
 
51
51
  export const AvatarName: React.FC<{ userName?: string }> = ({ userName }) => {
52
- return <span className="ml-2 mr-2">{userName}</span>;
52
+ return <span className="ml-2 mr-2">{userName}</span>;
53
53
  };
54
54
 
55
55
  const languageLabelMap: Record<string, string> = {
56
- [LOCALE.ZH_CN]: '中文',
57
- [LOCALE.EN_US]: 'English',
56
+ [LOCALE.ZH_CN]: '中文',
57
+ [LOCALE.EN_US]: 'English',
58
58
  };
59
59
 
60
60
  export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
61
- const { setInitialState, initialState } = useModel('@@initialState');
62
- const { formatMessage } = useIntl();
63
- // TODO: 临时使用假数据,后续接入真实登录态
64
- const currentUser = initialState?.currentUser || MOCK_USER;
65
- const { theme, setTheme } = useTheme();
66
- const [timezoneLoading, startTransition] = useTransition();
67
-
68
- // 时区相关状态
69
- const [timezoneList, setTimezoneList] = useState<ITimezone[]>([]);
70
- const [currentTimezone, setCurrentTimezone] = useState<string>(
71
- getCurrentTimezone(),
72
- );
73
-
74
- // 获取当前时区的显示名称
75
- const getCurrentTimezoneLabel = (): string => {
76
- const offsetNum = Number(
77
- currentTimezone.replace('UTC', '').replace('+', ''),
78
- );
79
- const currentRegion = getTimezoneRegion();
80
-
81
- // 从 timezoneList 中找到匹配的时区(同时匹配 region 和 utc_offset)
82
- const matchedTimezone = timezoneList.find(
83
- (tz) => tz.utc_offset === offsetNum && tz.region === currentRegion,
84
- );
85
-
86
- if (matchedTimezone) {
87
- return `${matchedTimezone.region} (${utcOffsetToTimezone(matchedTimezone.utc_offset)})`;
88
- }
89
-
90
- // 回退:只匹配 utc_offset
91
- const fallbackTimezone = timezoneList.find(
92
- (tz) => tz.utc_offset === offsetNum,
93
- );
94
- if (fallbackTimezone) {
95
- return `${fallbackTimezone.region} (${utcOffsetToTimezone(fallbackTimezone.utc_offset)})`;
96
- }
97
-
98
- return utcOffsetToTimezone(offsetNum);
99
- };
100
-
101
- // 管理头像 URL
102
- const [avatarSrc, setAvatarSrc] = useState<string>(
103
- currentUser?.avatar || DEFAULT_AVATAR,
104
- );
105
-
106
- useEffect(() => {
107
- setAvatarSrc(currentUser?.avatar || DEFAULT_AVATAR);
108
- }, [currentUser?.avatar]);
109
-
110
- // 加载时区列表
111
- useEffect(() => {
112
- const loadTimezoneList = async () => {
113
- try {
114
- const response = await getTimezoneList();
115
- if (response?.timezone_list) {
116
- setTimezoneList(response.timezone_list);
117
- }
118
- } catch (error) {
119
- console.error('加载时区列表失败:', error);
120
- }
121
- };
122
-
123
- startTransition(loadTimezoneList as any);
124
- }, []);
125
-
126
- // 监听 localStorage 中时区的变化
127
- useEffect(() => {
128
- const handleStorageChange = (e: StorageEvent) => {
129
- if (e.key === STORAGE_KEYS.TIMEZONE && e.newValue) {
130
- const offsetNum = Number(e.newValue);
131
- if (!Number.isNaN(offsetNum) && e.newValue.trim() !== '') {
132
- setCurrentTimezone(utcOffsetToTimezone(offsetNum));
133
- } else {
134
- setCurrentTimezone(e.newValue);
135
- }
136
- }
137
- };
138
-
139
- window.addEventListener('storage', handleStorageChange);
140
- return () => {
141
- window.removeEventListener('storage', handleStorageChange);
142
- };
143
- }, []);
144
-
145
- const handleAvatarError = () => {
146
- setAvatarSrc(DEFAULT_AVATAR);
147
- };
148
-
149
- const loginOut = async () => {
150
- logout();
151
- const { search, pathname } = window.location;
152
- const urlParams = new URL(window.location.href).searchParams;
153
- const searchParams = new URLSearchParams({
154
- redirect: pathname + search,
155
- });
156
- const redirect = urlParams.get('redirect');
157
- if (window.location.pathname !== '/user/login' && !redirect) {
158
- history.replace({
159
- pathname: '/user/login',
160
- search: searchParams.toString(),
161
- });
162
- }
163
- };
164
-
165
- const onMenuClick = (key: string, path?: string) => {
166
- if (key === 'logout') {
167
- flushSync(() => {
168
- setInitialState((s) => ({ ...s, currentUser: undefined }));
169
- });
170
- loginOut();
171
- return;
172
- }
173
-
174
- // 处理语言切换
175
- if (key.startsWith('language-')) {
176
- const newLocale = key.replace('language-', '') as TLocale;
177
- const currentLocale = getCurrentLocale();
178
- if (newLocale === currentLocale) {
179
- return;
180
- }
181
- // 存储 common-intl 格式的语言标识符,刷新后 umi 会通过映射转换
182
- setLocaleToStorage(newLocale);
183
- window.location.reload();
184
- return;
185
- }
186
-
187
- // 处理时区切换
188
- if (key.includes('/')) {
189
- const lastSlashIndex = key.lastIndexOf('/');
190
- const region = key.substring(0, lastSlashIndex);
191
- const utcOffsetStr = key.substring(lastSlashIndex + 1);
192
- const utcOffsetNum = Number(utcOffsetStr);
193
- const timezoneStr = utcOffsetToTimezone(utcOffsetNum);
194
- const currentRegion = getTimezoneRegion();
195
-
196
- if (timezoneStr === currentTimezone && region === currentRegion) {
197
- return;
198
- }
199
-
200
- setTimezoneToStorage(utcOffsetStr);
201
- setTimezoneRegionToStorage(region);
202
- setCurrentTimezone(timezoneStr);
203
- window.location.reload();
204
- return;
205
- }
206
-
207
- // 处理主题切换
208
- if (key.startsWith('theme-')) {
209
- const themeMode = key.replace('theme-', '') as 'light' | 'dark';
210
- // 如果切换的主题和当前主题一致,则无需切换
211
- if (themeMode === theme) {
212
- return;
213
- }
214
- // 切换主题
215
- setTheme(themeMode);
216
- return;
217
- }
218
-
219
- if (path) {
220
- history.push(path);
221
- } else {
222
- history.push(`/account/${key}`);
223
- }
224
- };
225
-
226
- // TODO: 临时关闭加载逻辑,使用假数据
227
- // const loading = <Spin size={16} className="ml-2 mr-2" />;
228
- // if (!initialState) {
229
- // return loading;
230
- // }
231
- // if (!currentUser || !currentUser.user_name) {
232
- // return loading;
233
- // }
234
-
235
- // 菜单配置
236
- const menuItems: IMenuItem[] = [
237
- ...(menu
238
- ? [
239
- {
240
- key: 'language',
241
- label: formatMessage({ id: 'avatar.language' }),
242
- children: [
243
- {
244
- key: `language-${LOCALE.ZH_CN}`,
245
- label: formatMessage({ id: 'avatar.language.zh_CN' }),
246
- },
247
- {
248
- key: `language-${LOCALE.EN_US}`,
249
- label: formatMessage({ id: 'avatar.language.en' }),
250
- },
251
- ],
252
- },
253
- {
254
- key: 'timezone',
255
- label: getCurrentTimezoneLabel(),
256
- children:
257
- timezoneLoading || timezoneList.length === 0
258
- ? []
259
- : timezoneList.map((tz) => ({
260
- key: `${tz.region}/${tz.utc_offset}`,
261
- label: `${tz.region} (${utcOffsetToTimezone(tz.utc_offset)})`,
262
- })),
263
- },
264
- {
265
- key: 'theme',
266
- label: formatMessage({ id: 'avatar.theme' }),
267
- children: [
268
- {
269
- key: 'theme-light',
270
- label: formatMessage({ id: 'avatar.theme.light' }),
271
- },
272
- {
273
- key: 'theme-dark',
274
- label: formatMessage({ id: 'avatar.theme.dark' }),
275
- },
276
- ],
277
- },
278
- ]
279
- : []),
280
- {
281
- key: 'logout',
282
- label: formatMessage({ id: 'avatar.logout' }),
283
- },
284
- ];
285
-
286
- // 递归渲染菜单项
287
- const renderMenuItems = (items: IMenuItem[]): React.ReactNode[] => {
288
- return items.map((item) => {
289
- if (item.children && item.children.length > 0) {
290
- const subMenuTitle = (
291
- <>
292
- {item.icon && <span className="mr-2">{item.icon}</span>}
293
- <span>{item.label}</span>
294
- </>
295
- );
296
- return (
297
- <SubMenu key={item.key} title={subMenuTitle}>
298
- {renderMenuItems(item.children)}
299
- </SubMenu>
300
- );
301
- }
302
-
303
- let displayLabel: string | React.ReactNode = item.label;
304
-
305
- // 否则渲染为普通菜单项
306
- // 检查是否是主题菜单项,如果是则显示当前使用状态
307
- if (item.key.startsWith('theme-')) {
308
- const themeMode = item.key.replace('theme-', '') as 'light' | 'dark';
309
- if (themeMode === theme) {
310
- displayLabel = <span className="selected-label">{item.label}</span>;
311
- }
312
- }
313
-
314
- // 语言菜单项选中状态
315
- if (item.key.startsWith('language-')) {
316
- const locale = item.key.replace('language-', '') as TLocale;
317
- const currentLocale = getCurrentLocale();
318
- if (locale === currentLocale) {
319
- displayLabel = (
320
- <>
321
- <span className="selected-label">
322
- {languageLabelMap[locale] || ''}
323
- </span>{' '}
324
- - <span className="label-name">{item.label}</span>
325
- </>
326
- );
327
- } else {
328
- displayLabel = (
329
- <>
330
- <span>{languageLabelMap[locale] || ''}</span> -{' '}
331
- <span className="label-name">{item.label}</span>
332
- </>
333
- );
334
- }
335
- }
336
-
337
- // 时区菜单项选中状态
338
- if (item.key.includes('/')) {
339
- const lastSlashIndex = item.key.lastIndexOf('/');
340
- const region = item.key.substring(0, lastSlashIndex);
341
- const utcOffsetStr = item.key.substring(lastSlashIndex + 1);
342
- const utcOffsetNum = Number(utcOffsetStr);
343
- const timezoneStr = utcOffsetToTimezone(utcOffsetNum);
344
- const currentRegion = getTimezoneRegion();
345
-
346
- if (timezoneStr === currentTimezone && region === currentRegion) {
347
- displayLabel = <span className="selected-label">{item.label}</span>;
348
- }
349
- }
350
-
351
- return (
352
- <MenuItem
353
- key={item.key}
354
- onClick={() => onMenuClick(item.key, item.path)}
355
- >
356
- {item.icon && <span className="mr-2">{item.icon}</span>}
357
- <span>{displayLabel}</span>
358
- </MenuItem>
359
- );
360
- });
361
- };
362
-
363
- return (
364
- <HeaderDropdown
365
- position="br"
366
- triggerProps={{ popupAlign: { bottom: [0, 20] } }}
367
- droplist={
368
- <Menu className="avatar-dropdown-menu">
369
- {renderMenuItems(menuItems)}
370
- </Menu>
371
- }
372
- >
373
- <div className="flex items-center avatar-dropdown-trigger">
374
- <div className="flex items-center">
375
- <Avatar size={24} shape="circle">
376
- <img src={avatarSrc} alt="avatar" onError={handleAvatarError} />
377
- </Avatar>
378
- </div>
379
- <AvatarName userName={currentUser?.user_name} />
380
- <IconFont
381
- type="webcs-outline_down1"
382
- className="avatar-dropdown-icon"
383
- fontSize={12}
384
- />
385
- </div>
386
- </HeaderDropdown>
387
- );
61
+ const { setInitialState, initialState } = useModel('@@initialState');
62
+ const { formatMessage } = useIntl();
63
+ // TODO: 临时使用假数据,后续接入真实登录态
64
+ const currentUser = initialState?.currentUser || MOCK_USER;
65
+ const { theme, setTheme } = useTheme();
66
+ const [timezoneLoading, startTransition] = useTransition();
67
+
68
+ // 时区相关状态
69
+ const [timezoneList, setTimezoneList] = useState<ITimezone[]>([]);
70
+ const [currentTimezone, setCurrentTimezone] = useState<string>(
71
+ getCurrentTimezone(),
72
+ );
73
+
74
+ // 获取当前时区的显示名称
75
+ const getCurrentTimezoneLabel = (): string => {
76
+ const offsetNum = Number(
77
+ currentTimezone.replace('UTC', '').replace('+', ''),
78
+ );
79
+ const currentRegion = getTimezoneRegion();
80
+
81
+ // 从 timezoneList 中找到匹配的时区(同时匹配 region 和 utc_offset)
82
+ const matchedTimezone = timezoneList.find(
83
+ (tz) => tz.utc_offset === offsetNum && tz.region === currentRegion,
84
+ );
85
+
86
+ if (matchedTimezone) {
87
+ return `${matchedTimezone.region} (${utcOffsetToTimezone(matchedTimezone.utc_offset)})`;
88
+ }
89
+
90
+ // 回退:只匹配 utc_offset
91
+ const fallbackTimezone = timezoneList.find(
92
+ (tz) => tz.utc_offset === offsetNum,
93
+ );
94
+ if (fallbackTimezone) {
95
+ return `${fallbackTimezone.region} (${utcOffsetToTimezone(fallbackTimezone.utc_offset)})`;
96
+ }
97
+
98
+ return utcOffsetToTimezone(offsetNum);
99
+ };
100
+
101
+ // 管理头像 URL
102
+ const [avatarSrc, setAvatarSrc] = useState<string>(
103
+ currentUser?.avatar || DEFAULT_AVATAR,
104
+ );
105
+
106
+ useEffect(() => {
107
+ setAvatarSrc(currentUser?.avatar || DEFAULT_AVATAR);
108
+ }, [currentUser?.avatar]);
109
+
110
+ // 加载时区列表
111
+ useEffect(() => {
112
+ const loadTimezoneList = async () => {
113
+ try {
114
+ const response = await getTimezoneList();
115
+ if (response?.timezone_list) {
116
+ setTimezoneList(response.timezone_list);
117
+ }
118
+ } catch (error) {
119
+ console.error('加载时区列表失败:', error);
120
+ }
121
+ };
122
+
123
+ startTransition(loadTimezoneList as any);
124
+ }, []);
125
+
126
+ // 监听 localStorage 中时区的变化
127
+ useEffect(() => {
128
+ const handleStorageChange = (e: StorageEvent) => {
129
+ if (e.key === STORAGE_KEYS.TIMEZONE && e.newValue) {
130
+ const offsetNum = Number(e.newValue);
131
+ if (!Number.isNaN(offsetNum) && e.newValue.trim() !== '') {
132
+ setCurrentTimezone(utcOffsetToTimezone(offsetNum));
133
+ } else {
134
+ setCurrentTimezone(e.newValue);
135
+ }
136
+ }
137
+ };
138
+
139
+ window.addEventListener('storage', handleStorageChange);
140
+ return () => {
141
+ window.removeEventListener('storage', handleStorageChange);
142
+ };
143
+ }, []);
144
+
145
+ const handleAvatarError = () => {
146
+ setAvatarSrc(DEFAULT_AVATAR);
147
+ };
148
+
149
+ const loginOut = async () => {
150
+ logout();
151
+ const { search, pathname } = window.location;
152
+ const urlParams = new URL(window.location.href).searchParams;
153
+ const searchParams = new URLSearchParams({
154
+ redirect: pathname + search,
155
+ });
156
+ const redirect = urlParams.get('redirect');
157
+ if (window.location.pathname !== '/user/login' && !redirect) {
158
+ history.replace({
159
+ pathname: '/user/login',
160
+ search: searchParams.toString(),
161
+ });
162
+ }
163
+ };
164
+
165
+ const onMenuClick = (key: string, path?: string) => {
166
+ if (key === 'logout') {
167
+ flushSync(() => {
168
+ setInitialState((s) => ({ ...s, currentUser: undefined }));
169
+ });
170
+ loginOut();
171
+ return;
172
+ }
173
+
174
+ // 处理语言切换
175
+ if (key.startsWith('language-')) {
176
+ const newLocale = key.replace('language-', '') as TLocale;
177
+ const currentLocale = getCurrentLocale();
178
+ if (newLocale === currentLocale) {
179
+ return;
180
+ }
181
+ // 存储 common-intl 格式的语言标识符,刷新后 umi 会通过映射转换
182
+ setLocaleToStorage(newLocale);
183
+ window.location.reload();
184
+ return;
185
+ }
186
+
187
+ // 处理时区切换
188
+ if (key.includes('/')) {
189
+ const lastSlashIndex = key.lastIndexOf('/');
190
+ const region = key.substring(0, lastSlashIndex);
191
+ const utcOffsetStr = key.substring(lastSlashIndex + 1);
192
+ const utcOffsetNum = Number(utcOffsetStr);
193
+ const timezoneStr = utcOffsetToTimezone(utcOffsetNum);
194
+ const currentRegion = getTimezoneRegion();
195
+
196
+ if (timezoneStr === currentTimezone && region === currentRegion) {
197
+ return;
198
+ }
199
+
200
+ setTimezoneToStorage(utcOffsetStr);
201
+ setTimezoneRegionToStorage(region);
202
+ setCurrentTimezone(timezoneStr);
203
+ window.location.reload();
204
+ return;
205
+ }
206
+
207
+ // 处理主题切换
208
+ if (key.startsWith('theme-')) {
209
+ const themeMode = key.replace('theme-', '') as 'light' | 'dark';
210
+ // 如果切换的主题和当前主题一致,则无需切换
211
+ if (themeMode === theme) {
212
+ return;
213
+ }
214
+ // 切换主题
215
+ setTheme(themeMode);
216
+ return;
217
+ }
218
+
219
+ if (path) {
220
+ history.push(path);
221
+ } else {
222
+ history.push(`/account/${key}`);
223
+ }
224
+ };
225
+
226
+ // TODO: 临时关闭加载逻辑,使用假数据
227
+ // const loading = <Spin size={16} className="ml-2 mr-2" />;
228
+ // if (!initialState) {
229
+ // return loading;
230
+ // }
231
+ // if (!currentUser || !currentUser.user_name) {
232
+ // return loading;
233
+ // }
234
+
235
+ // 菜单配置
236
+ const menuItems: IMenuItem[] = [
237
+ ...(menu
238
+ ? [
239
+ {
240
+ key: 'language',
241
+ label: formatMessage({ id: 'avatar.language' }),
242
+ children: [
243
+ {
244
+ key: `language-${LOCALE.ZH_CN}`,
245
+ label: formatMessage({ id: 'avatar.language.zh_CN' }),
246
+ },
247
+ {
248
+ key: `language-${LOCALE.EN_US}`,
249
+ label: formatMessage({ id: 'avatar.language.en' }),
250
+ },
251
+ ],
252
+ },
253
+ {
254
+ key: 'timezone',
255
+ label: getCurrentTimezoneLabel(),
256
+ children:
257
+ timezoneLoading || timezoneList.length === 0
258
+ ? []
259
+ : timezoneList.map((tz) => ({
260
+ key: `${tz.region}/${tz.utc_offset}`,
261
+ label: `${tz.region} (${utcOffsetToTimezone(tz.utc_offset)})`,
262
+ })),
263
+ },
264
+ {
265
+ key: 'theme',
266
+ label: formatMessage({ id: 'avatar.theme' }),
267
+ children: [
268
+ {
269
+ key: 'theme-light',
270
+ label: formatMessage({ id: 'avatar.theme.light' }),
271
+ },
272
+ {
273
+ key: 'theme-dark',
274
+ label: formatMessage({ id: 'avatar.theme.dark' }),
275
+ },
276
+ ],
277
+ },
278
+ ]
279
+ : []),
280
+ {
281
+ key: 'logout',
282
+ label: formatMessage({ id: 'avatar.logout' }),
283
+ },
284
+ ];
285
+
286
+ // 递归渲染菜单项
287
+ const renderMenuItems = (items: IMenuItem[]): React.ReactNode[] => {
288
+ return items.map((item) => {
289
+ if (item.children && item.children.length > 0) {
290
+ const subMenuTitle = (
291
+ <>
292
+ {item.icon && <span className="mr-2">{item.icon}</span>}
293
+ <span>{item.label}</span>
294
+ </>
295
+ );
296
+ return (
297
+ <SubMenu key={item.key} title={subMenuTitle}>
298
+ {renderMenuItems(item.children)}
299
+ </SubMenu>
300
+ );
301
+ }
302
+
303
+ let displayLabel: string | React.ReactNode = item.label;
304
+
305
+ // 否则渲染为普通菜单项
306
+ // 检查是否是主题菜单项,如果是则显示当前使用状态
307
+ if (item.key.startsWith('theme-')) {
308
+ const themeMode = item.key.replace('theme-', '') as 'light' | 'dark';
309
+ if (themeMode === theme) {
310
+ displayLabel = <span className="selected-label">{item.label}</span>;
311
+ }
312
+ }
313
+
314
+ // 语言菜单项选中状态
315
+ if (item.key.startsWith('language-')) {
316
+ const locale = item.key.replace('language-', '') as TLocale;
317
+ const currentLocale = getCurrentLocale();
318
+ if (locale === currentLocale) {
319
+ displayLabel = (
320
+ <>
321
+ <span className="selected-label">
322
+ {languageLabelMap[locale] || ''}
323
+ </span>{' '}
324
+ - <span className="label-name">{item.label}</span>
325
+ </>
326
+ );
327
+ } else {
328
+ displayLabel = (
329
+ <>
330
+ <span>{languageLabelMap[locale] || ''}</span> -{' '}
331
+ <span className="label-name">{item.label}</span>
332
+ </>
333
+ );
334
+ }
335
+ }
336
+
337
+ // 时区菜单项选中状态
338
+ if (item.key.includes('/')) {
339
+ const lastSlashIndex = item.key.lastIndexOf('/');
340
+ const region = item.key.substring(0, lastSlashIndex);
341
+ const utcOffsetStr = item.key.substring(lastSlashIndex + 1);
342
+ const utcOffsetNum = Number(utcOffsetStr);
343
+ const timezoneStr = utcOffsetToTimezone(utcOffsetNum);
344
+ const currentRegion = getTimezoneRegion();
345
+
346
+ if (timezoneStr === currentTimezone && region === currentRegion) {
347
+ displayLabel = <span className="selected-label">{item.label}</span>;
348
+ }
349
+ }
350
+
351
+ return (
352
+ <MenuItem
353
+ key={item.key}
354
+ onClick={() => onMenuClick(item.key, item.path)}
355
+ >
356
+ {item.icon && <span className="mr-2">{item.icon}</span>}
357
+ <span>{displayLabel}</span>
358
+ </MenuItem>
359
+ );
360
+ });
361
+ };
362
+
363
+ return (
364
+ <HeaderDropdown
365
+ position="br"
366
+ triggerProps={{ popupAlign: { bottom: [0, 20] } }}
367
+ droplist={
368
+ <Menu className="avatar-dropdown-menu">
369
+ {renderMenuItems(menuItems)}
370
+ </Menu>
371
+ }
372
+ >
373
+ <div className="flex items-center avatar-dropdown-trigger">
374
+ <div className="flex items-center">
375
+ <Avatar size={24} shape="circle">
376
+ <img src={avatarSrc} alt="avatar" onError={handleAvatarError} />
377
+ </Avatar>
378
+ </div>
379
+ <AvatarName userName={currentUser?.user_name} />
380
+ <IconFont
381
+ type="webcs-outline_down1"
382
+ className="avatar-dropdown-icon"
383
+ fontSize={12}
384
+ />
385
+ </div>
386
+ </HeaderDropdown>
387
+ );
388
388
  };
@@ -1,4 +1,4 @@
1
- @import '<%= packageScope %>/shared-styles/variables-only';
1
+ @import '@mico-platform/theme/variables';
2
2
 
3
3
  .avatar-dropdown-trigger {
4
4
  background-color: @color-fill-2;
@@ -1,60 +1,12 @@
1
- import { useTheme } from '@/hooks/useTheme';
2
- import { logout } from '@/common/auth';
3
- import { DEFAULT_NAME } from '@/constants';
4
- import { ROUTES } from '@/constants';
5
- import {
6
- Avatar,
7
- Divider,
8
- Dropdown,
9
- Layout,
10
- Menu,
11
- Space,
12
- } from '@mico-platform/ui';
13
-
14
- import {
15
- IconMoonFill,
16
- IconPoweroff,
17
- IconSettings,
18
- IconSunFill,
19
- } from '@mico-platform/ui/icon';
20
- import { useModel } from '@umijs/max';
21
- import React, { useCallback } from 'react';
22
1
  import { AvatarDropdown } from '@/components/RightContent';
2
+ import { DEFAULT_NAME } from '@/constants';
3
+ import { Layout, Space } from '@mico-platform/ui';
4
+ import React from 'react';
23
5
  import './index.less';
24
6
 
25
7
  const Header = Layout.Header;
26
8
 
27
9
  const LayoutHeader: React.FC = () => {
28
- const { initialState } = useModel('@@initialState');
29
- const { theme, toggleTheme } = useTheme();
30
-
31
- const handleMenuClick = useCallback((key: string) => {
32
- if (key === 'logout') {
33
- // 清除本地存储的认证信息
34
- logout();
35
- // 跳转到登录页
36
- window.location.href = ROUTES.LOGIN;
37
- return;
38
- } else if (key === 'settings') {
39
- // TODO: Navigate to settings page
40
- console.log('settings');
41
- }
42
- }, []);
43
-
44
- const droplist = (
45
- <Menu onClickMenuItem={handleMenuClick}>
46
- <Menu.Item key="settings">
47
- <IconSettings style={{ marginRight: 8 }} />
48
- 设置
49
- </Menu.Item>
50
- <Divider style={{ margin: '4px 0' }} />
51
- <Menu.Item key="logout">
52
- <IconPoweroff style={{ marginRight: 8 }} />
53
- 退出登录
54
- </Menu.Item>
55
- </Menu>
56
- );
57
-
58
10
  return (
59
11
  <Header className="layout-header">
60
12
  {/* Logo */}
@@ -1,4 +1,4 @@
1
- @import '<%= packageScope %>/shared-styles/variables-only';
1
+ @import '@mico-platform/theme/variables';
2
2
 
3
3
  .container {
4
4
  padding-top: 30px;
@@ -50,7 +50,7 @@ import {
50
50
  initIntl,
51
51
  type ILang,
52
52
  } from "@portal-web/common-intl";
53
- import { Message } from "@arco-design/web-react";
53
+ import { Message } from "@mico-platform/ui";
54
54
  import { request } from "@umijs/max";
55
55
 
56
56
  // 初始化国际化模块
@@ -99,7 +99,6 @@ import { intl } from "@portal-web/common-intl";
99
99
 
100
100
  ```typescript
101
101
  import { initIntl } from "@portal-web/common-intl";
102
- import { Toast } from "@arco-design/mobile-react";
103
102
  import { request } from "@umijs/max";
104
103
  import { convertLocaleToLangParam } from "./locales/utils";
105
104
  import { getSearchParams } from "@/common/jsbridge/jsbridge";
@@ -110,8 +109,8 @@ const fetchMultilingualData = initIntl({
110
109
  requestInstance: request,
111
110
  // 消息提示实例(必填)
112
111
  messageInstance: {
113
- error: (msg: string) => Toast.error(msg),
114
- warning: (msg: string) => Toast.warn(msg),
112
+ error: (msg: string) => console.error(msg),
113
+ warning: (msg: string) => console.warn(msg),
115
114
  },
116
115
  // 多语言中台接口参数(必填)
117
116
  fetchMultilingualDataParams: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-mico-cli",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "Yeoman generator for Mico CLI projects",
5
5
  "keywords": ["yeoman-generator", "generator", "cli"],
6
6
  "license": "MIT",