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.
- package/generators/micro-react/templates/CICD/start_dev.sh +1 -1
- package/generators/micro-react/templates/CICD/start_prod.sh +1 -1
- package/generators/micro-react/templates/CICD/start_test.sh +1 -1
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +1 -1
- package/generators/micro-react/templates/apps/layout/docs/common-intl.md +1 -1
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +22 -0
- package/generators/micro-react/templates/apps/layout/src/components/HeaderDropdown/index.tsx +1 -1
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +350 -350
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/avatar-dropdown.less +1 -1
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +3 -51
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +1 -1
- package/generators/micro-react/templates/packages/common-intl/README.md +3 -4
- package/package.json +1 -1
|
@@ -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
|
|
package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { logout, STORAGE_KEYS } from '@/common/auth';
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
getCurrentTimezone,
|
|
4
|
+
getTimezoneRegion,
|
|
5
|
+
setTimezoneRegion as setTimezoneRegionToStorage,
|
|
6
|
+
setTimezone as setTimezoneToStorage,
|
|
7
|
+
utcOffsetToTimezone,
|
|
8
8
|
} from '@/common/helpers';
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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 '@
|
|
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
|
-
|
|
30
|
-
|
|
29
|
+
menu?: boolean;
|
|
30
|
+
children?: React.ReactNode;
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
// 菜单项类型定义
|
|
34
34
|
interface IMenuItem {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
43
|
+
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png';
|
|
44
44
|
|
|
45
45
|
// TODO: 临时假数据,后续接入真实登录态
|
|
46
46
|
const MOCK_USER = {
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
user_name: 'Test User',
|
|
48
|
+
avatar: DEFAULT_AVATAR,
|
|
49
49
|
};
|
|
50
50
|
|
|
51
51
|
export const AvatarName: React.FC<{ userName?: string }> = ({ userName }) => {
|
|
52
|
-
|
|
52
|
+
return <span className="ml-2 mr-2">{userName}</span>;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
const languageLabelMap: Record<string, string> = {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
[LOCALE.ZH_CN]: '中文',
|
|
57
|
+
[LOCALE.EN_US]: 'English',
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
export const AvatarDropdown: React.FC<GlobalHeaderRightProps> = ({ menu }) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
};
|
package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx
CHANGED
|
@@ -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 */}
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
initIntl,
|
|
51
51
|
type ILang,
|
|
52
52
|
} from "@portal-web/common-intl";
|
|
53
|
-
import { Message } from "@
|
|
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) =>
|
|
114
|
-
warning: (msg: string) =>
|
|
112
|
+
error: (msg: string) => console.error(msg),
|
|
113
|
+
warning: (msg: string) => console.warn(msg),
|
|
115
114
|
},
|
|
116
115
|
// 多语言中台接口参数(必填)
|
|
117
116
|
fetchMultilingualDataParams: {
|