From 55aeab79d6098a9473d3ae77bfbedfe87f08fb51 Mon Sep 17 00:00:00 2001 From: Alex Zhu Date: Thu, 30 Apr 2026 17:02:32 +0800 Subject: [PATCH] feat: extract header actions from avatar dropdown and add all locale options (#11733) Co-authored-by: Claude Opus 4.7 --- config/routes.ts | 4 +- src/app.tsx | 13 +- .../RightContent/AvatarDropdown.tsx | 90 +----------- src/components/RightContent/index.tsx | 132 ++++++++++++++++-- src/components/index.ts | 4 +- src/pages/404.tsx | 22 --- src/pages/exception/404/index.tsx | 38 +++-- 7 files changed, 161 insertions(+), 142 deletions(-) delete mode 100644 src/pages/404.tsx diff --git a/config/routes.ts b/config/routes.ts index 4545fe74..ab67058e 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -38,7 +38,7 @@ export default [ }, { name: '404', - component: './404', + component: './exception/404', path: '/user/*', }, ], @@ -292,7 +292,7 @@ export default [ }, { name: '404', - component: './404', + component: './exception/404', path: '/*', }, ]; diff --git a/src/app.tsx b/src/app.tsx index 50db4597..17bf9ecb 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -10,7 +10,13 @@ import React from 'react'; // Initialize dayjs plugins globally dayjs.extend(relativeTime); -import { AvatarDropdown, Footer } from '@/components'; +import { + AvatarDropdown, + DocLink, + Footer, + LangDropdown, + VersionDropdown, +} from '@/components'; import { currentUser as queryCurrentUser } from '@/services/ant-design-pro/api'; import defaultSettings from '../config/defaultSettings'; import { errorConfig } from './requestErrorConfig'; @@ -80,6 +86,11 @@ export const layout: RunTimeLayoutConfig = ({ } return dom; }, + actionsRender: () => [ + , + , + , + ], avatarProps: { src: initialState?.currentUser?.avatar, title: 'ProUser', diff --git a/src/components/RightContent/AvatarDropdown.tsx b/src/components/RightContent/AvatarDropdown.tsx index b66897a8..fbc587e3 100644 --- a/src/components/RightContent/AvatarDropdown.tsx +++ b/src/components/RightContent/AvatarDropdown.tsx @@ -1,20 +1,9 @@ import { - BookOutlined, - CheckOutlined, - GlobalOutlined, - HistoryOutlined, LogoutOutlined, SettingOutlined, SkinOutlined, } from '@ant-design/icons'; -import { - getAllLocales, - getLocale, - history, - setLocale, - useIntl, - useModel, -} from '@umijs/max'; +import { history, useModel } from '@umijs/max'; import type { MenuProps } from 'antd'; import { Spin } from 'antd'; import React from 'react'; @@ -26,14 +15,6 @@ export type GlobalHeaderRightProps = { children?: React.ReactNode; }; -const localeLabelMap: Record = { - 'zh-CN': { emoji: '🇨🇳', label: '简体中文' }, - 'zh-TW': { emoji: '🇭🇰', label: '繁体中文' }, - 'en-US': { emoji: '🇺🇸', label: 'English' }, -}; - -const supportLocaleKeys = Object.keys(localeLabelMap); - export const AvatarDropdown: React.FC = ({ children, }) => { @@ -53,7 +34,6 @@ export const AvatarDropdown: React.FC = ({ } }; const { initialState, setInitialState } = useModel('@@initialState'); - const intl = useIntl(); const onMenuClick: MenuProps['onClick'] = (event) => { const { key } = event; @@ -68,19 +48,6 @@ export const AvatarDropdown: React.FC = ({ setInitialState((s) => ({ ...s, settingDrawerOpen: true })); return; } - if (key === 'doc') { - history.push('/welcome'); - return; - } - if (key.startsWith('lang-')) { - setLocale(key.replace('lang-', ''), false); - return; - } - if (key.startsWith('version-')) { - const url = key.replace('version-', ''); - window.open(url, '_blank', 'noopener,noreferrer'); - return; - } history.push(`/account/${key}`); }; @@ -94,12 +61,6 @@ export const AvatarDropdown: React.FC = ({ return ; } - const allLocales = getAllLocales(); - const currentLocale = getLocale(); - const supportLocales = allLocales.filter((l) => - supportLocaleKeys.includes(l), - ); - const menuItems: MenuProps['items'] = [ { key: 'settings', @@ -111,55 +72,6 @@ export const AvatarDropdown: React.FC = ({ icon: , label: '主题设置', }, - { - key: 'doc', - icon: , - label: '使用文档', - }, - { - key: 'version', - icon: , - label: intl.formatMessage({ - id: 'component.globalHeader.historyVersion', - }), - children: [ - { - key: 'version-https://v5.pro.ant.design', - label: 'v5', - }, - { - key: 'version-https://v4.pro.ant.design', - label: 'v4', - }, - { - key: 'version-https://v2.pro.ant.design', - label: 'v2', - }, - { - key: 'version-https://v1.pro.ant.design', - label: 'v1', - }, - ], - }, - ...(supportLocales.length > 1 - ? [ - { - 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, }, diff --git a/src/components/RightContent/index.tsx b/src/components/RightContent/index.tsx index 5430bbfa..ea0b19f2 100644 --- a/src/components/RightContent/index.tsx +++ b/src/components/RightContent/index.tsx @@ -1,20 +1,130 @@ -import { QuestionCircleOutlined } from '@ant-design/icons'; -import { history, SelectLang as UmiSelectLang } from '@umijs/max'; +import { + BookOutlined, + CheckOutlined, + ForkOutlined, + GlobalOutlined, +} from '@ant-design/icons'; +import { getAllLocales, getLocale, history, setLocale } from '@umijs/max'; +import type { MenuProps } from 'antd'; +import { Button, Tooltip } from 'antd'; +import { createStyles } from 'antd-style'; +import React, { useMemo } from 'react'; +import HeaderDropdown from '../HeaderDropdown'; -export type SiderTheme = 'light' | 'dark'; +export const localeLabelMap: Record = + { + 'zh-CN': { emoji: '🇨🇳', label: '简体中文' }, + 'zh-TW': { emoji: '🇭🇰', label: '繁體中文' }, + 'en-US': { emoji: '🇺🇸', label: 'English' }, + 'ja-JP': { emoji: '🇯🇵', label: '日本語' }, + 'pt-BR': { emoji: '🇧🇷', label: 'Português' }, + 'id-ID': { emoji: '🇮🇩', label: 'Bahasa Indonesia' }, + 'fa-IR': { emoji: '🇮🇷', label: 'فارسی' }, + 'bn-BD': { emoji: '🇧🇩', label: 'বাংলা' }, + }; -export const SelectLang: React.FC = () => { - return ; +const useStyles = createStyles(({ token, css }) => ({ + action: css` + display: inline-flex !important; + align-items: center !important; + justify-content: center !important; + height: 36px !important; + min-width: 36px; + padding-inline: 8px !important; + padding-block: 0 !important; + border-radius: ${token.borderRadius}px !important; + `, +})); + +export const DocLink: React.FC = () => { + const { styles } = useStyles(); + return ( + + + + ); }; -export const Question: React.FC = () => { +export const LangDropdown: React.FC = () => { + const { styles } = useStyles(); + const allLocales = useMemo(() => getAllLocales(), []); + const currentLocale = getLocale(); + const supportLocales = allLocales.filter((l) => l in localeLabelMap); + + if (supportLocales.length <= 1) { + return null; + } + + const langItems: MenuProps['items'] = supportLocales.map((locale) => ({ + key: `lang-${locale}`, + icon: + locale === currentLocale ? ( + + ) : ( + + ), + label: `${localeLabelMap[locale]?.emoji ?? ''} ${localeLabelMap[locale]?.label ?? locale}`, + })); + + const onLangClick: MenuProps['onClick'] = ({ key }) => { + if (key.startsWith('lang-')) { + setLocale(key.replace('lang-', ''), false); + } + }; + return ( - { - history.push('/welcome'); + - - + + ); }; diff --git a/src/components/index.ts b/src/components/index.ts index d6ae96e4..94092f55 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -6,7 +6,7 @@ * 布局组件 */ import Footer from './Footer'; -import { Question, SelectLang } from './RightContent'; +import { DocLink, LangDropdown, VersionDropdown } from './RightContent'; 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, Footer, Question, SelectLang }; +export { AvatarDropdown, DocLink, Footer, LangDropdown, VersionDropdown }; diff --git a/src/pages/404.tsx b/src/pages/404.tsx deleted file mode 100644 index 6d4dd981..00000000 --- a/src/pages/404.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Link, useIntl } from '@umijs/max'; -import { Button, Card, Result } from 'antd'; -import React from 'react'; - -const NoFoundPage: React.FC = () => ( - - - - - } - /> - -); - -export default NoFoundPage; diff --git a/src/pages/exception/404/index.tsx b/src/pages/exception/404/index.tsx index a1bb7685..ee1e7529 100644 --- a/src/pages/exception/404/index.tsx +++ b/src/pages/exception/404/index.tsx @@ -1,17 +1,25 @@ -import { Link } from '@umijs/max'; +import { Link, useIntl } from '@umijs/max'; import { Button, Card, Result } from 'antd'; +import React from 'react'; -export default () => ( - - - - - } - /> - -); +const Exception404: React.FC = () => { + const intl = useIntl(); + return ( + + + + + } + /> + + ); +}; + +export default Exception404;