generator-mico-cli 0.2.13 → 0.2.15
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/README.md +1 -1
- package/generators/micro-react/index.js +34 -10
- package/generators/micro-react/templates/.commitlintrc.js +1 -0
- package/generators/micro-react/templates/.cursor/rules/coding-conventions.mdc +1 -1
- package/generators/micro-react/templates/.cursor/rules/development-guide.mdc +1 -1
- package/generators/micro-react/templates/.cursor/rules/layout-app.mdc +2 -5
- package/generators/micro-react/templates/.cursor/rules/project-overview.mdc +2 -2
- package/generators/micro-react/templates/.cursor/rules/theme-system.mdc +10 -12
- package/generators/micro-react/templates/AGENTS.md +2 -2
- package/generators/micro-react/templates/CLAUDE.md +1 -2
- package/generators/micro-react/templates/README.md +2 -2
- package/generators/micro-react/templates/_npmrc +3 -0
- package/generators/micro-react/templates/apps/layout/config/config.dev.ts +15 -0
- package/generators/micro-react/templates/apps/layout/config/config.ts +4 -3
- package/generators/micro-react/templates/apps/layout/docs/common-intl.md +370 -0
- package/generators/micro-react/templates/apps/layout/docs/feature-404/351/241/265/351/235/242.md +2 -2
- 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 +21 -25
- 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 +15 -16
- 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 +1 -1
- package/generators/micro-react/templates/apps/layout/docs/utils-timezone.md +322 -0
- package/generators/micro-react/templates/apps/layout/mock/menus.ts +39 -1
- package/generators/micro-react/templates/apps/layout/package.json +5 -2
- package/generators/micro-react/templates/apps/layout/src/app.tsx +22 -10
- package/generators/micro-react/templates/apps/layout/src/common/auth/index.ts +3 -0
- package/generators/micro-react/templates/apps/layout/src/common/helpers.ts +177 -0
- package/generators/micro-react/templates/apps/layout/src/common/locale.ts +56 -4
- package/generators/micro-react/templates/apps/layout/src/common/menu/parser.ts +84 -5
- package/generators/micro-react/templates/apps/layout/src/common/menu/types.ts +13 -1
- package/generators/micro-react/templates/apps/layout/src/common/request/interceptors.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/common/request/sso.ts +7 -1
- package/generators/micro-react/templates/apps/layout/src/common/theme.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/common/upload/oss.ts +2 -3
- package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.less +8 -2
- package/generators/micro-react/templates/apps/layout/src/components/AppTabs/index.tsx +23 -6
- package/generators/micro-react/templates/apps/layout/src/components/HeaderDropdown/index.tsx +22 -0
- package/generators/micro-react/templates/apps/layout/src/components/IconFont/index.tsx +1 -1
- package/generators/micro-react/templates/apps/layout/src/components/MicroAppLoader/index.tsx +1 -1
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx +388 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/avatar-dropdown.less +35 -0
- package/generators/micro-react/templates/apps/layout/src/components/RightContent/index.ts +2 -0
- package/generators/micro-react/templates/apps/layout/src/constants/index.ts +2 -0
- package/generators/micro-react/templates/apps/layout/src/global.less +3 -6
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less +3 -1
- package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx +8 -53
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.less +3 -1
- package/generators/micro-react/templates/apps/layout/src/layouts/components/menu/index.tsx +4 -4
- package/generators/micro-react/templates/apps/layout/src/layouts/index.less +83 -13
- package/generators/micro-react/templates/apps/layout/src/layouts/index.tsx +40 -25
- package/generators/micro-react/templates/apps/layout/src/locales/en-US.ts +9 -0
- package/generators/micro-react/templates/apps/layout/src/locales/zh-CN.ts +9 -0
- package/generators/micro-react/templates/apps/layout/src/pages/403/index.tsx +2 -1
- package/generators/micro-react/templates/apps/layout/src/pages/404/index.tsx +1 -1
- package/generators/micro-react/templates/apps/layout/src/pages/Home/index.less +3 -0
- package/generators/micro-react/templates/apps/layout/src/pages/User/Login/index.less +1 -1
- package/generators/micro-react/templates/apps/layout/src/pages/User/Login/index.tsx +3 -3
- package/generators/micro-react/templates/apps/layout/src/requestErrorConfig.ts +1 -1
- package/generators/micro-react/templates/apps/layout/src/services/config/index.ts +63 -0
- package/generators/micro-react/templates/apps/layout/src/services/config/type.ts +32 -0
- package/generators/micro-react/templates/apps/layout/src/services/user.ts +23 -1
- package/generators/micro-react/templates/package.json +4 -1
- package/generators/micro-react/templates/packages/common-intl/.turbo/turbo-build.log +13 -0
- package/generators/micro-react/templates/packages/common-intl/README.md +427 -0
- package/generators/micro-react/templates/packages/common-intl/dist/index.d.ts +3 -0
- package/generators/micro-react/templates/packages/common-intl/dist/index.js +4388 -0
- package/generators/micro-react/templates/packages/common-intl/dist/indexedDBUtils.d.ts +13 -0
- package/generators/micro-react/templates/packages/common-intl/dist/intl.d.ts +1022 -0
- package/generators/micro-react/templates/packages/common-intl/dist/utils.d.ts +122 -0
- package/generators/micro-react/templates/packages/common-intl/package.json +34 -0
- package/generators/micro-react/templates/packages/common-intl/src/index.ts +7 -0
- package/generators/micro-react/templates/packages/common-intl/src/indexedDBUtils.ts +51 -0
- package/generators/micro-react/templates/packages/common-intl/src/intl.ts +5709 -0
- package/generators/micro-react/templates/packages/common-intl/src/utils.ts +481 -0
- package/generators/micro-react/templates/packages/common-intl/tsconfig.json +22 -0
- package/generators/micro-react/templates/packages/common-intl/vite.config.ts +25 -0
- package/generators/micro-react/templates/turbo.json +3 -0
- package/generators/subapp-react/index.js +13 -18
- package/generators/subapp-react/templates/homepage/README.md +3 -3
- package/generators/subapp-react/templates/homepage/config/config.dev.ts +8 -7
- package/generators/subapp-react/templates/homepage/config/config.prod.development.ts +2 -3
- package/generators/subapp-react/templates/homepage/config/config.prod.testing.ts +2 -3
- package/generators/subapp-react/templates/homepage/config/config.prod.ts +4 -5
- package/generators/subapp-react/templates/homepage/package.json +3 -2
- package/generators/subapp-react/templates/homepage/src/global.less +3 -3
- package/generators/subapp-react/templates/homepage/src/pages/index.less +2 -2
- package/generators/subapp-react/templates/homepage/src/pages/index.tsx +72 -33
- package/generators/subapp-react/templates/homepage/src/styles/theme.less +1 -1
- package/lib/utils.js +43 -0
- package/package.json +8 -11
- package/generators/micro-react/templates/packages/shared-styles/README.md +0 -124
- package/generators/micro-react/templates/packages/shared-styles/arco-design-mobile-override.less +0 -91
- package/generators/micro-react/templates/packages/shared-styles/arco-override.less +0 -119
- package/generators/micro-react/templates/packages/shared-styles/index.d.ts +0 -44
- package/generators/micro-react/templates/packages/shared-styles/index.less +0 -13
- package/generators/micro-react/templates/packages/shared-styles/package.json +0 -30
- package/generators/micro-react/templates/packages/shared-styles/theme-inject.less +0 -10
- package/generators/micro-react/templates/packages/shared-styles/themes/dark/custom-var.less +0 -290
- package/generators/micro-react/templates/packages/shared-styles/themes/normal/custom-var.less +0 -269
- package/generators/micro-react/templates/packages/shared-styles/variables-only.less +0 -433
- package/generators/micro-react/templates/packages/shared-styles/variables.less +0 -452
package/generators/micro-react/templates/apps/layout/src/components/RightContent/AvatarDropdown.tsx
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { logout, STORAGE_KEYS } from '@/common/auth';
|
|
2
|
+
import {
|
|
3
|
+
getCurrentTimezone,
|
|
4
|
+
getTimezoneRegion,
|
|
5
|
+
setTimezoneRegion as setTimezoneRegionToStorage,
|
|
6
|
+
setTimezone as setTimezoneToStorage,
|
|
7
|
+
utcOffsetToTimezone,
|
|
8
|
+
} from '@/common/helpers';
|
|
9
|
+
import {
|
|
10
|
+
getCurrentLocale,
|
|
11
|
+
LOCALE,
|
|
12
|
+
setLocaleToStorage,
|
|
13
|
+
type TLocale,
|
|
14
|
+
} from '<%= packageScope %>/common-intl';
|
|
15
|
+
import IconFont from '@/components/IconFont';
|
|
16
|
+
import { useTheme } from '@/hooks/useTheme';
|
|
17
|
+
import { getTimezoneList, type ITimezone } from '@/services/config';
|
|
18
|
+
import { Avatar, Menu } from '@mico-platform/ui';
|
|
19
|
+
import { history, useIntl, useModel } from '@umijs/max';
|
|
20
|
+
import React, { useEffect, useState, useTransition } from 'react';
|
|
21
|
+
import { flushSync } from 'react-dom';
|
|
22
|
+
import HeaderDropdown from '../HeaderDropdown';
|
|
23
|
+
import './avatar-dropdown.less';
|
|
24
|
+
|
|
25
|
+
const MenuItem = Menu.Item;
|
|
26
|
+
const SubMenu = Menu.SubMenu;
|
|
27
|
+
|
|
28
|
+
export type GlobalHeaderRightProps = {
|
|
29
|
+
menu?: boolean;
|
|
30
|
+
children?: React.ReactNode;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// 菜单项类型定义
|
|
34
|
+
interface IMenuItem {
|
|
35
|
+
key: string;
|
|
36
|
+
label: string;
|
|
37
|
+
icon?: React.ReactNode;
|
|
38
|
+
path?: string;
|
|
39
|
+
children?: IMenuItem[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const DEFAULT_AVATAR =
|
|
43
|
+
'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png';
|
|
44
|
+
|
|
45
|
+
// TODO: 临时假数据,后续接入真实登录态
|
|
46
|
+
const MOCK_USER = {
|
|
47
|
+
user_name: 'Test User',
|
|
48
|
+
avatar: DEFAULT_AVATAR,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const AvatarName: React.FC<{ userName?: string }> = ({ userName }) => {
|
|
52
|
+
return <span className="ml-2 mr-2">{userName}</span>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const languageLabelMap: Record<string, string> = {
|
|
56
|
+
[LOCALE.ZH_CN]: '中文',
|
|
57
|
+
[LOCALE.EN_US]: 'English',
|
|
58
|
+
};
|
|
59
|
+
|
|
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
|
+
);
|
|
388
|
+
};
|
|
@@ -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
|
+
}
|
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
// ==================== 全局样式 ====================
|
|
2
|
-
// 先导入 Arco Design CSS(确保它在项目样式之前)
|
|
3
|
-
@import (css) '~@arco-design/web-react/dist/css/arco.css';
|
|
4
1
|
|
|
5
|
-
//
|
|
6
|
-
// 这样项目的变量定义会在
|
|
7
|
-
@import '
|
|
2
|
+
// 再导入 @mico-platform/theme 主题变量(CSS Variables + Less Variables)
|
|
3
|
+
// 这样项目的变量定义会在 UI 库之后,可以正确覆盖
|
|
4
|
+
@import '@mico-platform/theme';
|
|
8
5
|
|
|
9
6
|
* {
|
|
10
7
|
box-sizing: border-box;
|
package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.less
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import '
|
|
1
|
+
@import '@mico-platform/theme/variables';
|
|
2
2
|
|
|
3
3
|
// Common mixins
|
|
4
4
|
.header-gray-btn-mixin() {
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
z-index: @header-z-index;
|
|
23
23
|
height: @header-height;
|
|
24
24
|
line-height: @header-height;
|
|
25
|
+
position: fixed;
|
|
26
|
+
width: 100vw;
|
|
25
27
|
padding: 0 @spacing-lg;
|
|
26
28
|
display: flex;
|
|
27
29
|
align-items: center;
|
package/generators/micro-react/templates/apps/layout/src/layouts/components/header/index.tsx
CHANGED
|
@@ -1,58 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { logout } from '@/common/auth';
|
|
1
|
+
import { AvatarDropdown } from '@/components/RightContent';
|
|
3
2
|
import { DEFAULT_NAME } from '@/constants';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
Avatar,
|
|
7
|
-
Divider,
|
|
8
|
-
Dropdown,
|
|
9
|
-
Layout,
|
|
10
|
-
Menu,
|
|
11
|
-
Space,
|
|
12
|
-
} from '@arco-design/web-react';
|
|
13
|
-
import {
|
|
14
|
-
IconMoonFill,
|
|
15
|
-
IconPoweroff,
|
|
16
|
-
IconSettings,
|
|
17
|
-
IconSunFill,
|
|
18
|
-
} from '@arco-design/web-react/icon';
|
|
19
|
-
import { useModel } from '@umijs/max';
|
|
20
|
-
import React, { useCallback } from 'react';
|
|
3
|
+
import { Layout, Space } from '@mico-platform/ui';
|
|
4
|
+
import React from 'react';
|
|
21
5
|
import './index.less';
|
|
22
6
|
|
|
23
7
|
const Header = Layout.Header;
|
|
24
8
|
|
|
25
9
|
const LayoutHeader: React.FC = () => {
|
|
26
|
-
const { initialState } = useModel('@@initialState');
|
|
27
|
-
const { theme, toggleTheme } = useTheme();
|
|
28
|
-
|
|
29
|
-
const handleMenuClick = useCallback((key: string) => {
|
|
30
|
-
if (key === 'logout') {
|
|
31
|
-
// 清除本地存储的认证信息
|
|
32
|
-
logout();
|
|
33
|
-
// 跳转到登录页
|
|
34
|
-
window.location.href = ROUTES.LOGIN;
|
|
35
|
-
return;
|
|
36
|
-
} else if (key === 'settings') {
|
|
37
|
-
// TODO: Navigate to settings page
|
|
38
|
-
console.log('settings');
|
|
39
|
-
}
|
|
40
|
-
}, []);
|
|
41
|
-
|
|
42
|
-
const droplist = (
|
|
43
|
-
<Menu onClickMenuItem={handleMenuClick}>
|
|
44
|
-
<Menu.Item key="settings">
|
|
45
|
-
<IconSettings style={{ marginRight: 8 }} />
|
|
46
|
-
设置
|
|
47
|
-
</Menu.Item>
|
|
48
|
-
<Divider style={{ margin: '4px 0' }} />
|
|
49
|
-
<Menu.Item key="logout">
|
|
50
|
-
<IconPoweroff style={{ marginRight: 8 }} />
|
|
51
|
-
退出登录
|
|
52
|
-
</Menu.Item>
|
|
53
|
-
</Menu>
|
|
54
|
-
);
|
|
55
|
-
|
|
56
10
|
return (
|
|
57
11
|
<Header className="layout-header">
|
|
58
12
|
{/* Logo */}
|
|
@@ -66,18 +20,18 @@ const LayoutHeader: React.FC = () => {
|
|
|
66
20
|
<div className="layout-header-right">
|
|
67
21
|
<Space size="medium">
|
|
68
22
|
{/* Theme toggle */}
|
|
69
|
-
<div
|
|
23
|
+
{/* <div
|
|
70
24
|
className="layout-header-action"
|
|
71
25
|
onClick={toggleTheme}
|
|
72
26
|
title={theme === 'dark' ? '切换到亮色模式' : '切换到暗色模式'}
|
|
73
27
|
>
|
|
74
28
|
{theme === 'dark' ? <IconSunFill /> : <IconMoonFill />}
|
|
75
|
-
</div>
|
|
29
|
+
</div> */}
|
|
76
30
|
|
|
77
31
|
<span className="split-line" />
|
|
78
32
|
|
|
79
33
|
{/* User info */}
|
|
80
|
-
<Dropdown droplist={droplist} position="br" trigger="click">
|
|
34
|
+
{/* <Dropdown droplist={droplist} position="br" trigger="click">
|
|
81
35
|
<div className="layout-header-user">
|
|
82
36
|
{initialState?.currentUser?.avatar && (
|
|
83
37
|
<Avatar size={32} style={{ marginRight: 8 }}>
|
|
@@ -91,7 +45,8 @@ const LayoutHeader: React.FC = () => {
|
|
|
91
45
|
initialState?.currentUser?.email}
|
|
92
46
|
</span>
|
|
93
47
|
</div>
|
|
94
|
-
</Dropdown>
|
|
48
|
+
</Dropdown> */}
|
|
49
|
+
<AvatarDropdown menu />
|
|
95
50
|
</Space>
|
|
96
51
|
</div>
|
|
97
52
|
</Header>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
@import '
|
|
1
|
+
@import '@mico-platform/theme/variables';
|
|
2
2
|
|
|
3
3
|
.arco-layout-sider-collapsed {
|
|
4
4
|
.layout-menu {
|
|
@@ -135,6 +135,8 @@
|
|
|
135
135
|
// Sider styles
|
|
136
136
|
.<%= projectName %>-sider {
|
|
137
137
|
box-shadow: none;
|
|
138
|
+
position: fixed !important;
|
|
139
|
+
height: calc(100vh - 56px) !important;
|
|
138
140
|
|
|
139
141
|
.arco-layout-sider-trigger {
|
|
140
142
|
display: flex;
|
|
@@ -4,8 +4,8 @@ import { isAuthDisabled, isNoPermissionRoute } from '@/constants';
|
|
|
4
4
|
import IconFont from '@/components/IconFont';
|
|
5
5
|
import { useMenuState } from '@/hooks/useMenuState';
|
|
6
6
|
import { useTheme } from '@/hooks/useTheme';
|
|
7
|
-
import { Layout, Menu } from '@
|
|
8
|
-
import * as Icons from '@
|
|
7
|
+
import { Layout, Menu } from '@mico-platform/ui';
|
|
8
|
+
import * as Icons from '@mico-platform/ui/icon';
|
|
9
9
|
import { useLocation, useModel } from '@umijs/max';
|
|
10
10
|
import React, { useEffect, useMemo, useRef } from 'react';
|
|
11
11
|
import './index.less';
|
|
@@ -15,8 +15,8 @@ const SubMenu = Menu.SubMenu;
|
|
|
15
15
|
const Sider = Layout.Sider;
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* 图标名称到
|
|
19
|
-
* 用于处理自定义图标名称与
|
|
18
|
+
* 图标名称到 @mico-platform/ui Icon 组件的映射
|
|
19
|
+
* 用于处理自定义图标名称与 UI 图标名称的差异
|
|
20
20
|
*/
|
|
21
21
|
const ICON_ALIAS_MAP: Record<string, keyof typeof Icons> = {
|
|
22
22
|
Package: 'IconApps',
|