Files
YG-Datasets/easy-dataset-main/components/Navbar/index.js

258 lines
7.8 KiB
JavaScript

'use client';
import React, { useState, useRef, useEffect } from 'react';
import {
AppBar,
Toolbar,
Box,
IconButton,
useTheme as useMuiTheme,
Tooltip,
useMediaQuery,
LinearProgress
} from '@mui/material';
import { useTranslation } from 'react-i18next';
import { usePathname, useRouter } from 'next/navigation';
import { useTheme } from 'next-themes';
import MenuIcon from '@mui/icons-material/Menu';
// 样式
import * as styles from './styles';
// 子组件
import Logo from './Logo';
import ActionButtons from './ActionButtons';
import NavigationTabs from './NavigationTabs';
import MobileDrawer from './MobileDrawer';
import DesktopMenus from './DesktopMenus';
import ContextBar from './ContextBar';
export default function Navbar({ projects = [], currentProject }) {
const { t } = useTranslation();
const pathname = usePathname();
const router = useRouter();
const theme = useMuiTheme();
const { resolvedTheme, setTheme } = useTheme();
const isProjectDetail = pathname.includes('/projects/') && pathname.split('/').length > 3;
// 检测移动设备
const isMobile = useMediaQuery(theme.breakpoints.down('lg'));
// 移动端抽屉状态
const [drawerOpen, setDrawerOpen] = useState(false);
const [expandedMenu, setExpandedMenu] = useState(null);
// 桌面端菜单状态
const [menuState, setMenuState] = useState({ anchorEl: null, menuType: null });
const [navLoading, setNavLoading] = useState(false);
const navLoadingTimeoutRef = useRef(null);
// ContextBar 悬浮状态
const [contextBarHovered, setContextBarHovered] = useState(false);
const contextTriggerRef = useRef(null);
const contextBarRef = useRef(null);
useEffect(() => {
if (!contextBarHovered) return;
const handleOutsideClick = event => {
if (contextBarRef.current?.contains(event.target)) return;
if (contextTriggerRef.current?.contains(event.target)) return;
const projectMenuEl = document.getElementById('project-menu');
if (projectMenuEl?.contains(event.target)) return;
setContextBarHovered(false);
};
document.addEventListener('pointerdown', handleOutsideClick, true);
return () => {
document.removeEventListener('pointerdown', handleOutsideClick, true);
};
}, [contextBarHovered]);
useEffect(() => {
if (!menuState.menuType) return;
const handleOutsideMenuClick = event => {
if (menuState.anchorEl?.contains(event.target)) return;
if (event.target?.closest?.('.MuiMenu-root')) return;
setMenuState({ anchorEl: null, menuType: null });
};
document.addEventListener('pointerdown', handleOutsideMenuClick, true);
return () => {
document.removeEventListener('pointerdown', handleOutsideMenuClick, true);
};
}, [menuState.anchorEl, menuState.menuType]);
useEffect(() => {
setNavLoading(false);
if (navLoadingTimeoutRef.current) {
clearTimeout(navLoadingTimeoutRef.current);
navLoadingTimeoutRef.current = null;
}
}, [pathname]);
useEffect(() => {
if (!isProjectDetail || !currentProject) return;
const prefetchRoutes = [
`/projects/${currentProject}/multi-turn`,
`/projects/${currentProject}/eval-datasets`,
`/projects/${currentProject}/eval-tasks`
];
prefetchRoutes.forEach(route => router.prefetch(route));
}, [router, currentProject, isProjectDetail]);
useEffect(() => {
return () => {
if (navLoadingTimeoutRef.current) {
clearTimeout(navLoadingTimeoutRef.current);
}
};
}, []);
const handleNavigateStart = () => {
setNavLoading(true);
if (navLoadingTimeoutRef.current) {
clearTimeout(navLoadingTimeoutRef.current);
}
navLoadingTimeoutRef.current = setTimeout(() => {
setNavLoading(false);
navLoadingTimeoutRef.current = null;
}, 12000);
};
const handleMenuOpen = (event, menuType) => {
setMenuState({ anchorEl: event.currentTarget, menuType });
};
const handleMenuClose = () => {
setMenuState({ anchorEl: null, menuType: null });
};
const isMenuOpen = menuType => menuState.menuType === menuType;
const toggleDrawer = () => {
setDrawerOpen(!drawerOpen);
setExpandedMenu(null);
};
const toggleMobileSubmenu = menuType => {
setExpandedMenu(expandedMenu === menuType ? null : menuType);
};
const toggleTheme = () => {
setTheme(resolvedTheme === 'dark' ? 'light' : 'dark');
};
return (
<>
<AppBar
component="nav"
position="sticky"
elevation={0}
color={theme.palette.mode === 'dark' ? 'transparent' : 'primary'}
sx={styles.getAppBarStyles(theme)}
style={{ borderRadius: 0, zIndex: 1200 }}
role="navigation"
aria-label={t('common.mainNavigation', 'Main navigation')}
>
<Toolbar sx={styles.toolbarStyles}>
{/* 左侧: 汉堡菜单(移动端) + Logo */}
<Box
ref={contextTriggerRef}
sx={styles.logoContainerStyles}
onMouseEnter={() => isProjectDetail && setContextBarHovered(true)}
>
{/* 汉堡菜单按钮 */}
{isProjectDetail && isMobile && (
<Tooltip title={t('common.menu', 'Menu')} placement="bottom">
<IconButton
onClick={toggleDrawer}
size="medium"
aria-label={t('common.openMenu', 'Open navigation menu')}
aria-expanded={drawerOpen}
aria-controls="mobile-navigation-drawer"
sx={styles.getHamburgerButtonStyles(theme)}
>
<MenuIcon />
</IconButton>
</Tooltip>
)}
{/* Logo 组件 */}
<Logo theme={theme} />
</Box>
{/* 中间导航 - 仅桌面端 */}
{isProjectDetail && !isMobile && (
<NavigationTabs
theme={theme}
pathname={pathname}
currentProject={currentProject}
handleMenuOpen={handleMenuOpen}
handleMenuClose={handleMenuClose}
onNavigateStart={handleNavigateStart}
/>
)}
{/* 右侧操作区 */}
<ActionButtons
theme={theme}
resolvedTheme={resolvedTheme}
toggleTheme={toggleTheme}
isProjectDetail={isProjectDetail}
currentProject={currentProject}
onActionAreaEnter={!isMobile ? handleMenuClose : undefined}
/>
</Toolbar>
{isProjectDetail && (
<LinearProgress
color="secondary"
sx={{
position: 'absolute',
left: 0,
right: 0,
bottom: 0,
height: 2,
opacity: navLoading ? 1 : 0,
transition: 'opacity 180ms ease'
}}
/>
)}
</AppBar>
{/* ContextBar - 在 Logo 或 ContextBar 悬浮时展示 */}
{isProjectDetail && contextBarHovered && (
<Box ref={contextBarRef} onMouseLeave={() => setContextBarHovered(false)}>
<ContextBar
projects={projects}
currentProjectId={currentProject}
onMouseLeave={() => setContextBarHovered(false)}
/>
</Box>
)}
{/* 移动端抽屉组件 */}
<MobileDrawer
theme={theme}
drawerOpen={drawerOpen}
toggleDrawer={toggleDrawer}
expandedMenu={expandedMenu}
toggleMobileSubmenu={toggleMobileSubmenu}
currentProject={currentProject}
onNavigateStart={handleNavigateStart}
/>
{/* 桌面端菜单组件 */}
<DesktopMenus
theme={theme}
menuState={menuState}
isMenuOpen={isMenuOpen}
handleMenuClose={handleMenuClose}
currentProject={currentProject}
onNavigateStart={handleNavigateStart}
/>
</>
);
}