Files
web/src/app/(home)/@header/page.tsx

285 lines
8.0 KiB
TypeScript
Raw Normal View History

2025-06-18 17:57:12 +08:00
'use client'
import {useCallback, useEffect, useMemo, useState, PointerEvent, ComponentProps} from 'react'
import Link from 'next/link'
2025-06-18 17:57:12 +08:00
import Image from 'next/image'
import SolutionMenu from './menu-solution'
import ProductMenu from './menu-product'
import HelpMenu from './menu-help'
import MobileMenu from './menu-mobile'
import Wrap from '@/components/wrap'
2025-06-18 17:57:12 +08:00
import logo from '@/assets/logo.webp'
import {Button} from '@/components/ui/button'
import {useProfileStore} from '@/components/stores-provider'
import UserCenter from '@/components/composites/user-center'
2025-06-18 17:57:12 +08:00
import {MenuIcon} from 'lucide-react'
import down from '@/assets/header/down.svg'
import {merge} from '@/lib/utils'
import {HeaderContext} from './common'
2025-03-24 11:45:54 +08:00
export type ProviderProps = {}
2025-03-18 18:00:29 +08:00
export default function Page(props: ProviderProps) {
2025-06-18 17:57:12 +08:00
// ======================
// 滚动条状态
2025-06-18 17:57:12 +08:00
// ======================
const [scroll, setScroll] = useState(false) // Changed to false for client-side rendering
2025-06-18 17:57:12 +08:00
const handleScroll = useCallback(() => {
setScroll(window.scrollY > 48)
}, [])
2025-06-18 17:57:12 +08:00
useEffect(() => {
// Initialize scroll state on client
2025-06-18 17:57:12 +08:00
setScroll(window.scrollY > 48)
window.addEventListener('scroll', handleScroll)
return () => {
window.removeEventListener('scroll', handleScroll)
}
}, [handleScroll])
// ======================
// 菜单状态
2025-06-18 17:57:12 +08:00
// ======================
const [menu, setMenu] = useState(false)
const [page, setPage] = useState(0)
const pages = useMemo(() => [
<ProductMenu key="product"/>,
<SolutionMenu key="solution"/>,
<HelpMenu key="help"/>,
<MobileMenu key="mobile"/>,
], [])
const enterMenu = (i: number) => {
return (e: PointerEvent) => {
setPage(i)
if (e.pointerType === 'mouse' || page !== i) {
setMenu(true)
}
else {
setMenu(!menu)
}
}
}
const exitMenu = (e: PointerEvent) => {
if (e.pointerType === 'mouse') {
setMenu(false)
}
}
const enterMenuContent = (e: PointerEvent) => {
setMenu(true)
}
const exitMenuContent = (e: PointerEvent) => {
if (e.pointerType === 'mouse') {
setMenu(false)
}
}
const enterMenuMask = (e: PointerEvent) => {
if (e.pointerType !== 'mouse') {
setMenu(false)
}
}
// ======================
// 用户信息
// ======================
const profile = useProfileStore(store => store.profile)
2025-06-18 17:57:12 +08:00
// ======================
// render
// ======================
2025-03-18 18:00:29 +08:00
return (
2025-06-18 17:57:12 +08:00
<header
className={merge(
'fixed top-0 left-0 w-screen z-10 flex flex-col',
menu && 'h-screen',
)}>
<HeaderContext.Provider value={{setMenu}}>
{/* 菜单栏 */}
2025-06-18 17:57:12 +08:00
<div className={merge(
`flex-none`,
`transition-[background,shadow] duration-200 ease-in-out`,
menu
? `bg-[#fffe] backdrop-blur-sm`
: scroll
? `bg-[#fffe] backdrop-blur-sm shadow-lg`
: `bg-transparent shadow-none`,
2025-06-18 17:57:12 +08:00
)}>
<Wrap className="h-20 max-md:h-16 flex justify-between">
<nav className="flex items-center justify-between lg:flex-row-reverse gap-4 lg:gap-8">
{/* 桌面端菜单 */}
<ul className="h-full items-stretch hidden lg:flex">
<LinkItem text="首页" href="/"/>
<MenuItem
text="产品订购"
active={menu && page === 0}
onPointerEnter={enterMenu(0)}
onPointerLeave={exitMenu}
/>
<MenuItem
text="业务场景"
active={menu && page === 1}
onPointerEnter={enterMenu(1)}
onPointerLeave={exitMenu}
/>
<MenuItem
text="帮助中心"
active={menu && page === 2}
onPointerEnter={enterMenu(2)}
onPointerLeave={exitMenu}
/>
<LinkItem
text="企业服务"
href="#"/>
<LinkItem
text="推广返利"
href="#"/>
</ul>
{/* 移动端菜单 */}
<Button
theme="ghost"
className="lg:hidden size-10"
onPointerEnter={enterMenu(3)}
onPointerLeave={exitMenu}
>
<MenuIcon/>
</Button>
{/* logo */}
<Link href="/" className="flex items-center">
<Image src={logo} alt="logo" height={40} className="translate-y-0.5"/>
</Link>
</nav>
{/* 登录 */}
<div className="flex items-center">
{profile == undefined
? (
<>
<Link
href="/login"
className="w-24 h-12 flex items-center justify-center lg:text-lg"
>
<span></span>
</Link>
<Link
href="/login"
className={[
`w-20 lg:w-24 h-10 lg:h-12 bg-gradient-to-r rounded-sm flex items-center justify-center lg:text-lg text-white`,
`transition-colors duration-200 ease-in-out`,
`from-blue-500 to-cyan-400 hover:from-blue-500 hover:to-cyan-300`,
].join(' ')}
>
<span></span>
</Link>
</>
)
: (
<UserCenter profile={profile}/>
)
}
2025-06-18 17:57:12 +08:00
</div>
</Wrap>
</div>
{/* 下拉菜单 */}
<div
className={merge(
`flex-auto overflow-auto lg:flex-none lg:basis-72 shadow-lg box-content`,
`bg-[#fffe] backdrop-blur-sm`,
`transition-[opacity,padding] transition-discrete duration-200 ease-in-out`,
menu
? `delay-[0s,0s] opacity-100 py-4`
: `delay-[0s,0s] opacity-0 py-0 pointer-events-none`,
)}
onPointerEnter={enterMenuContent}
onPointerLeave={exitMenuContent}
>
{pages[page]}
</div>
{/* 遮罩层 */}
<div className="flex-auto" onPointerEnter={enterMenuMask}/>
</HeaderContext.Provider>
2025-03-28 15:00:46 +08:00
</header>
2025-03-18 18:00:29 +08:00
)
}
2025-06-18 17:57:12 +08:00
function LinkItem(props: {
text: string
href: string
}) {
return (
<li className="group relative">
<Link
href={props.href}
className={[
`h-full px-4 flex items-center text-lg`,
`transition-colors duration-200 ease-in-out`,
`hover:text-blue-500`,
].join(' ')}
>
{props.text}
</Link>
<div className={[
`absolute bottom-0 w-full h-0.5 bg-transparent `,
`transition-colors duration-200`,
`group-hover:bg-blue-500`,
].join(' ')}>
</div>
</li>
)
}
2025-06-18 17:57:12 +08:00
function MenuItem(props: {
text: string
active: boolean
} & ComponentProps<'button'>) {
2025-06-18 17:57:12 +08:00
return (
<li className="group relative">
<button
onPointerEnter={props.onPointerEnter}
onPointerLeave={props.onPointerLeave}
className={[
`h-full px-4 flex gap-3 items-center cursor-pointer text-lg`,
`transition-colors duration-200 ease-in-out`,
props.active
? `text-blue-500`
: ``,
].join(' ')}
>
<span>{props.text}</span>
<Image
src={down}
alt="drop_menu"
className={[
`transition-transform duration-200 ease-in-out`,
props.active
? `rotate-180`
: ``,
].join(' ')}
/>
</button>
<div
className={[
`absolute bottom-0 w-full h-0.5 pointer-events-none`,
`transition-colors duration-200`,
props.active
? `bg-blue-500`
: 'bg-transparent',
].join(' ')}/>
</li>
2025-06-18 17:57:12 +08:00
)
}