使用heroui完成对页面的重构

This commit is contained in:
2026-01-29 16:23:10 +08:00
parent 2377e2dda2
commit d265126035
24 changed files with 5360 additions and 1121 deletions

View File

@@ -2,14 +2,26 @@
import { useState, useEffect } from 'react';
import Image from 'next/image';
import {
Navbar as HeroNavbar,
NavbarBrand,
NavbarContent,
NavbarItem,
Button,
Dropdown,
DropdownTrigger,
DropdownMenu,
DropdownItem
} from '@heroui/react';
import ProductMenu from './ProductMenu';
import ResourceMenu from './ResourceMenu';
import { useLanguage } from '@/contexts/LanguageContext';
import { useTheme } from '@/contexts/ThemeContext';
export default function Navbar() {
const [scrolled, setScrolled] = useState(false);
const { language, setLanguage, t } = useLanguage();
const [showLangMenu, setShowLangMenu] = useState(false);
const { theme, toggleTheme } = useTheme();
const [showProductMenu, setShowProductMenu] = useState(false);
const [showResourceMenu, setShowResourceMenu] = useState(false);
const [animate, setAnimate] = useState(false);
@@ -24,7 +36,6 @@ export default function Navbar() {
}, []);
useEffect(() => {
// 页面加载后触发动画
requestAnimationFrame(() => {
requestAnimationFrame(() => {
setAnimate(true);
@@ -35,9 +46,6 @@ export default function Navbar() {
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
const target = e.target as Element;
if (showLangMenu && !target.closest('.language-selector')) {
setShowLangMenu(false);
}
if (showProductMenu && !target.closest('.product-menu-container')) {
setShowProductMenu(false);
}
@@ -48,158 +56,239 @@ export default function Navbar() {
document.addEventListener('click', handleClickOutside);
return () => document.removeEventListener('click', handleClickOutside);
}, [showLangMenu, showProductMenu, showResourceMenu]);
}, [showProductMenu, showResourceMenu]);
const toggleLanguage = (lang: 'zh' | 'en') => {
setLanguage(lang);
setShowLangMenu(false);
};
const isDark = theme === 'dark';
return (
<>
<nav className={`fixed top-0 left-0 right-0 z-50 px-10 py-5 flex items-center justify-between transition-all duration-300 ${
scrolled
? 'bg-white/70 backdrop-blur-[50px] shadow-sm'
: 'bg-white/90 backdrop-blur-[50px]'
}`}>
{/* Logo Section */}
<div
className="flex-1 transition-all duration-1000 ease-out"
<HeroNavbar
maxWidth="full"
style={{
transform: animate ? 'translateY(0)' : 'translateY(-3rem)',
opacity: animate ? 1 : 0
backgroundColor: isDark
? scrolled ? 'rgba(10, 10, 10, 0.7)' : 'rgba(10, 10, 10, 0.9)'
: scrolled ? 'rgba(255, 255, 255, 0.7)' : 'rgba(255, 255, 255, 0.9)',
backdropFilter: 'blur(50px)',
borderBottom: isDark
? scrolled ? '1px solid #27272a' : '1px solid #18181b'
: '1px solid transparent',
transition: 'background-color 0.3s ease-out, border-color 0.3s ease-out'
}}
classNames={{
wrapper: "px-10 py-2"
}}
suppressHydrationWarning
>
<Image
src="/logo0.svg"
alt="Logo"
width={160}
height={40}
priority
/>
</div>
{/* Logo */}
<NavbarBrand>
<div
style={{
transform: animate ? 'translateY(0)' : 'translateY(-3rem)',
opacity: animate ? 1 : 0,
transition: 'transform 1s ease-out, opacity 1s ease-out'
}}
>
<Image
src="/logo0.svg"
alt="Logo"
width={160}
height={40}
priority
style={{
filter: isDark ? 'invert(1) brightness(1.2)' : 'none',
transition: 'filter 0.3s ease-out'
}}
/>
</div>
</NavbarBrand>
{/* Navigation Menu */}
<div className="flex items-center gap-12">
<div className="flex items-center gap-6">
{/* Product - Active */}
<div className="product-menu-container">
<div
className={`rounded-lg px-4 py-2 h-9 flex items-center justify-center cursor-pointer transition-colors select-none ${
showProductMenu ? 'bg-[#f3f4f6]' : 'bg-transparent hover:bg-[#f3f4f6]'
{/* Center Menu */}
<NavbarContent className="hidden sm:flex gap-6" justify="center">
{/* Product */}
<NavbarItem className="product-menu-container">
<Button
variant="light"
className={`font-bold text-sm select-none ${
showProductMenu
? isDark ? 'text-[#fafafa] bg-[#27272a]' : 'text-[#111827] bg-[#f3f4f6]'
: isDark ? 'text-[#a1a1aa]' : 'text-[#4b5563]'
}`}
onClick={() => {
onPress={() => {
setShowProductMenu(!showProductMenu);
setShowResourceMenu(false);
}}
>
<span className={`font-bold text-sm leading-[150%] font-inter ${
showProductMenu ? 'text-[#111827]' : 'text-[#4b5563]'
}`}>
{t('nav.product')}
</span>
</div>
</div>
{t('nav.product')}
</Button>
</NavbarItem>
{/* Community */}
<div className="bg-transparent rounded-lg px-4 py-2 h-9 flex items-center justify-center hover:bg-[#f3f4f6] transition-colors cursor-pointer select-none">
<span className="text-[#4b5563] font-bold text-sm leading-[150%] font-inter">
<NavbarItem>
<Button
variant="light"
className={`font-bold text-sm select-none ${
isDark ? 'text-[#a1a1aa]' : 'text-[#4b5563]'
}`}
>
{t('nav.community')}
</span>
</div>
</Button>
</NavbarItem>
{/* Resource */}
<div className="resource-menu-container">
<div
className={`rounded-lg px-4 py-2 h-9 flex items-center justify-center cursor-pointer transition-colors select-none ${
showResourceMenu ? 'bg-[#f3f4f6]' : 'bg-transparent hover:bg-[#f3f4f6]'
<NavbarItem className="resource-menu-container">
<Button
variant="light"
className={`font-bold text-sm select-none ${
showResourceMenu
? isDark ? 'text-[#fafafa] bg-[#27272a]' : 'text-[#111827] bg-[#f3f4f6]'
: isDark ? 'text-[#a1a1aa]' : 'text-[#4b5563]'
}`}
onClick={() => {
onPress={() => {
setShowResourceMenu(!showResourceMenu);
setShowProductMenu(false);
}}
>
<span className={`font-bold text-sm leading-[150%] font-inter ${
showResourceMenu ? 'text-[#111827]' : 'text-[#4b5563]'
}`}>
{t('nav.resource')}
</span>
</div>
</div>
</div>
</div>
{t('nav.resource')}
</Button>
</NavbarItem>
</NavbarContent>
{/* Launch App Button & Language Selector */}
<div className="flex-1 flex justify-end items-center gap-4">
<div
style={{
transform: animate ? 'translateY(0)' : 'translateY(-3rem)',
opacity: animate ? 1 : 0,
transition: 'transform 1s ease-out 200ms, opacity 1s ease-out 200ms'
}}
suppressHydrationWarning
>
<div className="bg-[#111827] rounded-lg px-5 py-2.5 h-10 flex items-center justify-center overflow-hidden cursor-pointer hover:scale-105 select-none transition-transform duration-300">
<span className="text-[#fcfcfd] font-bold text-sm leading-[150%] font-inter">
{t('nav.launchApp')}
</span>
</div>
</div>
{/* Language Selector */}
<div className="relative language-selector">
<div
className="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-[#f3f4f6] transition-colors cursor-pointer select-none"
onClick={() => setShowLangMenu(!showLangMenu)}
>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 18.3333C14.6024 18.3333 18.3333 14.6024 18.3333 10C18.3333 5.39763 14.6024 1.66667 10 1.66667C5.39763 1.66667 1.66667 5.39763 1.66667 10C1.66667 14.6024 5.39763 18.3333 10 18.3333Z" stroke="#4B5563" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M1.66667 10H18.3333" stroke="#4B5563" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M10 1.66667C12.0844 3.94863 13.269 6.91003 13.3333 10C13.269 13.09 12.0844 16.0514 10 18.3333C7.91561 16.0514 6.73104 13.09 6.66667 10C6.73104 6.91003 7.91561 3.94863 10 1.66667Z" stroke="#4B5563" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
<span className="text-[#4b5563] font-inter text-sm font-bold">
{language === 'zh' ? '中文' : 'EN'}
</span>
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4.5L6 7.5L9 4.5" stroke="#4B5563" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
{/* Dropdown Menu */}
{showLangMenu && (
<div className="absolute right-0 mt-2 w-32 bg-white rounded-lg shadow-lg border border-[#e5e7eb] overflow-hidden z-50">
<div
className={`px-4 py-2.5 hover:bg-[#f3f4f6] cursor-pointer transition-colors select-none ${
language === 'zh' ? 'bg-[#f3f4f6]' : ''
}`}
onClick={() => toggleLanguage('zh')}
{/* Right Content */}
<NavbarContent justify="end" className="gap-4">
{/* Launch App Button */}
<NavbarItem>
<div
style={{
transform: animate ? 'translateY(0)' : 'translateY(-3rem)',
opacity: animate ? 1 : 0,
transition: 'transform 1s ease-out 200ms, opacity 1s ease-out 200ms'
}}
>
<Button
className="font-bold text-sm"
style={{
backgroundColor: isDark ? '#ffffff' : '#111827',
color: isDark ? '#0a0a0a' : '#fafafa',
transition: 'background-color 0.3s ease-out, color 0.3s ease-out'
}}
onPress={() => {}}
>
<span className="text-[#111827] font-inter text-sm font-medium"></span>
</div>
<div
className={`px-4 py-2.5 hover:bg-[#f3f4f6] cursor-pointer transition-colors select-none ${
language === 'en' ? 'bg-[#f3f4f6]' : ''
}`}
onClick={() => toggleLanguage('en')}
>
<span className="text-[#111827] font-inter text-sm font-medium">English</span>
</div>
{t('nav.launchApp')}
</Button>
</div>
)}
</div>
</div>
</nav>
<ProductMenu
isOpen={showProductMenu}
onClose={() => setShowProductMenu(false)}
language={language}
/>
<ResourceMenu
isOpen={showResourceMenu}
onClose={() => setShowResourceMenu(false)}
language={language}
/>
</NavbarItem>
{/* Theme Toggle */}
<NavbarItem>
<Button
isIconOnly
variant="light"
onPress={toggleTheme}
className="transition-colors"
>
<div className="relative w-5 h-5">
{/* Moon icon */}
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="absolute inset-0 transition-all duration-300"
style={{
opacity: isDark ? 1 : 0,
transform: isDark ? 'rotate(0deg) scale(1)' : 'rotate(-90deg) scale(0.5)'
}}
>
<path d="M17.5 10.8333C17.3833 12.2083 16.875 13.4792 16.0625 14.5208C15.25 15.5625 14.1875 16.3333 12.9792 16.7292C11.7708 17.125 10.4792 17.125 9.27083 16.7292C8.0625 16.3333 7 15.5625 6.1875 14.5208C5.375 13.4792 4.86667 12.2083 4.75 10.8333C4.63333 9.45833 4.91667 8.08333 5.5625 6.875C6.20833 5.66667 7.1875 4.6875 8.375 4.04167C9.5625 3.39583 10.9167 3.125 12.2917 3.25C11.2083 4.45833 10.625 6.04167 10.625 7.70833C10.625 9.375 11.2083 10.9583 12.2917 12.1667C13.375 13.375 14.8333 14.0417 16.375 14.0417C16.7917 14.0417 17.2083 14 17.5 10.8333Z" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
{/* Sun icon */}
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="absolute inset-0 transition-all duration-300"
style={{
opacity: isDark ? 0 : 1,
transform: isDark ? 'rotate(90deg) scale(0.5)' : 'rotate(0deg) scale(1)'
}}
>
<path d="M10 2.5V4.16667M10 15.8333V17.5M17.5 10H15.8333M4.16667 10H2.5M15.3033 4.69667L14.1667 5.83333M5.83333 14.1667L4.69667 15.3033M15.3033 15.3033L14.1667 14.1667M5.83333 5.83333L4.69667 4.69667M13.3333 10C13.3333 11.8409 11.8409 13.3333 10 13.3333C8.15905 13.3333 6.66667 11.8409 6.66667 10C6.66667 8.15905 8.15905 6.66667 10 6.66667C11.8409 6.66667 13.3333 8.15905 13.3333 10Z" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
</div>
</Button>
</NavbarItem>
{/* Language Selector */}
<NavbarItem>
<Dropdown
classNames={{
content: isDark ? 'bg-[#18181b] border-none shadow-lg min-w-[120px] w-[120px]' : 'bg-white border-none shadow-lg min-w-[120px] w-[120px]'
}}
>
<DropdownTrigger>
<Button
variant="light"
className={`font-bold text-sm ${
isDark ? 'text-[#a1a1aa]' : 'text-[#4b5563]'
}`}
startContent={
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 18.3333C14.6024 18.3333 18.3333 14.6024 18.3333 10C18.3333 5.39763 14.6024 1.66667 10 1.66667C5.39763 1.66667 1.66667 5.39763 1.66667 10C1.66667 14.6024 5.39763 18.3333 10 18.3333Z" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M1.66667 10H18.3333" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
<path d="M10 1.66667C12.0844 3.94863 13.269 6.91003 13.3333 10C13.269 13.09 12.0844 16.0514 10 18.3333C7.91561 16.0514 6.73104 13.09 6.66667 10C6.73104 6.91003 7.91561 3.94863 10 1.66667Z" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
}
endContent={
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 4.5L6 7.5L9 4.5" stroke={isDark ? '#a1a1aa' : '#4B5563'} strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
</svg>
}
>
{language === 'zh' ? '中文' : 'EN'}
</Button>
</DropdownTrigger>
<DropdownMenu
aria-label="Language selection"
onAction={(key) => setLanguage(key as 'zh' | 'en')}
selectedKeys={new Set([language])}
classNames={{
list: "py-2"
}}
itemClasses={{
base: "py-3 flex items-center justify-center"
}}
>
<DropdownItem
key="zh"
className={isDark ? 'text-[#fafafa] hover:bg-[#27272a] text-center' : 'text-[#111827] text-center'}
>
</DropdownItem>
<DropdownItem
key="en"
className={isDark ? 'text-[#fafafa] hover:bg-[#27272a] text-center' : 'text-[#111827] text-center'}
>
English
</DropdownItem>
</DropdownMenu>
</Dropdown>
</NavbarItem>
</NavbarContent>
</HeroNavbar>
{/* Custom Menus */}
<ProductMenu
isOpen={showProductMenu}
onClose={() => setShowProductMenu(false)}
language={language}
/>
<ResourceMenu
isOpen={showResourceMenu}
onClose={() => setShowResourceMenu(false)}
language={language}
/>
</>
);
}