diff --git a/src/app.tsx b/src/app.tsx index 72d5ce19..944fa0b3 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -10,19 +10,12 @@ import React from 'react'; // Initialize dayjs plugins globally dayjs.extend(relativeTime); -import { - AvatarDropdown, - AvatarName, - Footer, - Question, - SelectLang, -} from '@/components'; +import { AvatarDropdown, Footer } from '@/components'; import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api'; import defaultSettings from '../config/defaultSettings'; import { errorConfig } from './requestErrorConfig'; const isDev = process.env.NODE_ENV === 'development'; -const isDevOrTest = isDev || process.env.CI; const loginPath = '/user/login'; /** @@ -33,6 +26,7 @@ export async function getInitialState(): Promise<{ currentUser?: API.CurrentUser; loading?: boolean; fetchUserInfo?: () => Promise; + settingDrawerOpen?: boolean; }> { const fetchUserInfo = async () => { try { @@ -57,11 +51,13 @@ export async function getInitialState(): Promise<{ fetchUserInfo, currentUser, settings: defaultSettings as Partial, + settingDrawerOpen: false, }; } return { fetchUserInfo, settings: defaultSettings as Partial, + settingDrawerOpen: false, }; } @@ -71,10 +67,6 @@ export const layout: RunTimeLayoutConfig = ({ setInitialState, }) => { return { - actionsRender: () => [ - , - , - ], menuItemRender: (item, dom) => { if (item.path) { return ( @@ -87,7 +79,7 @@ export const layout: RunTimeLayoutConfig = ({ }, avatarProps: { src: initialState?.currentUser?.avatar, - title: , + title: 'ProUser', render: (_, avatarChildren) => ( {avatarChildren} ), @@ -140,19 +132,24 @@ export const layout: RunTimeLayoutConfig = ({ return ( <> {children} - {isDevOrTest && ( - { - setInitialState((preInitialState) => ({ - ...preInitialState, - settings, - })); - }} - /> - )} + { + setInitialState((s) => ({ + ...s, + settingDrawerOpen: open, + })); + }} + settings={initialState?.settings} + onSettingChange={(settings) => { + setInitialState((s) => ({ + ...s, + settings, + })); + }} + /> ); }, diff --git a/src/components/HeaderDropdown/index.tsx b/src/components/HeaderDropdown/index.tsx index 3a23617e..8a77cae4 100644 --- a/src/components/HeaderDropdown/index.tsx +++ b/src/components/HeaderDropdown/index.tsx @@ -10,6 +10,14 @@ const useStyles = createStyles(({ token }) => { [`@media screen and (max-width: ${token.screenXS}px)`]: { width: '100%', }, + '.ant-dropdown-menu-item .anticon, .ant-dropdown-menu-submenu-title .anticon': + { + display: 'inline-flex', + alignItems: 'center', + }, + '.ant-dropdown-menu-submenu-title .anticon': { + color: token.colorTextSecondary, + }, }, }; }); diff --git a/src/components/RightContent/AvatarDropdown.tsx b/src/components/RightContent/AvatarDropdown.tsx index 96945c74..4d4fd92e 100644 --- a/src/components/RightContent/AvatarDropdown.tsx +++ b/src/components/RightContent/AvatarDropdown.tsx @@ -1,53 +1,40 @@ import { + BookOutlined, + CheckOutlined, + GlobalOutlined, LogoutOutlined, SettingOutlined, - UserOutlined, + SkinOutlined, } from '@ant-design/icons'; -import { history, useModel } from '@umijs/max'; +import { + getAllLocales, + getLocale, + history, + setLocale, + useModel, +} from '@umijs/max'; import type { MenuProps } from 'antd'; import { Spin } from 'antd'; -import { createStyles } from 'antd-style'; import React from 'react'; import { flushSync } from 'react-dom'; import { outLogin } from '@/services/ant-design-pro/api'; import HeaderDropdown from '../HeaderDropdown'; export type GlobalHeaderRightProps = { - menu?: boolean; children?: React.ReactNode; }; -export const AvatarName = () => { - const { initialState } = useModel('@@initialState'); - const { currentUser } = initialState || {}; - return {currentUser?.name}; +const localeLabelMap: Record = { + 'zh-CN': { emoji: '🇨🇳', label: '简体中文' }, + 'zh-TW': { emoji: '🇭🇰', label: '繁体中文' }, + 'en-US': { emoji: '🇺🇸', label: 'English' }, }; -const useStyles = createStyles(({ token }) => { - return { - action: { - display: 'flex', - height: '48px', - marginLeft: 'auto', - overflow: 'hidden', - alignItems: 'center', - padding: '0 8px', - cursor: 'pointer', - borderRadius: token.borderRadius, - '&:hover': { - backgroundColor: token.colorBgTextHover, - }, - }, - }; -}); +const supportLocaleKeys = Object.keys(localeLabelMap); export const AvatarDropdown: React.FC = ({ - menu, children, }) => { - /** - * 退出登录,并且将当前的 url 保存 - */ const loginOut = async () => { await outLogin(); const { search, pathname } = window.location; @@ -55,9 +42,7 @@ export const AvatarDropdown: React.FC = ({ const searchParams = new URLSearchParams({ redirect: pathname + search, }); - /** 此方法会跳转到 redirect 参数所在的位置 */ const redirect = urlParams.get('redirect'); - // Note: There may be security issues, please note if (window.location.pathname !== '/user/login' && !redirect) { history.replace({ pathname: '/user/login', @@ -65,8 +50,6 @@ export const AvatarDropdown: React.FC = ({ }); } }; - const { styles } = useStyles(); - const { initialState, setInitialState } = useModel('@@initialState'); const onMenuClick: MenuProps['onClick'] = (event) => { @@ -78,49 +61,75 @@ export const AvatarDropdown: React.FC = ({ loginOut(); return; } + if (key === 'theme') { + setInitialState((s) => ({ ...s, settingDrawerOpen: true })); + return; + } + if (key === 'doc') { + history.push('/welcome'); + return; + } + if (key.startsWith('lang-')) { + setLocale(key.replace('lang-', ''), false); + return; + } history.push(`/account/${key}`); }; - const loading = ( - - - - ); - if (!initialState) { - return loading; + return ; } const { currentUser } = initialState; - if (!currentUser?.name) { - return loading; + if (!currentUser) { + return ; } - const menuItems = [ - ...(menu + const allLocales = getAllLocales(); + const currentLocale = getLocale(); + const supportLocales = allLocales.filter((l) => + supportLocaleKeys.includes(l), + ); + + const menuItems: MenuProps['items'] = [ + { + key: 'settings', + icon: , + label: '个人设置', + }, + { + key: 'theme', + icon: , + label: '主题设置', + }, + { + key: 'doc', + icon: , + label: '使用文档', + }, + ...(supportLocales.length > 1 ? [ { - key: 'center', - icon: , - label: '个人中心', - }, - { - key: 'settings', - icon: , - label: '个人设置', - }, - { - type: 'divider' as const, + key: 'lang', + icon: , + label: localeLabelMap[currentLocale]?.label ?? currentLocale, + children: supportLocales.map((locale) => ({ + key: `lang-${locale}`, + icon: + locale === currentLocale ? ( + + ) : ( + + ), + label: `${localeLabelMap[locale]?.emoji ?? ''} ${localeLabelMap[locale]?.label ?? locale}`, + })), }, ] : []), + { + type: 'divider' as const, + }, { key: 'logout', icon: , @@ -130,11 +139,13 @@ export const AvatarDropdown: React.FC = ({ return ( {children} diff --git a/src/components/RightContent/index.tsx b/src/components/RightContent/index.tsx index 5d88fc4f..5430bbfa 100644 --- a/src/components/RightContent/index.tsx +++ b/src/components/RightContent/index.tsx @@ -1,32 +1,20 @@ import { QuestionCircleOutlined } from '@ant-design/icons'; -import { SelectLang as UmiSelectLang } from '@umijs/max'; +import { history, SelectLang as UmiSelectLang } from '@umijs/max'; export type SiderTheme = 'light' | 'dark'; export const SelectLang: React.FC = () => { - return ( - - ); + return ; }; export const Question: React.FC = () => { return ( - { + history.push('/welcome'); }} > - + ); }; diff --git a/src/components/index.ts b/src/components/index.ts index 30dc1d0f..d6ae96e4 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -7,7 +7,7 @@ */ import Footer from './Footer'; import { Question, SelectLang } from './RightContent'; -import { AvatarDropdown, AvatarName } from './RightContent/AvatarDropdown'; +import { AvatarDropdown } from './RightContent/AvatarDropdown'; /** * 业务组件 @@ -17,4 +17,4 @@ export { default as AvatarList } from './AvatarList'; export { default as StandardFormRow } from './StandardFormRow'; export { default as TagSelect } from './TagSelect'; -export { AvatarDropdown, AvatarName, Footer, Question, SelectLang }; +export { AvatarDropdown, Footer, Question, SelectLang };