452 lines
21 KiB
TypeScript
452 lines
21 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||
import Link from 'next/link';
|
||
import { usePathname, useRouter } from 'next/navigation';
|
||
import { ChevronDown, Menu, X, Globe, Phone, MessageCircle, Send, Search } from 'lucide-react';
|
||
import Logo from './Logo';
|
||
import { contactConfig } from '../../lib/useContact';
|
||
import { homeTranslations } from '../../lib/home-translations';
|
||
|
||
interface HeaderProps {
|
||
language: string;
|
||
setLanguage: (lang: string) => void;
|
||
translations: any;
|
||
locale?: string;
|
||
}
|
||
|
||
interface NavigationItem {
|
||
path: string;
|
||
label: string;
|
||
hasSubmenu?: boolean;
|
||
submenu?: { path: string; label: string; description?: string; }[];
|
||
}
|
||
|
||
export default function Header({ language, setLanguage, translations, locale }: HeaderProps) {
|
||
const pathname = usePathname();
|
||
const router = useRouter();
|
||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
||
const [isLanguageDropdownOpen, setIsLanguageDropdownOpen] = useState(false);
|
||
const [activeSubmenu, setActiveSubmenu] = useState<string | null>(null);
|
||
const [mounted, setMounted] = useState(false);
|
||
const [isScrolled, setIsScrolled] = useState(false);
|
||
|
||
// 处理滚动效果
|
||
useEffect(() => {
|
||
// 确保在客户端执行
|
||
if (typeof window === 'undefined') return;
|
||
|
||
const handleScroll = () => {
|
||
setIsScrolled(window.scrollY > 10);
|
||
};
|
||
|
||
window.addEventListener('scroll', handleScroll);
|
||
return () => window.removeEventListener('scroll', handleScroll);
|
||
}, []);
|
||
|
||
useEffect(() => {
|
||
setMounted(true);
|
||
}, []);
|
||
|
||
// 获取当前locale
|
||
const currentLocale = useMemo(() => {
|
||
if (!mounted) return locale || 'zh-CN';
|
||
return locale || (pathname.startsWith('/en') ? 'en' : pathname.startsWith('/zh-TW') ? 'zh-TW' : 'zh-CN');
|
||
}, [mounted, locale, pathname]);
|
||
|
||
// 同步语言状态
|
||
useEffect(() => {
|
||
if (mounted && language !== currentLocale) {
|
||
setLanguage(currentLocale);
|
||
}
|
||
}, [mounted, currentLocale, language, setLanguage]);
|
||
|
||
// 获取当前翻译
|
||
const t = useMemo(() => {
|
||
return translations || homeTranslations[currentLocale] || homeTranslations['zh-CN'];
|
||
}, [translations, currentLocale]);
|
||
|
||
// 获取路径(不含locale前缀)
|
||
const pathWithoutLocale = useMemo(() => {
|
||
if (!mounted) return '/';
|
||
return pathname.replace(/^\/(en|zh-TW|zh-CN)/, '') || '/';
|
||
}, [mounted, pathname]);
|
||
|
||
// 导航项配置
|
||
const navigationItems: NavigationItem[] = useMemo(() => [
|
||
{ path: '/', label: t.nav?.home || '首页' },
|
||
{
|
||
path: '/products',
|
||
label: t.nav?.products || '产品与服务',
|
||
hasSubmenu: true,
|
||
submenu: [
|
||
{ path: '/products#cloud-server', label: '云服务器', description: '弹性可扩展的计算服务' },
|
||
{ path: '/products#storage', label: '存储服务', description: '安全可靠的数据存储' },
|
||
{ path: '/products#database', label: '数据库服务', description: '高性能托管数据库' },
|
||
{ path: '/products#network', label: '网络服务', description: '全球网络加速服务' }
|
||
]
|
||
},
|
||
{ path: '/news', label: t.nav?.news || '新闻资讯' },
|
||
{ path: '/support', label: t.nav?.support || '客户支持' },
|
||
{ path: '/about', label: t.nav?.about || '关于我们' }
|
||
], [t.nav]);
|
||
|
||
// 语言选项
|
||
const languageOptions = useMemo(() => [
|
||
{ code: 'zh-CN', label: '简体中文', flag: '🇨🇳' },
|
||
{ code: 'zh-TW', label: '繁體中文', flag: '🇹🇼' },
|
||
{ code: 'en', label: 'English', flag: '🇺🇸' }
|
||
], []);
|
||
|
||
// 检查是否为活跃路径
|
||
const isActive = useCallback((path: string) => {
|
||
if (path === '/') return pathWithoutLocale === '/';
|
||
return pathWithoutLocale.startsWith(path);
|
||
}, [pathWithoutLocale]);
|
||
|
||
// 生成本地化路径
|
||
const getLocalizedPath = useCallback((path: string, targetLocale: string = currentLocale) => {
|
||
return `/${targetLocale}${path}`;
|
||
}, [currentLocale]);
|
||
|
||
// 处理语言切换
|
||
const handleLanguageChange = useCallback((newLanguage: string) => {
|
||
if (!mounted) return;
|
||
|
||
setLanguage(newLanguage);
|
||
setIsLanguageDropdownOpen(false);
|
||
|
||
const newPath = getLocalizedPath(pathWithoutLocale, newLanguage);
|
||
router.push(newPath);
|
||
}, [mounted, setLanguage, pathWithoutLocale, router, getLocalizedPath]);
|
||
|
||
// 切换移动菜单
|
||
const toggleMobileMenu = useCallback(() => {
|
||
setIsMobileMenuOpen(prev => !prev);
|
||
}, []);
|
||
|
||
// 处理子菜单
|
||
const handleSubmenuEnter = useCallback((path: string) => {
|
||
setActiveSubmenu(path);
|
||
}, []);
|
||
|
||
const handleSubmenuLeave = useCallback(() => {
|
||
setActiveSubmenu(null);
|
||
}, []);
|
||
|
||
// 关闭所有菜单
|
||
const closeAllMenus = useCallback(() => {
|
||
setIsMobileMenuOpen(false);
|
||
setIsLanguageDropdownOpen(false);
|
||
setActiveSubmenu(null);
|
||
}, []);
|
||
|
||
// 点击外部关闭菜单
|
||
useEffect(() => {
|
||
const handleClickOutside = (event: MouseEvent) => {
|
||
const target = event.target as Element;
|
||
if (!target.closest('.header-menu')) {
|
||
closeAllMenus();
|
||
}
|
||
};
|
||
|
||
document.addEventListener('click', handleClickOutside);
|
||
return () => document.removeEventListener('click', handleClickOutside);
|
||
}, [closeAllMenus]);
|
||
|
||
// 渲染桌面导航项
|
||
const renderDesktopNavItem = (item: NavigationItem) => {
|
||
const isActiveItem = isActive(item.path);
|
||
|
||
return (
|
||
<div
|
||
key={item.path}
|
||
className="relative group"
|
||
onMouseEnter={() => item.hasSubmenu && handleSubmenuEnter(item.path)}
|
||
onMouseLeave={() => item.hasSubmenu && handleSubmenuLeave()}
|
||
>
|
||
<Link
|
||
href={getLocalizedPath(item.path)}
|
||
className={`relative px-3 py-2 rounded-md text-sm font-medium transition-all duration-200 flex items-center gap-1 ${
|
||
isActiveItem
|
||
? 'text-blue-600 bg-blue-50'
|
||
: 'text-gray-700 hover:text-blue-600 hover:bg-gray-50'
|
||
}`}
|
||
>
|
||
{item.label}
|
||
{item.hasSubmenu && (
|
||
<ChevronDown size={14} className={`transition-transform duration-200 ${
|
||
activeSubmenu === item.path ? 'rotate-180' : ''
|
||
}`} />
|
||
)}
|
||
{isActiveItem && (
|
||
<span className="absolute bottom-0 left-1/2 transform -translate-x-1/2 w-1 h-1 bg-blue-600 rounded-full"></span>
|
||
)}
|
||
</Link>
|
||
|
||
{/* 子菜单 */}
|
||
{item.hasSubmenu && item.submenu && (
|
||
<div className={`absolute top-full left-0 mt-1 w-72 bg-white rounded-lg shadow-lg border border-gray-200 py-2 transition-all duration-200 ${
|
||
activeSubmenu === item.path
|
||
? 'opacity-100 visible translate-y-0'
|
||
: 'opacity-0 invisible -translate-y-2'
|
||
}`}>
|
||
{item.submenu.map((subItem) => (
|
||
<Link
|
||
key={subItem.path}
|
||
href={getLocalizedPath(subItem.path)}
|
||
className="block px-4 py-3 text-sm text-gray-700 hover:bg-gray-50 hover:text-blue-600 transition-colors"
|
||
onClick={closeAllMenus}
|
||
>
|
||
<div className="font-medium">{subItem.label}</div>
|
||
{subItem.description && (
|
||
<div className="text-xs text-gray-500 mt-1">{subItem.description}</div>
|
||
)}
|
||
</Link>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// 渲染移动端导航项
|
||
const renderMobileNavItem = (item: NavigationItem) => {
|
||
const isActiveItem = isActive(item.path);
|
||
|
||
return (
|
||
<div key={item.path} className="space-y-1">
|
||
<Link
|
||
href={getLocalizedPath(item.path)}
|
||
className={`flex items-center justify-between px-4 py-3 rounded-lg text-base font-medium transition-colors ${
|
||
isActiveItem
|
||
? 'text-blue-600 bg-blue-50'
|
||
: 'text-gray-700 hover:text-blue-600 hover:bg-gray-50'
|
||
}`}
|
||
onClick={closeAllMenus}
|
||
>
|
||
{item.label}
|
||
{isActiveItem && <span className="w-2 h-2 bg-blue-600 rounded-full"></span>}
|
||
</Link>
|
||
|
||
{/* 移动端子菜单 */}
|
||
{item.hasSubmenu && item.submenu && (
|
||
<div className="pl-4 space-y-1">
|
||
{item.submenu.map((subItem) => (
|
||
<Link
|
||
key={subItem.path}
|
||
href={getLocalizedPath(subItem.path)}
|
||
className="block px-4 py-2 text-sm text-gray-600 hover:text-blue-600 hover:bg-gray-50 rounded-md transition-colors"
|
||
onClick={closeAllMenus}
|
||
>
|
||
{subItem.label}
|
||
</Link>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<header className={`sticky top-0 z-50 transition-all duration-300 ${
|
||
isScrolled
|
||
? 'bg-white/95 backdrop-blur-md shadow-md'
|
||
: 'bg-white shadow-sm'
|
||
}`}>
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div className="flex justify-between items-center h-16">
|
||
{/* Logo */}
|
||
<Link
|
||
href={getLocalizedPath('/')}
|
||
className="flex items-center space-x-2 hover:opacity-80 transition-opacity"
|
||
>
|
||
<Logo size="sm" variant="dark" />
|
||
</Link>
|
||
|
||
{/* 桌面端导航 */}
|
||
<nav className="hidden lg:flex items-center space-x-1">
|
||
{navigationItems.map(renderDesktopNavItem)}
|
||
</nav>
|
||
|
||
{/* 桌面端右侧操作区 */}
|
||
<div className="hidden lg:flex items-center space-x-4">
|
||
{/* 联系方式 */}
|
||
<div className="flex items-center space-x-3 text-sm border-r border-gray-200 pr-4">
|
||
<a
|
||
href={contactConfig.telegram}
|
||
className="flex items-center space-x-1 text-gray-600 hover:text-blue-500 transition-colors"
|
||
title="Telegram"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
>
|
||
<Send size={16} />
|
||
<span className="hidden xl:inline">Telegram</span>
|
||
</a>
|
||
<a
|
||
href={contactConfig.whatsapp}
|
||
className="flex items-center space-x-1 text-gray-600 hover:text-green-600 transition-colors"
|
||
title="WhatsApp"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
>
|
||
<MessageCircle size={16} />
|
||
<span className="hidden xl:inline">WhatsApp</span>
|
||
</a>
|
||
<a
|
||
href={contactConfig.phoneHref}
|
||
className="flex items-center space-x-1 text-gray-600 hover:text-blue-600 transition-colors"
|
||
title="电话联系"
|
||
>
|
||
<Phone size={16} />
|
||
<span className="hidden xl:inline">{contactConfig.phone}</span>
|
||
</a>
|
||
</div>
|
||
|
||
{/* 语言选择器 */}
|
||
<div className="relative header-menu">
|
||
<button
|
||
onClick={() => setIsLanguageDropdownOpen(!isLanguageDropdownOpen)}
|
||
className="flex items-center space-x-1 px-3 py-2 text-sm text-gray-600 hover:text-blue-600 rounded-md hover:bg-gray-50 transition-colors"
|
||
aria-label="选择语言"
|
||
title="选择语言"
|
||
>
|
||
<Globe size={16} />
|
||
<span>{languageOptions.find(opt => opt.code === currentLocale)?.flag}</span>
|
||
<ChevronDown size={14} className={`transition-transform duration-200 ${
|
||
isLanguageDropdownOpen ? 'rotate-180' : ''
|
||
}`} />
|
||
</button>
|
||
|
||
{isLanguageDropdownOpen && (
|
||
<div className="absolute right-0 top-full mt-1 w-48 bg-white rounded-lg shadow-lg border border-gray-200 py-1">
|
||
{languageOptions.map((option) => (
|
||
<button
|
||
key={option.code}
|
||
onClick={() => handleLanguageChange(option.code)}
|
||
className={`w-full flex items-center space-x-3 px-4 py-2 text-sm text-left hover:bg-gray-50 transition-colors ${
|
||
currentLocale === option.code ? 'text-blue-600 bg-blue-50' : 'text-gray-700'
|
||
}`}
|
||
>
|
||
<span>{option.flag}</span>
|
||
<span>{option.label}</span>
|
||
{currentLocale === option.code && (
|
||
<span className="ml-auto w-2 h-2 bg-blue-600 rounded-full"></span>
|
||
)}
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 联系我们按钮 */}
|
||
<Link
|
||
href={getLocalizedPath('/contact')}
|
||
className="bg-gradient-to-r from-blue-600 to-blue-700 text-white px-4 py-2 rounded-md hover:from-blue-700 hover:to-blue-800 transition-all duration-200 shadow-sm hover:shadow-md transform hover:-translate-y-0.5"
|
||
>
|
||
{t.nav?.contact || '联系我们'}
|
||
</Link>
|
||
</div>
|
||
|
||
{/* 移动端菜单按钮 */}
|
||
<div className="lg:hidden flex items-center space-x-3">
|
||
{/* 移动端语言选择 */}
|
||
<div className="relative header-menu">
|
||
<button
|
||
onClick={() => setIsLanguageDropdownOpen(!isLanguageDropdownOpen)}
|
||
className="p-2 text-gray-600 hover:text-blue-600 rounded-md hover:bg-gray-50 transition-colors"
|
||
aria-label="选择语言"
|
||
title="选择语言"
|
||
>
|
||
<Globe size={20} />
|
||
</button>
|
||
|
||
{isLanguageDropdownOpen && (
|
||
<div className="absolute right-0 top-full mt-1 w-40 bg-white rounded-lg shadow-lg border border-gray-200 py-1">
|
||
{languageOptions.map((option) => (
|
||
<button
|
||
key={option.code}
|
||
onClick={() => handleLanguageChange(option.code)}
|
||
className={`w-full flex items-center space-x-2 px-3 py-2 text-sm text-left hover:bg-gray-50 transition-colors ${
|
||
currentLocale === option.code ? 'text-blue-600 bg-blue-50' : 'text-gray-700'
|
||
}`}
|
||
>
|
||
<span>{option.flag}</span>
|
||
<span className="text-xs">{option.code.toUpperCase()}</span>
|
||
</button>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 汉堡菜单按钮 */}
|
||
<button
|
||
onClick={toggleMobileMenu}
|
||
className="p-2 text-gray-600 hover:text-blue-600 rounded-md hover:bg-gray-50 transition-colors"
|
||
aria-label="切换菜单"
|
||
>
|
||
{isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 移动端菜单 */}
|
||
<div className={`lg:hidden transition-all duration-300 ease-in-out ${
|
||
isMobileMenuOpen
|
||
? 'max-h-screen opacity-100'
|
||
: 'max-h-0 opacity-0 overflow-hidden'
|
||
}`}>
|
||
<div className="pb-4 pt-2 border-t border-gray-200">
|
||
{/* 移动端导航项 */}
|
||
<div className="space-y-1 mb-6">
|
||
{navigationItems.map(renderMobileNavItem)}
|
||
</div>
|
||
|
||
{/* 移动端联系我们按钮 */}
|
||
<div className="px-4">
|
||
<Link
|
||
href={getLocalizedPath('/contact')}
|
||
className="w-full bg-gradient-to-r from-blue-600 to-blue-700 text-white text-center px-4 py-3 rounded-lg hover:from-blue-700 hover:to-blue-800 transition-all duration-200 font-medium block"
|
||
onClick={closeAllMenus}
|
||
>
|
||
{t.nav?.contact || '联系我们'}
|
||
</Link>
|
||
</div>
|
||
|
||
{/* 移动端联系方式 */}
|
||
<div className="mt-6 pt-4 border-t border-gray-200 px-4">
|
||
<div className="flex justify-center space-x-6">
|
||
<a
|
||
href={contactConfig.telegram}
|
||
className="flex items-center space-x-2 text-blue-500 hover:text-blue-600 transition-colors"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
>
|
||
<Send size={20} />
|
||
<span className="text-sm">Telegram</span>
|
||
</a>
|
||
<a
|
||
href={contactConfig.whatsapp}
|
||
className="flex items-center space-x-2 text-green-600 hover:text-green-700 transition-colors"
|
||
target="_blank"
|
||
rel="noopener noreferrer"
|
||
>
|
||
<MessageCircle size={20} />
|
||
<span className="text-sm">WhatsApp</span>
|
||
</a>
|
||
<a
|
||
href={contactConfig.phoneHref}
|
||
className="flex items-center space-x-2 text-blue-600 hover:text-blue-700 transition-colors"
|
||
>
|
||
<Phone size={20} />
|
||
<span className="text-sm">电话</span>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
);
|
||
}
|