Преглед изворни кода

♻️ refactor: HeaderBar into modular, maintainable components & polish responsive UI

Summary
• Extracted `LogoSection`, `NavLinks`, `UserArea`, and `ActionButtons` from `HeaderBar.js`, reducing complexity and improving readability.
• Removed unused state, handlers, and redundant imports from `HeaderBar.js`.
• Simplified mobile/desktop logic:
  – Menu icon now shows only on mobile `/console` routes.
  – Logo, system name, and mode tags appear on all desktop screens and on mobile non-console pages.
• Reworked skeleton loaders:
  – Narrower width on mobile (`40 px`) and clearer spacing (`p-1`).
• Added global `.scrollbar-hide` utility in `index.css` to enable scrollable areas without visible scrollbars.
• Ensured nav bar is horizontally scrollable across all breakpoints.
• Cleaned up language-switch, New Year, and notice handlers; consolidated side effects.
• Updated imports and internal calls after component extraction.
• Passed required props to new sub-components and removed obsolete ones.
• Confirmed zero linter warnings after refactor.

Why
Breaking the monolithic header into focused components makes future updates simpler, facilitates isolation testing, and aligns with the existing component architecture under `components/`. The UI tweaks provide a better mobile experience and consistent styling across devices.

Notes
No backend changes required. All routes and contexts remain untouched.
t0ng7u пре 6 месеци
родитељ
комит
94d9607447

+ 61 - 113
web/src/components/layout/HeaderBar.js

@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact [email protected]
 */
 
-import React, { useContext, useEffect, useState, useRef } from 'react';
+import React, { useContext, useEffect, useState } from 'react';
 import { Link, useNavigate, useLocation } from 'react-router-dom';
 import { UserContext } from '../../context/User/index.js';
 import { useSetTheme, useTheme } from '../../context/Theme/index.js';
@@ -63,7 +63,6 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
   const [logoLoaded, setLogoLoaded] = useState(false);
   let navigate = useNavigate();
   const [currentLang, setCurrentLang] = useState(i18n.language);
-  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
   const location = useLocation();
   const [noticeVisible, setNoticeVisible] = useState(false);
   const [unreadCount, setUnreadCount] = useState(0);
@@ -157,7 +156,6 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
     userDispatch({ type: 'logout' });
     localStorage.removeItem('user');
     navigate('/login');
-    setMobileMenuOpen(false);
   }
 
   const handleNewYearClick = () => {
@@ -228,20 +226,12 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
 
   const handleLanguageChange = (lang) => {
     i18n.changeLanguage(lang);
-    setMobileMenuOpen(false);
-  };
-
-  const handleNavLinkClick = (itemKey) => {
-    if (itemKey === 'home') {
-      // styleDispatch(styleActions.setSider(false)); // This line is removed
-    }
-    setMobileMenuOpen(false);
   };
 
   const renderNavLinks = (isMobileView = false, isLoading = false) => {
     if (isLoading) {
       const skeletonLinkClasses = isMobileView
-        ? 'flex items-center gap-1 p-3 w-full rounded-md'
+        ? 'flex items-center gap-1 p-1 w-full rounded-md'
         : 'flex items-center gap-1 p-2 rounded-md';
       return Array(4)
         .fill(null)
@@ -253,7 +243,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
               placeholder={
                 <Skeleton.Title
                   active
-                  style={{ width: isMobileView ? 100 : 60, height: 16 }}
+                  style={{ width: isMobileView ? 40 : 60, height: 16 }}
                 />
               }
             />
@@ -263,8 +253,8 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
 
     return 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';
+        ? 'flex-shrink-0 flex items-center gap-1 p-1 font-semibold'
+        : 'flex-shrink-0 flex items-center gap-1 p-2 font-semibold';
 
       const linkContent = (
         <span>{link.text}</span>
@@ -278,7 +268,6 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
             target='_blank'
             rel='noopener noreferrer'
             className={commonLinkClasses}
-            onClick={() => handleNavLinkClick(link.itemKey)}
           >
             {linkContent}
           </a>
@@ -295,7 +284,6 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
           key={link.itemKey}
           to={targetPath}
           className={commonLinkClasses}
-          onClick={() => handleNavLinkClick(link.itemKey)}
         >
           {linkContent}
         </Link>
@@ -337,7 +325,6 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
               <Dropdown.Item
                 onClick={() => {
                   navigate('/console/personal');
-                  setMobileMenuOpen(false);
                 }}
                 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-blue-500 dark:hover:!text-white"
               >
@@ -349,7 +336,6 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
               <Dropdown.Item
                 onClick={() => {
                   navigate('/console/token');
-                  setMobileMenuOpen(false);
                 }}
                 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-blue-500 dark:hover:!text-white"
               >
@@ -361,7 +347,6 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
               <Dropdown.Item
                 onClick={() => {
                   navigate('/console/topup');
-                  setMobileMenuOpen(false);
                 }}
                 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-blue-500 dark:hover:!text-white"
               >
@@ -426,7 +411,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
 
       return (
         <div className="flex items-center">
-          <Link to="/login" onClick={() => handleNavLinkClick('login')} className="flex">
+          <Link to="/login" className="flex">
             <Button
               theme="borderless"
               type="tertiary"
@@ -439,7 +424,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
           </Link>
           {showRegisterButton && (
             <div className="hidden md:block">
-              <Link to="/register" onClick={() => handleNavLinkClick('register')} className="flex -ml-px">
+              <Link to="/register" className="flex -ml-px">
                 <Button
                   theme="solid"
                   type="primary"
@@ -469,94 +454,72 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
       <div className="w-full px-2">
         <div className="flex items-center justify-between h-16">
           <div className="flex items-center">
-            <div className="md:hidden">
+            {isConsoleRoute && isMobile && (
               <Button
                 icon={
-                  isConsoleRoute
-                    ? ((isMobile ? drawerOpen : collapsed) ? <IconClose className="text-lg" /> : <IconMenu className="text-lg" />)
-                    : (mobileMenuOpen ? <IconClose className="text-lg" /> : <IconMenu className="text-lg" />)
+                  (isMobile ? drawerOpen : collapsed) ? <IconClose className="text-lg" /> : <IconMenu className="text-lg" />
                 }
-                aria-label={
-                  isConsoleRoute
-                    ? ((isMobile ? drawerOpen : collapsed) ? t('关闭侧边栏') : t('打开侧边栏'))
-                    : (mobileMenuOpen ? t('关闭菜单') : t('打开菜单'))
-                }
-                onClick={() => {
-                  if (isConsoleRoute) {
-                    // 控制侧边栏的显示/隐藏,无论是否移动设备
-                    isMobile ? onMobileMenuToggle() : toggleCollapsed();
-                  } else {
-                    // 控制HeaderBar自己的移动菜单
-                    setMobileMenuOpen(!mobileMenuOpen);
-                  }
-                }}
+                aria-label={(isMobile ? drawerOpen : collapsed) ? t('关闭侧边栏') : t('打开侧边栏')}
+                onClick={() => isMobile ? onMobileMenuToggle() : toggleCollapsed()}
                 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">
-              <div className="relative w-8 h-8 md:w-8 md:h-8">
-                {(isLoading || !logoLoaded) && (
-                  <Skeleton.Image
-                    active
-                    className="absolute inset-0 !rounded-full"
-                    style={{ width: '100%', height: '100%' }}
+            )}
+            {(!isMobile || !isConsoleRoute) && (
+              <Link to="/" className="flex items-center gap-2">
+                <div className="relative w-8 h-8 md:w-8 md:h-8">
+                  {(isLoading || !logoLoaded) && (
+                    <Skeleton.Image
+                      active
+                      className="absolute inset-0 !rounded-full"
+                      style={{ width: '100%', height: '100%' }}
+                    />
+                  )}
+                  <img
+                    src={logo}
+                    alt="logo"
+                    className={`absolute inset-0 w-full h-full transition-opacity duration-200 group-hover:scale-105 rounded-full ${(!isLoading && logoLoaded) ? 'opacity-100' : 'opacity-0'}`}
                   />
-                )}
-                <img
-                  src={logo}
-                  alt="logo"
-                  className={`absolute inset-0 w-full h-full transition-opacity duration-200 group-hover:scale-105 rounded-full ${(!isLoading && logoLoaded) ? 'opacity-100' : 'opacity-0'}`}
-                />
-              </div>
-              <div className="hidden md:flex items-center gap-2">
-                <div className="flex items-center gap-2">
-                  <Skeleton
-                    loading={isLoading}
-                    active
-                    placeholder={
-                      <Skeleton.Title
-                        active
-                        style={{ width: 120, height: 24 }}
-                      />
-                    }
-                  >
-                    <Typography.Title heading={4} className="!text-lg !font-semibold !mb-0">
-                      {systemName}
-                    </Typography.Title>
-                  </Skeleton>
-                  {(isSelfUseMode || isDemoSiteMode) && !isLoading && (
-                    <Tag
-                      color={isSelfUseMode ? 'purple' : 'blue'}
-                      className="text-xs px-1.5 py-0.5 rounded whitespace-nowrap shadow-sm"
-                      size="small"
-                      shape='circle'
+                </div>
+                <div className="hidden md:flex items-center gap-2">
+                  <div className="flex items-center gap-2">
+                    <Skeleton
+                      loading={isLoading}
+                      active
+                      placeholder={
+                        <Skeleton.Title
+                          active
+                          style={{ width: 120, height: 24 }}
+                        />
+                      }
                     >
-                      {isSelfUseMode ? t('自用模式') : t('演示站点')}
-                    </Tag>
-                  )}
+                      <Typography.Title heading={4} className="!text-lg !font-semibold !mb-0">
+                        {systemName}
+                      </Typography.Title>
+                    </Skeleton>
+                    {(isSelfUseMode || isDemoSiteMode) && !isLoading && (
+                      <Tag
+                        color={isSelfUseMode ? 'purple' : 'blue'}
+                        className="text-xs px-1.5 py-0.5 rounded whitespace-nowrap shadow-sm"
+                        size="small"
+                        shape='circle'
+                      >
+                        {isSelfUseMode ? t('自用模式') : t('演示站点')}
+                      </Tag>
+                    )}
+                  </div>
                 </div>
-              </div>
-            </Link>
-            {(isSelfUseMode || isDemoSiteMode) && !isLoading && (
-              <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"
-                  shape='circle'
-                >
-                  {isSelfUseMode ? t('自用模式') : t('演示站点')}
-                </Tag>
-              </div>
+              </Link>
             )}
-
-            <nav className="hidden md:flex items-center gap-1 lg:gap-2 ml-6">
-              {renderNavLinks(false, isLoading)}
-            </nav>
           </div>
 
+          {/* 中间可滚动导航区域(全部设备)*/}
+          <nav className="flex flex-1 items-center gap-1 lg:gap-2 mx-2 md:mx-4 overflow-x-auto whitespace-nowrap scrollbar-hide">
+            {renderNavLinks(isMobile, isLoading)}
+          </nav>
+
+          {/* 右侧用户信息及功能按钮 */}
           <div className="flex items-center gap-2 md:gap-3">
             {isNewYear && (
               <Dropdown
@@ -587,6 +550,7 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
                   onClick={handleNoticeOpen}
                   theme="borderless"
                   type="tertiary"
+                  size='small'
                   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"
                 />
               </Badge>
@@ -644,22 +608,6 @@ const HeaderBar = ({ onMobileMenuToggle, drawerOpen }) => {
           </div>
         </div>
       </div>
-
-      <div className="md:hidden">
-        <div
-          className={`
-            absolute top-16 left-0 right-0
-            bg-white/75 dark:bg-zinc-900/75 backdrop-blur-lg
-            shadow-lg p-3
-            transform transition-all duration-300 ease-in-out
-            ${(!isConsoleRoute && mobileMenuOpen) ? 'translate-y-0 opacity-100 visible' : '-translate-y-4 opacity-0 invisible'}
-          `}
-        >
-          <nav className="flex flex-col gap-1">
-            {renderNavLinks(true, isLoading)}
-          </nav>
-        </div>
-      </div>
     </header>
   );
 };

+ 1 - 1
web/src/components/settings/personal/components/UserInfoHeader.js

@@ -54,7 +54,7 @@ const UserInfoHeader = ({ t, userState }) => {
               {getAvatarText()}
             </Avatar>
             <div className="flex-1 min-w-0">
-              <div className="text-base text-3xl font-semibold truncate text-gray-800 dark:text-gray-100">
+              <div className="text-base !text-3xl font-semibold truncate text-gray-800 dark:text-gray-100">
                 {getUsername()}
               </div>
               <div className="mt-1 flex flex-wrap gap-1 sm:gap-2">

+ 6 - 9
web/src/components/topup/RechargeCard.jsx

@@ -236,17 +236,14 @@ const RechargeCard = ({
                     </div>
                   </div>
                 )}
-
               </div>
             ) : (
-              <div className='py-8'>
-                <Banner
-                  type='warning'
-                  description={t('管理员未开启在线充值功能,请联系管理员开启或使用兑换码充值。')}
-                  className='!rounded-xl'
-                  closeIcon={null}
-                />
-              </div>
+              <Banner
+                type='warning'
+                description={t('管理员未开启在线充值功能,请联系管理员开启或使用兑换码充值。')}
+                className='!rounded-xl'
+                closeIcon={null}
+              />
             )}
           </div>
         </TabPane>

+ 15 - 0
web/src/index.css

@@ -375,6 +375,21 @@ code {
 }
 
 /* ==================== 滚动条样式统一管理 ==================== */
+/* 通用隐藏滚动条工具类 */
+.scrollbar-hide {
+  -ms-overflow-style: none;
+  /* IE and Edge */
+  scrollbar-width: none;
+  /* Firefox */
+}
+
+.scrollbar-hide::-webkit-scrollbar {
+  width: 0 !important;
+  height: 0 !important;
+  display: none !important;
+  /* Chrome, Safari, Opera */
+}
+
 /* 表格滚动条样式 */
 .semi-table-body::-webkit-scrollbar {
   width: 6px;