refactor(导航): 将哈希路由改为标准路径路由

重构导航系统,将原有的哈希路由(#section)改为标准路径路由(/path)
修改相关组件和测试用例以适应新的路由方式
移除不必要的滚动和哈希监听逻辑
This commit was merged in pull request #12.
This commit is contained in:
张翔
2026-04-23 08:07:48 +08:00
parent 95f246fa36
commit 7484512252
5 changed files with 32 additions and 140 deletions
+9 -90
View File
@@ -1,66 +1,24 @@
'use client';
import { Suspense, useState, useEffect, useCallback, useRef } from 'react';
import { Suspense, useState, useEffect, useCallback } from 'react';
import { StaticLink } from '@/components/ui/static-link';
import Image from 'next/image';
import { usePathname, useSearchParams } from 'next/navigation';
import { usePathname } from 'next/navigation';
import { Menu, X } from 'lucide-react';
import { AnimatePresence, motion } from 'framer-motion';
import { Button } from '@/components/ui/button';
import { COMPANY_INFO, NAVIGATION, type NavigationItem } from '@/lib/constants';
import { useFocusTrap } from '@/hooks/use-focus-trap';
declare global {
interface Window {
__isProgrammaticScroll?: boolean;
}
}
function HeaderContent() {
const [isOpen, setIsOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
const pathname = usePathname();
const searchParams = useSearchParams();
const focusTrapRef = useFocusTrap<HTMLDivElement>(isOpen);
const isScrollingRef = useRef(false);
const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const getActiveSection = useCallback(() => {
if (pathname === '/contact') {return 'contact';}
if (pathname === '/') {
const section = searchParams.get('section');
return section || 'home';
}
return '';
}, [pathname, searchParams]);
const activeSection = getActiveSection();
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 20);
if (pathname === '/' && !isScrollingRef.current && !window.__isProgrammaticScroll) {
const scrollPosition = window.scrollY + 100;
const sections = ['home', 'services', 'solutions', 'products', 'cases', 'about', 'team', 'news'];
for (const sectionId of sections) {
const element = document.getElementById(sectionId);
if (element) {
const offsetTop = element.offsetTop;
const offsetHeight = element.offsetHeight;
if (scrollPosition >= offsetTop && scrollPosition < offsetTop + offsetHeight) {
const currentSection = searchParams.get('section') || 'home';
if (currentSection !== sectionId) {
const url = sectionId === 'home' ? '/' : `/?section=${sectionId}`;
window.history.replaceState(null, '', url);
}
break;
}
}
}
}
};
const handleGlobalKeyDown = (e: KeyboardEvent) => {
@@ -75,11 +33,8 @@ function HeaderContent() {
return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('keydown', handleGlobalKeyDown);
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
};
}, [pathname, isOpen, searchParams]);
}, [isOpen]);
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
@@ -93,57 +48,21 @@ function HeaderContent() {
const handleNavClick = useCallback((e: React.MouseEvent<HTMLAnchorElement>, item: NavigationItem) => {
e.preventDefault();
if (item.id === 'contact') {
window.location.href = '/contact';
} else if (item.id === 'home') {
if (pathname === '/') {
isScrollingRef.current = true;
window.scrollTo({ top: 0, behavior: 'smooth' });
window.history.pushState(null, '', '/');
scrollTimeoutRef.current = setTimeout(() => {
isScrollingRef.current = false;
}, 1000);
} else {
window.location.href = '/';
}
} else {
if (pathname === '/') {
const scrollToSection = (retryCount = 0) => {
const element = document.getElementById(item.id);
if (element) {
isScrollingRef.current = true;
element.scrollIntoView({ behavior: 'smooth', block: 'start' });
window.history.pushState(null, '', `/?section=${item.id}`);
scrollTimeoutRef.current = setTimeout(() => {
isScrollingRef.current = false;
}, 1000);
} else if (retryCount < 10) {
setTimeout(() => scrollToSection(retryCount + 1), 100);
}
};
scrollToSection();
} else {
window.location.href = `/?section=${item.id}`;
}
}
window.location.href = item.href;
setIsOpen(false);
}, [pathname]);
}, []);
const isActive = useCallback((item: NavigationItem) => {
if (item.id === 'contact') {
return pathname === '/contact';
}
if (pathname === '/') {
return activeSection === item.id;
if (item.id === 'home') {
return pathname === '/';
}
return false;
}, [pathname, activeSection]);
return pathname === `/${item.id}`;
}, [pathname]);
const navigationItems = NAVIGATION;