2025-03-28 15:00:46 +08:00
|
|
|
|
'use client'
|
2025-06-12 18:51:54 +08:00
|
|
|
|
import {createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'
|
2025-03-28 15:00:46 +08:00
|
|
|
|
import Link from 'next/link'
|
|
|
|
|
|
import Image from 'next/image'
|
|
|
|
|
|
import {LinkItem, MenuItem} from './navs'
|
|
|
|
|
|
import SolutionMenu from './solution'
|
|
|
|
|
|
import ProductMenu from './product'
|
|
|
|
|
|
import HelpMenu from './help'
|
|
|
|
|
|
import Wrap from '@/components/wrap'
|
|
|
|
|
|
import logo from '@/assets/logo.webp'
|
2025-04-23 19:00:53 +08:00
|
|
|
|
import {Button} from '@/components/ui/button'
|
|
|
|
|
|
import {useProfileStore} from '@/components/providers/StoreProvider'
|
2025-06-06 18:41:19 +08:00
|
|
|
|
import UserCenter from '@/components/composites/user-center'
|
2025-03-28 15:00:46 +08:00
|
|
|
|
|
|
|
|
|
|
export const HeaderContext = createContext<{
|
|
|
|
|
|
setMenu: (value: boolean) => void
|
2025-06-12 18:51:54 +08:00
|
|
|
|
isMobile: boolean
|
2025-03-28 15:00:46 +08:00
|
|
|
|
} | null>(null)
|
|
|
|
|
|
|
2025-04-23 19:00:53 +08:00
|
|
|
|
export type ProviderProps = {}
|
2025-03-28 15:00:46 +08:00
|
|
|
|
|
|
|
|
|
|
export default function Provider(props: ProviderProps) {
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
// 滚动条状态
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
const [scroll, setScroll] = useState(false) // Changed to false for client-side rendering
|
|
|
|
|
|
|
|
|
|
|
|
const handleScroll = useCallback(() => {
|
|
|
|
|
|
setScroll(window.scrollY > 48)
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
// Initialize scroll state on client
|
|
|
|
|
|
setScroll(window.scrollY > 48)
|
|
|
|
|
|
window.addEventListener('scroll', handleScroll)
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
window.removeEventListener('scroll', handleScroll)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [handleScroll])
|
|
|
|
|
|
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
// 菜单状态
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
|
|
|
|
|
|
const [menu, setMenu] = useState(false)
|
|
|
|
|
|
const [page, setPage] = useState(0)
|
2025-06-12 18:51:54 +08:00
|
|
|
|
const menuRef = useRef<HTMLDivElement>(null)
|
|
|
|
|
|
// 屏幕1024时
|
|
|
|
|
|
const [isMobile, setIsMobile] = useState(false)
|
|
|
|
|
|
|
|
|
|
|
|
// 点击外部关闭菜单
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const handleClickOutside = (e: MouseEvent | TouchEvent) => {
|
|
|
|
|
|
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
|
|
|
|
const target = e.target as HTMLElement
|
|
|
|
|
|
// 排除所有交互元素
|
|
|
|
|
|
if (!target.closest('a, button, [role="button"], [role="tab"]')) {
|
|
|
|
|
|
setMenu(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
document.addEventListener('mousedown', handleClickOutside)
|
|
|
|
|
|
document.addEventListener('touchstart', handleClickOutside)
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
document.removeEventListener('mousedown', handleClickOutside)
|
|
|
|
|
|
document.removeEventListener('touchstart', handleClickOutside)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
|
|
const handleMenuEnter = (pageIndex: number) => {
|
|
|
|
|
|
if (isMobile) {
|
|
|
|
|
|
// 移动端: 点击切换当前菜单
|
|
|
|
|
|
if (menu && page === pageIndex) {
|
|
|
|
|
|
// 添加关闭动画延迟
|
|
|
|
|
|
setTimeout(() => setMenu(false), 100)
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
setPage(pageIndex)
|
|
|
|
|
|
// 先设置page,再设置menu为true
|
|
|
|
|
|
setTimeout(() => setMenu(true), 0)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else {
|
|
|
|
|
|
// 桌面端: 悬停打开
|
|
|
|
|
|
setPage(pageIndex)
|
|
|
|
|
|
setMenu(true)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const handleMenuLeave = useCallback(() => {
|
|
|
|
|
|
if (!isMobile) {
|
|
|
|
|
|
// 桌面端: 添加延迟关闭以避免意外关闭
|
|
|
|
|
|
setMenu(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
}, [isMobile])
|
2025-03-28 15:00:46 +08:00
|
|
|
|
const pages = useMemo(() => [
|
2025-06-06 18:41:19 +08:00
|
|
|
|
<ProductMenu key="product"/>,
|
|
|
|
|
|
<SolutionMenu key="solution"/>,
|
|
|
|
|
|
<HelpMenu key="help"/>,
|
2025-03-28 15:00:46 +08:00
|
|
|
|
], [])
|
|
|
|
|
|
|
|
|
|
|
|
// ======================
|
2025-04-23 19:00:53 +08:00
|
|
|
|
// 用户信息
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
|
2025-06-06 16:17:33 +08:00
|
|
|
|
const profile = useProfileStore(store => store.profile)
|
2025-04-23 19:00:53 +08:00
|
|
|
|
|
|
|
|
|
|
// ======================
|
|
|
|
|
|
// render
|
2025-03-28 15:00:46 +08:00
|
|
|
|
// ======================
|
|
|
|
|
|
|
2025-06-12 18:51:54 +08:00
|
|
|
|
// 屏幕1024时响应式处理
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
const checkMobile = () => {
|
|
|
|
|
|
const mobile = window.innerWidth <= 1024
|
|
|
|
|
|
setIsMobile(mobile)
|
|
|
|
|
|
// 移除自动关闭菜单的逻辑,让交互逻辑处理菜单状态
|
|
|
|
|
|
}
|
|
|
|
|
|
checkMobile()
|
|
|
|
|
|
window.addEventListener('resize', checkMobile)
|
|
|
|
|
|
return () => window.removeEventListener('resize', checkMobile)
|
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
2025-03-28 15:00:46 +08:00
|
|
|
|
return (
|
2025-06-12 18:51:54 +08:00
|
|
|
|
<HeaderContext.Provider value={{setMenu, isMobile}}>
|
|
|
|
|
|
<div
|
|
|
|
|
|
ref={menuRef}
|
|
|
|
|
|
className={[
|
|
|
|
|
|
`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`,
|
|
|
|
|
|
].join(' ')}>
|
2025-03-28 15:00:46 +08:00
|
|
|
|
<Wrap className="h-20 max-md:h-16 flex justify-between">
|
|
|
|
|
|
<div className="flex justify-between gap-8">
|
|
|
|
|
|
{/* logo */}
|
2025-06-06 18:41:19 +08:00
|
|
|
|
<Link href="/" className="flex items-center">
|
2025-06-09 16:55:44 +08:00
|
|
|
|
<Image src={logo} alt="logo" height={40} className="translate-y-0.5"/>
|
2025-03-28 15:00:46 +08:00
|
|
|
|
</Link>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 菜单 */}
|
|
|
|
|
|
<nav>
|
|
|
|
|
|
<ul className="h-full flex items-stretch max-lg:hidden">
|
2025-06-06 18:41:19 +08:00
|
|
|
|
<LinkItem text="首页" href="/"/>
|
2025-03-28 15:00:46 +08:00
|
|
|
|
<MenuItem
|
2025-06-06 18:41:19 +08:00
|
|
|
|
text="产品订购"
|
2025-03-28 15:00:46 +08:00
|
|
|
|
active={menu && page === 0}
|
2025-06-12 18:51:54 +08:00
|
|
|
|
onEnter={() => handleMenuEnter(0)}
|
|
|
|
|
|
onLeave={handleMenuLeave}
|
|
|
|
|
|
onTouchStart={() => handleMenuEnter(0)}
|
2025-03-28 15:00:46 +08:00
|
|
|
|
/>
|
|
|
|
|
|
<MenuItem
|
2025-06-06 18:41:19 +08:00
|
|
|
|
text="业务场景"
|
2025-03-28 15:00:46 +08:00
|
|
|
|
active={menu && page === 1}
|
2025-06-12 18:51:54 +08:00
|
|
|
|
onEnter={() => handleMenuEnter(1)}
|
|
|
|
|
|
onLeave={handleMenuLeave}
|
|
|
|
|
|
onTouchStart={() => handleMenuEnter(1)}
|
2025-03-28 15:00:46 +08:00
|
|
|
|
/>
|
|
|
|
|
|
<MenuItem
|
2025-06-06 18:41:19 +08:00
|
|
|
|
text="帮助中心"
|
2025-03-28 15:00:46 +08:00
|
|
|
|
active={menu && page === 2}
|
2025-06-12 18:51:54 +08:00
|
|
|
|
onEnter={() => handleMenuEnter(2)}
|
|
|
|
|
|
onLeave={handleMenuLeave}
|
|
|
|
|
|
onTouchStart={() => handleMenuEnter(2)}
|
2025-03-28 15:00:46 +08:00
|
|
|
|
/>
|
|
|
|
|
|
<LinkItem
|
2025-06-06 18:41:19 +08:00
|
|
|
|
text="企业服务"
|
|
|
|
|
|
href="#"/>
|
2025-03-28 15:00:46 +08:00
|
|
|
|
<LinkItem
|
2025-06-06 18:41:19 +08:00
|
|
|
|
text="推广返利"
|
|
|
|
|
|
href="#"/>
|
2025-03-28 15:00:46 +08:00
|
|
|
|
</ul>
|
|
|
|
|
|
</nav>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{/* 登录 */}
|
2025-06-06 18:41:19 +08:00
|
|
|
|
<div className="flex items-center">
|
2025-04-23 19:00:53 +08:00
|
|
|
|
{profile == undefined
|
2025-06-06 18:41:19 +08:00
|
|
|
|
? (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<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>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)
|
2025-04-23 19:00:53 +08:00
|
|
|
|
: (
|
2025-06-06 18:41:19 +08:00
|
|
|
|
// <Link href="/admin">
|
|
|
|
|
|
// <Button theme="gradient" className="h-12">
|
|
|
|
|
|
// 进入控制台
|
|
|
|
|
|
// </Button>
|
|
|
|
|
|
// </Link>
|
|
|
|
|
|
<UserCenter/>
|
2025-04-23 19:00:53 +08:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
</div>
|
2025-03-28 15:00:46 +08:00
|
|
|
|
</Wrap>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 下拉菜单 */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
className={[
|
|
|
|
|
|
`shadow-lg`,
|
|
|
|
|
|
`overflow-hidden bg-[#fffe] backdrop-blur-sm`,
|
|
|
|
|
|
`transition-[opacity,padding,height] transition-discrete duration-200 ease-in-out`,
|
|
|
|
|
|
menu
|
|
|
|
|
|
? `delay-[0s,0s,0s] opacity-100 py-8 h-auto`
|
|
|
|
|
|
: `delay-[0s,0s,0.2s] opacity-0 py-0 h-0`,
|
2025-06-12 18:51:54 +08:00
|
|
|
|
isMobile && `max-lg:transition-all max-lg:duration-300`,
|
2025-03-28 15:00:46 +08:00
|
|
|
|
].join(' ')}
|
2025-06-12 18:51:54 +08:00
|
|
|
|
onPointerEnter={() => !isMobile && setMenu(true)}
|
|
|
|
|
|
onPointerLeave={() => !isMobile && setMenu(false)}
|
2025-03-28 15:00:46 +08:00
|
|
|
|
>
|
|
|
|
|
|
{pages[page]}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</HeaderContext.Provider>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|