Browse Source

♻️refactor: HeaderBar

Apple\Apple 9 months ago
parent
commit
1ea0dd8f06
2 changed files with 318 additions and 378 deletions
  1. 316 376
      web/src/components/HeaderBar.js
  2. 2 2
      web/src/i18n/locales/en.json

+ 316 - 376
web/src/components/HeaderBar.js

@@ -3,166 +3,82 @@ import { Link, useNavigate } from 'react-router-dom';
 import { UserContext } from '../context/User';
 import { useSetTheme, useTheme } from '../context/Theme';
 import { useTranslation } from 'react-i18next';
-
-import { API, getLogo, getSystemName, isMobile, showSuccess } from '../helpers';
-import '../index.css';
-
+import { API, getLogo, getSystemName, showSuccess } from '../helpers';
 import fireworks from 'react-fireworks';
+import { CN, GB } from 'country-flag-icons/react/3x2';
 
 import {
   IconClose,
-  IconHelpCircle,
-  IconHome,
-  IconHomeStroked,
-  IconIndentLeft,
-  IconComment,
-  IconKey,
   IconMenu,
-  IconNoteMoneyStroked,
-  IconPriceTag,
-  IconUser,
   IconLanguage,
-  IconInfoCircle,
-  IconCreditCard,
-  IconTerminal,
+  IconChevronDown,
+  IconSun,
+  IconMoon,
 } from '@douyinfe/semi-icons';
 import {
   Avatar,
   Button,
   Dropdown,
-  Layout,
-  Nav,
-  Switch,
   Tag,
+  Typography,
+  Skeleton,
 } from '@douyinfe/semi-ui';
 import { stringToColor } from '../helpers/render';
-import Text from '@douyinfe/semi-ui/lib/es/typography/text';
-import { StyleContext } from '../context/Style/index.js';
 import { StatusContext } from '../context/Status/index.js';
-
-// 自定义顶部栏样式
-const headerStyle = {
-  boxShadow: '0 2px 10px rgba(0, 0, 0, 0.1)',
-  borderBottom: '1px solid var(--semi-color-border)',
-  background: 'var(--semi-color-bg-0)',
-  transition: 'all 0.3s ease',
-  width: '100%',
-};
-
-// 自定义顶部栏按钮样式
-const headerItemStyle = {
-  borderRadius: '4px',
-  margin: '0 4px',
-  transition: 'all 0.3s ease',
-};
-
-// 自定义顶部栏按钮悬停样式
-const headerItemHoverStyle = {
-  backgroundColor: 'var(--semi-color-primary-light-default)',
-  color: 'var(--semi-color-primary)',
-};
-
-// 自定义顶部栏Logo样式
-const logoStyle = {
-  display: 'flex',
-  alignItems: 'center',
-  gap: '10px',
-  padding: '0 10px',
-  height: '100%',
-};
-
-// 自定义顶部栏系统名称样式
-const systemNameStyle = {
-  fontWeight: 'bold',
-  fontSize: '18px',
-  background:
-    'linear-gradient(45deg, var(--semi-color-primary), var(--semi-color-secondary))',
-  WebkitBackgroundClip: 'text',
-  WebkitTextFillColor: 'transparent',
-  padding: '0 5px',
-};
-
-// 自定义顶部栏按钮图标样式
-const headerIconStyle = {
-  fontSize: '18px',
-  transition: 'all 0.3s ease',
-};
-
-// 自定义头像样式
-const avatarStyle = {
-  margin: '4px',
-  cursor: 'pointer',
-  boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
-  transition: 'all 0.3s ease',
-};
-
-// 自定义下拉菜单样式
-const dropdownStyle = {
-  borderRadius: '8px',
-  boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
-  overflow: 'hidden',
-};
-
-// 自定义主题切换开关样式
-const switchStyle = {
-  margin: '0 8px',
-};
+import { StyleContext } from '../context/Style/index.js';
 
 const HeaderBar = () => {
   const { t, i18n } = useTranslation();
   const [userState, userDispatch] = useContext(UserContext);
-  const [styleState, styleDispatch] = useContext(StyleContext);
   const [statusState, statusDispatch] = useContext(StatusContext);
+  const [styleState, styleDispatch] = useContext(StyleContext);
+  const [isLoading, setIsLoading] = useState(true);
   let navigate = useNavigate();
   const [currentLang, setCurrentLang] = useState(i18n.language);
+  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
 
   const systemName = getSystemName();
   const logo = getLogo();
   const currentDate = new Date();
-  // enable fireworks on new year(1.1 and 2.9-2.24)
   const isNewYear = currentDate.getMonth() === 0 && currentDate.getDate() === 1;
 
-  // Check if self-use mode is enabled
   const isSelfUseMode = statusState?.status?.self_use_mode_enabled || false;
   const docsLink = statusState?.status?.docs_link || '';
   const isDemoSiteMode = statusState?.status?.demo_site_enabled || false;
 
-  let buttons = [
+  const theme = useTheme();
+  const setTheme = useSetTheme();
+
+  const mainNavLinks = [
     {
       text: t('首页'),
       itemKey: 'home',
       to: '/',
-      icon: <IconHome style={headerIconStyle} />,
     },
     {
       text: t('控制台'),
       itemKey: 'detail',
-      to: '/',
-      icon: <IconTerminal style={headerIconStyle} />,
+      to: '/detail',
     },
     {
       text: t('定价'),
       itemKey: 'pricing',
       to: '/pricing',
-      icon: <IconPriceTag style={headerIconStyle} />,
     },
-    // Only include the docs button if docsLink exists
     ...(docsLink
       ? [
-          {
-            text: t('文档'),
-            itemKey: 'docs',
-            isExternal: true,
-            externalLink: docsLink,
-            icon: <IconHelpCircle style={headerIconStyle} />,
-          },
-        ]
+        {
+          text: t('文档'),
+          itemKey: 'docs',
+          isExternal: true,
+          externalLink: docsLink,
+        },
+      ]
       : []),
     {
       text: t('关于'),
       itemKey: 'about',
       to: '/about',
-      icon: <IconInfoCircle style={headerIconStyle} />,
     },
   ];
 
@@ -172,6 +88,7 @@ const HeaderBar = () => {
     userDispatch({ type: 'logout' });
     localStorage.removeItem('user');
     navigate('/login');
+    setMobileMenuOpen(false);
   }
 
   const handleNewYearClick = () => {
@@ -179,31 +96,24 @@ const HeaderBar = () => {
     fireworks.start();
     setTimeout(() => {
       fireworks.stop();
-      setTimeout(() => {
-        window.location.reload();
-      }, 10000);
     }, 3000);
   };
 
-  const theme = useTheme();
-  const setTheme = useSetTheme();
-
   useEffect(() => {
     if (theme === 'dark') {
       document.body.setAttribute('theme-mode', 'dark');
+      document.documentElement.classList.add('dark');
     } else {
       document.body.removeAttribute('theme-mode');
+      document.documentElement.classList.remove('dark');
     }
-    // 发送当前主题模式给子页面
+
     const iframe = document.querySelector('iframe');
     if (iframe) {
       iframe.contentWindow.postMessage({ themeMode: theme }, '*');
     }
 
-    if (isNewYear) {
-      console.log('Happy New Year!');
-    }
-  }, [theme]);
+  }, [theme, isNewYear]);
 
   useEffect(() => {
     const handleLanguageChanged = (lng) => {
@@ -215,279 +125,309 @@ const HeaderBar = () => {
     };
 
     i18n.on('languageChanged', handleLanguageChanged);
-
     return () => {
       i18n.off('languageChanged', handleLanguageChanged);
     };
   }, [i18n]);
 
+  useEffect(() => {
+    // 模拟加载用户状态的过程
+    const timer = setTimeout(() => {
+      setIsLoading(false);
+    }, 500);
+    return () => clearTimeout(timer);
+  }, []);
+
   const handleLanguageChange = (lang) => {
     i18n.changeLanguage(lang);
+    setMobileMenuOpen(false);
   };
 
-  return (
-    <>
-      <Layout>
-        <div style={{ width: '100%' }}>
-          <Nav
-            className={'topnav'}
-            mode={'horizontal'}
-            style={headerStyle}
-            itemStyle={headerItemStyle}
-            hoverStyle={headerItemHoverStyle}
-            renderWrapper={({ itemElement, isSubNav, isInSubNav, props }) => {
-              const routerMap = {
-                about: '/about',
-                login: '/login',
-                register: '/register',
-                pricing: '/pricing',
-                detail: '/detail',
-                home: '/',
-                chat: '/chat',
-              };
-              return (
-                <div
-                  onClick={(e) => {
-                    if (props.itemKey === 'home') {
-                      styleDispatch({
-                        type: 'SET_INNER_PADDING',
-                        payload: false,
-                      });
-                      styleDispatch({ type: 'SET_SIDER', payload: false });
-                    } else {
-                      styleDispatch({
-                        type: 'SET_INNER_PADDING',
-                        payload: true,
-                      });
-                      if (!styleState.isMobile) {
-                        styleDispatch({ type: 'SET_SIDER', payload: true });
-                      }
-                    }
-                  }}
+  const handleNavLinkClick = (itemKey) => {
+    if (itemKey === 'home') {
+      styleDispatch({ type: 'SET_INNER_PADDING', payload: false });
+      styleDispatch({ type: 'SET_SIDER', payload: false });
+    } else {
+      styleDispatch({ type: 'SET_INNER_PADDING', payload: true });
+      if (!styleState.isMobile) {
+        styleDispatch({ type: 'SET_SIDER', payload: true });
+      }
+    }
+    setMobileMenuOpen(false);
+  };
+
+  const renderNavLinks = (isMobileView = false) =>
+    mainNavLinks.map((link) => {
+      const commonLinkClasses = isMobileView
+        ? 'flex items-center gap-1 p-3 w-full text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-md transition-colors font-semibold'
+        : 'flex items-center gap-1 p-2 text-sm text-gray-700 dark:text-gray-300 hover:text-blue-600 dark:hover:text-blue-400 transition-colors rounded-md font-semibold';
+
+      const linkContent = (
+        <span>{link.text}</span>
+      );
+
+      if (link.isExternal) {
+        return (
+          <a
+            key={link.itemKey}
+            href={link.externalLink}
+            target='_blank'
+            rel='noopener noreferrer'
+            className={commonLinkClasses}
+            onClick={() => handleNavLinkClick(link.itemKey)}
+          >
+            {linkContent}
+          </a>
+        );
+      }
+      return (
+        <Link
+          key={link.itemKey}
+          to={link.to}
+          className={commonLinkClasses}
+          onClick={() => handleNavLinkClick(link.itemKey)}
+        >
+          {linkContent}
+        </Link>
+      );
+    });
+
+  const renderUserArea = () => {
+    if (isLoading) {
+      return (
+        <div className="flex items-center p-1 rounded-full bg-semi-color-fill-0 dark:bg-semi-color-fill-1">
+          <Skeleton.Avatar size="extra-small" className="shadow-sm" />
+          <div className="ml-1.5 mr-1">
+            <Skeleton.Title style={{ width: styleState.isMobile ? 15 : 50, height: 12 }} />
+          </div>
+        </div>
+      );
+    }
+
+    if (userState.user) {
+      return (
+        <Dropdown
+          position="bottomRight"
+          render={
+            <Dropdown.Menu className="!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600">
+              <Dropdown.Item onClick={logout} className="!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-red-500 dark:hover:!text-white">
+                {t('退出')}
+              </Dropdown.Item>
+            </Dropdown.Menu>
+          }
+        >
+          <Button
+            theme="borderless"
+            type="tertiary"
+            className="flex items-center gap-1.5 !p-1 !rounded-full hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700 !bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 dark:hover:!bg-semi-color-fill-2"
+          >
+            <Avatar
+              size="extra-small"
+              color={stringToColor(userState.user.username)}
+              className="shadow-sm mr-1"
+            >
+              {userState.user.username[0].toUpperCase()}
+            </Avatar>
+            <span className="hidden md:inline">
+              <Typography.Text className="!text-xs !font-medium !text-semi-color-text-1 dark:!text-gray-300 mr-1">
+                {userState.user.username}
+              </Typography.Text>
+            </span>
+            <IconChevronDown className="text-xs text-semi-color-text-2 dark:text-gray-400" />
+          </Button>
+        </Dropdown>
+      );
+    } else {
+      const showRegisterButton = !isSelfUseMode;
+
+      const commonSizingAndLayoutClass = "flex items-center justify-center !py-[10px] !px-1.5";
+
+      const loginButtonSpecificStyling = "!bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700 transition-colors";
+      let loginButtonClasses = `${commonSizingAndLayoutClass} ${loginButtonSpecificStyling}`;
+
+      let registerButtonClasses = `${commonSizingAndLayoutClass}`;
+
+      const loginButtonTextSpanClass = "!text-xs !text-semi-color-text-1 dark:!text-gray-300 !p-1.5";
+      const registerButtonTextSpanClass = "!text-xs !text-white !p-1.5";
+
+      if (showRegisterButton) {
+        if (styleState.isMobile) {
+          loginButtonClasses += " !rounded-full";
+        } else {
+          loginButtonClasses += " !rounded-l-full !rounded-r-none";
+        }
+        registerButtonClasses += " !rounded-r-full !rounded-l-none";
+      } else {
+        loginButtonClasses += " !rounded-full";
+      }
+
+      return (
+        <div className="flex items-center">
+          <Link to="/login" onClick={() => handleNavLinkClick('login')} className="flex">
+            <Button
+              theme="borderless"
+              type="tertiary"
+              className={loginButtonClasses}
+            >
+              <span className={loginButtonTextSpanClass}>
+                {t('登录')}
+              </span>
+            </Button>
+          </Link>
+          {showRegisterButton && (
+            <div className="hidden md:block">
+              <Link to="/register" onClick={() => handleNavLinkClick('register')} className="flex -ml-px">
+                <Button
+                  theme="solid"
+                  type="primary"
+                  className={registerButtonClasses}
                 >
-                  {props.isExternal ? (
-                    <a
-                      className='header-bar-text'
-                      style={{ textDecoration: 'none' }}
-                      href={props.externalLink}
-                      target='_blank'
-                      rel='noopener noreferrer'
-                    >
-                      {itemElement}
-                    </a>
-                  ) : (
-                    <Link
-                      className='header-bar-text'
-                      style={{ textDecoration: 'none' }}
-                      to={routerMap[props.itemKey]}
+                  <span className={registerButtonTextSpanClass}>
+                    {t('注册')}
+                  </span>
+                </Button>
+              </Link>
+            </div>
+          )}
+        </div>
+      );
+    }
+  };
+
+  return (
+    <header className="bg-semi-color-bg-0 text-semi-color-text-0 shadow-sm sticky top-0 z-50 transition-colors duration-300">
+      <div className="w-full px-4">
+        <div className="flex items-center justify-between h-16">
+          <div className="flex items-center">
+            <div className="md:hidden">
+              <Button
+                icon={mobileMenuOpen ? <IconClose className="text-lg" /> : <IconMenu className="text-lg" />}
+                aria-label={mobileMenuOpen ? t('关闭菜单') : t('打开菜单')}
+                onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
+                theme="borderless"
+                type="tertiary"
+                className="!p-2 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700"
+              />
+            </div>
+            <Link to="/" onClick={() => handleNavLinkClick('home')} className="flex items-center gap-2 group ml-2">
+              <img src={logo} alt="logo" className="h-7 md:h-8 transition-transform duration-300 ease-in-out group-hover:scale-105" />
+              <div className="hidden md:flex items-center gap-2">
+                <div className="flex items-center gap-2">
+                  <Typography.Title heading={4} className="!text-lg !font-semibold !mb-0 
+                                                          bg-gradient-to-r from-blue-500 to-purple-500 dark:from-blue-400 dark:to-purple-400
+                                                          bg-clip-text text-transparent">
+                    {systemName}
+                  </Typography.Title>
+                  {(isSelfUseMode || isDemoSiteMode) && (
+                    <Tag
+                      color={isSelfUseMode ? 'purple' : 'blue'}
+                      className="text-xs px-1.5 py-0.5 rounded whitespace-nowrap shadow-sm"
+                      size="small"
                     >
-                      {itemElement}
-                    </Link>
+                      {isSelfUseMode ? t('自用模式') : t('演示站点')}
+                    </Tag>
                   )}
                 </div>
-              );
-            }}
-            selectedKeys={[]}
-            // items={headerButtons}
-            onSelect={(key) => {}}
-            header={
-              styleState.isMobile
-                ? {
-                    logo: (
-                      <div
-                        style={{
-                          display: 'flex',
-                          alignItems: 'center',
-                          position: 'relative',
-                        }}
-                      >
-                        {!styleState.showSider ? (
-                          <Button
-                            icon={<IconMenu />}
-                            theme='light'
-                            aria-label={t('展开侧边栏')}
-                            onClick={() =>
-                              styleDispatch({
-                                type: 'SET_SIDER',
-                                payload: true,
-                              })
-                            }
-                          />
-                        ) : (
-                          <Button
-                            icon={<IconIndentLeft />}
-                            theme='light'
-                            aria-label={t('闭侧边栏')}
-                            onClick={() =>
-                              styleDispatch({
-                                type: 'SET_SIDER',
-                                payload: false,
-                              })
-                            }
-                          />
-                        )}
-                        {(isSelfUseMode || isDemoSiteMode) && (
-                          <Tag
-                            color={isSelfUseMode ? 'purple' : 'blue'}
-                            style={{
-                              position: 'absolute',
-                              top: '-8px',
-                              right: '-15px',
-                              fontSize: '0.7rem',
-                              padding: '0 4px',
-                              height: 'auto',
-                              lineHeight: '1.2',
-                              zIndex: 1,
-                              pointerEvents: 'none',
-                            }}
-                          >
-                            {isSelfUseMode ? t('自用模式') : t('演示站点')}
-                          </Tag>
-                        )}
-                      </div>
-                    ),
-                  }
-                : {
-                    logo: (
-                      <div style={logoStyle}>
-                        <img src={logo} alt='logo' style={{ height: '28px' }} />
-                      </div>
-                    ),
-                    text: (
-                      <div
-                        style={{
-                          position: 'relative',
-                          display: 'inline-block',
-                        }}
-                      >
-                        <span style={systemNameStyle}>{systemName}</span>
-                        {(isSelfUseMode || isDemoSiteMode) && (
-                          <Tag
-                            color={isSelfUseMode ? 'purple' : 'blue'}
-                            style={{
-                              position: 'absolute',
-                              top: '-10px',
-                              right: '-25px',
-                              fontSize: '0.7rem',
-                              padding: '0 4px',
-                              whiteSpace: 'nowrap',
-                              zIndex: 1,
-                              boxShadow: '0 0 3px rgba(255, 255, 255, 0.7)',
-                            }}
-                          >
-                            {isSelfUseMode ? t('自用模式') : t('演示站点')}
-                          </Tag>
-                        )}
-                      </div>
-                    ),
-                  }
-            }
-            items={buttons}
-            footer={
-              <>
-                {isNewYear && (
-                  // happy new year
-                  <Dropdown
-                    position='bottomRight'
-                    render={
-                      <Dropdown.Menu style={dropdownStyle}>
-                        <Dropdown.Item onClick={handleNewYearClick}>
-                          Happy New Year!!!
-                        </Dropdown.Item>
-                      </Dropdown.Menu>
-                    }
-                  >
-                    <Nav.Item itemKey={'new-year'} text={'🎉'} />
-                  </Dropdown>
-                )}
-                {/* <Nav.Item itemKey={'about'} icon={<IconHelpCircle />} /> */}
-                <>
-                  <Switch
-                    checkedText='🌞'
-                    size={styleState.isMobile ? 'default' : 'large'}
-                    checked={theme === 'dark'}
-                    uncheckedText='🌙'
-                    style={switchStyle}
-                    onChange={(checked) => {
-                      setTheme(checked);
-                    }}
-                  />
-                </>
-                <Dropdown
-                  position='bottomRight'
-                  render={
-                    <Dropdown.Menu style={dropdownStyle}>
-                      <Dropdown.Item
-                        onClick={() => handleLanguageChange('zh')}
-                        type={currentLang === 'zh' ? 'primary' : 'tertiary'}
-                      >
-                        中文
-                      </Dropdown.Item>
-                      <Dropdown.Item
-                        onClick={() => handleLanguageChange('en')}
-                        type={currentLang === 'en' ? 'primary' : 'tertiary'}
-                      >
-                        English
-                      </Dropdown.Item>
-                    </Dropdown.Menu>
-                  }
+              </div>
+            </Link>
+            {(isSelfUseMode || isDemoSiteMode) && (
+              <div className="md:hidden">
+                <Tag
+                  color={isSelfUseMode ? 'purple' : 'blue'}
+                  className="ml-2 text-xs px-1 py-0.5 rounded whitespace-nowrap shadow-sm"
+                  size="small"
                 >
-                  <Nav.Item
-                    itemKey={'language'}
-                    icon={<IconLanguage style={headerIconStyle} />}
-                  />
-                </Dropdown>
-                {userState.user ? (
-                  <>
-                    <Dropdown
-                      position='bottomRight'
-                      render={
-                        <Dropdown.Menu style={dropdownStyle}>
-                          <Dropdown.Item onClick={logout}>
-                            {t('退出')}
-                          </Dropdown.Item>
-                        </Dropdown.Menu>
-                      }
-                    >
-                      <Avatar
-                        size='small'
-                        color={stringToColor(userState.user.username)}
-                        style={avatarStyle}
-                      >
-                        {userState.user.username[0]}
-                      </Avatar>
-                      {styleState.isMobile ? null : (
-                        <Text style={{ marginLeft: '4px', fontWeight: '500' }}>
-                          {userState.user.username}
-                        </Text>
-                      )}
-                    </Dropdown>
-                  </>
-                ) : (
-                  <>
-                    <Nav.Item
-                      itemKey={'login'}
-                      text={!styleState.isMobile ? t('登录') : null}
-                      icon={<IconUser style={headerIconStyle} />}
-                    />
-                    {
-                      // Hide register option in self-use mode
-                      !styleState.isMobile && !isSelfUseMode && (
-                        <Nav.Item
-                          itemKey={'register'}
-                          text={t('注册')}
-                          icon={<IconKey style={headerIconStyle} />}
-                        />
-                      )
-                    }
-                  </>
-                )}
-              </>
-            }
-          ></Nav>
+                  {isSelfUseMode ? t('自用模式') : t('演示站点')}
+                </Tag>
+              </div>
+            )}
+
+            <nav className="hidden md:flex items-center gap-1 lg:gap-2 ml-6">
+              {renderNavLinks()}
+            </nav>
+          </div>
+
+          <div className="flex items-center gap-2 md:gap-3">
+            {isNewYear && (
+              <Dropdown
+                position="bottomRight"
+                render={
+                  <Dropdown.Menu className="!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600">
+                    <Dropdown.Item onClick={handleNewYearClick} className="!text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-gray-600">
+                      Happy New Year!!! 🎉
+                    </Dropdown.Item>
+                  </Dropdown.Menu>
+                }
+              >
+                <Button
+                  theme="borderless"
+                  type="tertiary"
+                  icon={<span className="text-xl">🎉</span>}
+                  aria-label="New Year"
+                  className="!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 rounded-full"
+                />
+              </Dropdown>
+            )}
+
+            <Button
+              icon={theme === 'dark' ? <IconSun size="large" className="text-yellow-500" /> : <IconMoon size="large" className="text-gray-300" />}
+              aria-label={t('切换主题')}
+              onClick={() => setTheme(theme === 'dark' ? false : true)}
+              theme="borderless"
+              type="tertiary"
+              className="!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 !rounded-full bg-semi-color-fill-0 dark:bg-semi-color-fill-1 hover:bg-semi-color-fill-1 dark:hover:bg-semi-color-fill-2"
+            />
+
+            <Dropdown
+              position="bottomRight"
+              render={
+                <Dropdown.Menu className="!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600">
+                  <Dropdown.Item
+                    onClick={() => handleLanguageChange('zh')}
+                    className={`!flex !items-center !gap-2 !px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'zh' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
+                  >
+                    <CN title="中文" className="!w-5 !h-auto" />
+                    <span>中文</span>
+                  </Dropdown.Item>
+                  <Dropdown.Item
+                    onClick={() => handleLanguageChange('en')}
+                    className={`!flex !items-center !gap-2 !px-3 !py-1.5 !text-sm !text-semi-color-text-0 dark:!text-gray-200 ${currentLang === 'en' ? '!bg-semi-color-primary-light-default dark:!bg-blue-600 !font-semibold' : 'hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-600'}`}
+                  >
+                    <GB title="English" className="!w-5 !h-auto" />
+                    <span>English</span>
+                  </Dropdown.Item>
+                </Dropdown.Menu>
+              }
+            >
+              <Button
+                icon={<IconLanguage className="text-lg" />}
+                aria-label={t('切换语言')}
+                theme="borderless"
+                type="tertiary"
+                className="!p-1.5 !text-current focus:!bg-semi-color-fill-1 dark:focus:!bg-gray-700 !rounded-full bg-semi-color-fill-0 dark:bg-semi-color-fill-1 hover:bg-semi-color-fill-1 dark:hover:bg-semi-color-fill-2"
+              />
+            </Dropdown>
+
+            {renderUserArea()}
+          </div>
+        </div>
+      </div>
+
+      <div className="md:hidden">
+        <div
+          className={`
+            absolute top-16 left-0 right-0 bg-semi-color-bg-0 
+            shadow-lg p-3
+            transform transition-all duration-300 ease-in-out
+            ${mobileMenuOpen ? 'translate-y-0 opacity-100 visible' : '-translate-y-4 opacity-0 invisible'}
+          `}
+        >
+          <nav className="flex flex-col gap-1">
+            {renderNavLinks(true)}
+          </nav>
         </div>
-      </Layout>
-    </>
+      </div>
+    </header>
   );
 };
 

+ 2 - 2
web/src/i18n/locales/en.json

@@ -162,8 +162,8 @@
   "聊天": "Chat",
   "注销成功!": "Logout successful!",
   "注销": "Logout",
-  "登录": "Login",
-  "注册": "Register",
+  "登录": "Sign in",
+  "注册": "Sign up",
   "加载{name}中...": "Loading {name}...",
   "未登录或登录已过期,请重新登录!": "Not logged in or session expired. Please login again!",
   "用户登录": "User Login",