手机端适配小于1024时菜单栏的子菜单设置固定展示

This commit is contained in:
Eamon-meng
2025-06-16 17:46:21 +08:00
parent 8bbc0fc5f5
commit e2d559d4e0
3 changed files with 61 additions and 40 deletions

View File

@@ -45,7 +45,7 @@ export function MenuItem(props: {
`h-full px-4 flex gap-3 items-center cursor-pointer text-lg`, `h-full px-4 flex gap-3 items-center cursor-pointer text-lg`,
`transition-colors duration-200 ease-in-out`, `transition-colors duration-200 ease-in-out`,
props.active props.active
? `text-blue-500` ? `lg:text-blue-500`
: ``, : ``,
].join(' ')} ].join(' ')}
> >
@@ -66,7 +66,7 @@ export function MenuItem(props: {
`absolute bottom-0 w-full h-0.5 pointer-events-none`, `absolute bottom-0 w-full h-0.5 pointer-events-none`,
`transition-colors duration-200`, `transition-colors duration-200`,
props.active props.active
? `bg-blue-500` ? `lg:bg-blue-500`
: 'bg-transparent', : 'bg-transparent',
].join(' ')}/> ].join(' ')}/>
</li> </li>

View File

@@ -68,7 +68,7 @@ export function Domestic(props: {}) {
active={currentType === 'fixed'} active={currentType === 'fixed'}
/> />
</div> </div>
<div className="w-64 flex flex-col gap-4"> <div className="w-64 flex flex-col gap-4 max-lg:hidden">
<h3 className="font-bold mb-2 flex items-center gap-3"> <h3 className="font-bold mb-2 flex items-center gap-3">
<Image src={custom} alt="定制" className="w-10 h-10"/> <Image src={custom} alt="定制" className="w-10 h-10"/>
<span></span> <span></span>

View File

@@ -11,6 +11,8 @@ import logo from '@/assets/logo.webp'
import {Button} from '@/components/ui/button' import {Button} from '@/components/ui/button'
import {useProfileStore} from '@/components/providers/StoreProvider' import {useProfileStore} from '@/components/providers/StoreProvider'
import UserCenter from '@/components/composites/user-center' import UserCenter from '@/components/composites/user-center'
import {MenuIcon} from 'lucide-react'
import {merge} from '@/lib/utils'
export const HeaderContext = createContext<{ export const HeaderContext = createContext<{
setMenu: (value: boolean) => void setMenu: (value: boolean) => void
@@ -24,7 +26,7 @@ export default function Provider(props: ProviderProps) {
// 滚动条状态 // 滚动条状态
// ====================== // ======================
const [scroll, setScroll] = useState(false) // Changed to false for client-side rendering const [scroll, setScroll] = useState(false) // Changed to false for client-side rendering
const [mobileMenuOpen, setMobileMenuOpen] = useState(false) // 控制移动端菜单展开/收起
const handleScroll = useCallback(() => { const handleScroll = useCallback(() => {
setScroll(window.scrollY > 48) setScroll(window.scrollY > 48)
}, []) }, [])
@@ -47,6 +49,8 @@ export default function Provider(props: ProviderProps) {
const menuRef = useRef<HTMLDivElement>(null) const menuRef = useRef<HTMLDivElement>(null)
// 屏幕1024时 // 屏幕1024时
const [isMobile, setIsMobile] = useState(false) const [isMobile, setIsMobile] = useState(false)
// 控制产品订购下拉菜单的展开/收起
const [productDropdownOpen, setProductDropdownOpen] = useState(false)
// 点击外部关闭菜单 // 点击外部关闭菜单
useEffect(() => { useEffect(() => {
@@ -56,6 +60,7 @@ export default function Provider(props: ProviderProps) {
// 排除所有交互元素 // 排除所有交互元素
if (!target.closest('a, button, [role="button"], [role="tab"]')) { if (!target.closest('a, button, [role="button"], [role="tab"]')) {
setMenu(false) setMenu(false)
setProductDropdownOpen(false) // 同时关闭产品下拉菜单
} }
} }
} }
@@ -68,20 +73,10 @@ export default function Provider(props: ProviderProps) {
}, []) }, [])
const handleMenuEnter = (pageIndex: number) => { const handleMenuEnter = (pageIndex: number) => {
if (isMobile) { if (isMobile && menu && page === pageIndex) {
// 移动端: 点击切换当前菜单 setMenu(false)
if (menu && page === pageIndex) {
// 添加关闭动画延迟
setTimeout(() => setMenu(false), 100)
} }
else { else {
setPage(pageIndex)
// 先设置page再设置menu为true
setTimeout(() => setMenu(true), 0)
}
}
else {
// 桌面端: 悬停打开
setPage(pageIndex) setPage(pageIndex)
setMenu(true) setMenu(true)
} }
@@ -89,7 +84,6 @@ export default function Provider(props: ProviderProps) {
const handleMenuLeave = useCallback(() => { const handleMenuLeave = useCallback(() => {
if (!isMobile) { if (!isMobile) {
// 桌面端: 添加延迟关闭以避免意外关闭
setMenu(false) setMenu(false)
} }
}, [isMobile]) }, [isMobile])
@@ -121,56 +115,86 @@ export default function Provider(props: ProviderProps) {
return () => window.removeEventListener('resize', checkMobile) return () => window.removeEventListener('resize', checkMobile)
}, []) }, [])
// 切换移动菜单
const toggleMobileMenu = () => {
setMobileMenuOpen(!mobileMenuOpen)
}
return ( return (
<HeaderContext.Provider value={{setMenu, isMobile}}> <HeaderContext.Provider value={{setMenu, isMobile}}>
<div <div
ref={menuRef} ref={menuRef}
className={[ className={[
`transition-[background, shadow] duration-200 ease-in-out`, `transition-[background, shadow] duration-200 ease-in-out`,
menu menu || mobileMenuOpen || productDropdownOpen
? `bg-[#fffe] backdrop-blur-sm` ? `bg-[#fffe] backdrop-blur-sm`
: scroll : scroll
? `bg-[#fffe] backdrop-blur-sm shadow-lg` ? `bg-[#fffe] backdrop-blur-sm shadow-lg`
: `bg-transparent shadow-none`, : `bg-transparent shadow-none`,
].join(' ')}> ].join(' ')}>
<Wrap className="h-20 max-md:h-16 flex justify-between"> <Wrap className="h-20 flex justify-between items-center">
<div className="flex justify-between gap-8"> <div className="flex justify-between items-center gap-2 lg:gap-8 h-9 lg:h-auto max-lg:flex-row-reverse lg:items-stretch">
{/* logo */} {/* logo */}
<Link href="/" className="flex items-center"> <Link href="/" className="flex items-center">
<Image src={logo} alt="logo" height={40} className="translate-y-0.5"/> <Image src={logo} alt="logo" height={40} className="translate-y-0.5"/>
</Link> </Link>
{/* 菜单 */} {/* 菜单 */}
<nav> <nav className="flex flex-col items-center">
<ul className="h-full flex items-stretch max-lg:hidden"> <Button
theme="ghost"
className="w-9 h-9 lg:hidden"
onClick={toggleMobileMenu}
aria-expanded={productDropdownOpen}
>
<MenuIcon/>
</Button>
<ul
className={merge(
'h-full items-stretch max-lg:h-auto relative',
'max-lg:absolute max-lg:top-full max-lg:left-0 max-lg:w-screen',
'max-lg:bg-[#fffe] max-lg:backdrop-blur-sm',
mobileMenuOpen
? 'max-lg:flex max-lg:flex-col max-lg:max-h-[500px] max-lg:py-4 max-lg:opacity-100'
: 'max-lg:hidden max-lg:max-h-0 max-lg:py-0 max-lg:opacity-0',
'lg:flex lg:items-stretch ',
)}
>
<LinkItem text="首页" href="/"/> <LinkItem text="首页" href="/"/>
<MenuItem <MenuItem
text="产品订购" text="产品订购"
active={menu && page === 0} active={menu && page === 0}
onEnter={() => handleMenuEnter(0)} onEnter={() => handleMenuEnter(0)}
onLeave={handleMenuLeave} onLeave={handleMenuLeave}
onTouchStart={() => handleMenuEnter(0)} onTouchStart={() => handleMenuEnter(0)}
/> >
{/* {!isMobile && !menu && <ProductMenu/>} */}
</MenuItem>
<MenuItem <MenuItem
text="业务场景" text="业务场景"
active={menu && page === 1} active={menu && page === 1}
onEnter={() => handleMenuEnter(1)} onEnter={() => handleMenuEnter(1)}
onLeave={handleMenuLeave} onLeave={handleMenuLeave}
onTouchStart={() => handleMenuEnter(1)} // onTouchStart={() => handleMenuEnter(1)}
/> >
{/* {!isMobile && !menu && <SolutionMenu/>} */}
</MenuItem>
<MenuItem <MenuItem
text="帮助中心" text="帮助中心"
active={menu && page === 2} active={menu && page === 2}
onEnter={() => handleMenuEnter(2)} onEnter={() => handleMenuEnter(2)}
onLeave={handleMenuLeave} onLeave={handleMenuLeave}
onTouchStart={() => handleMenuEnter(2)} // onTouchStart={() => handleMenuEnter(2)}
/> >
{/* {!isMobile && !menu && <HelpMenu/>} */}
</MenuItem>
<LinkItem <LinkItem
text="企业服务" text="企业服务"
href="#"/> href="#"/>
<LinkItem <LinkItem
text="推广返利" text="推广返利"
href="#"/> href="#"/>
</ul> </ul>
</nav> </nav>
</div> </div>
@@ -178,31 +202,26 @@ export default function Provider(props: ProviderProps) {
<div className="flex items-center"> <div className="flex items-center">
{profile == undefined {profile == undefined
? ( ? (
<> <div className="flex items-center max-lg:gap-2">
<Link <Link
href="/login" href="/login"
className="w-24 h-12 flex items-center justify-center lg:text-lg" className="px-2 lg:w-24 h-9 lg:h-12 flex items-center justify-center lg:text-lg"
> >
<span></span> <span></span>
</Link> </Link>
<Link <Link
href="/login" href="/login"
className={[ 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`, `px-2 lg:w-24 h-9 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`, `transition-colors duration-200 ease-in-out`,
`from-blue-500 to-cyan-400 hover:from-blue-500 hover:to-cyan-300`, `from-blue-500 to-cyan-400 hover:from-blue-500 hover:to-cyan-300`,
].join(' ')} ].join(' ')}
> >
<span></span> <span></span>
</Link> </Link>
</> </div>
) )
: ( : (
// <Link href="/admin">
// <Button theme="gradient" className="h-12">
// 进入控制台
// </Button>
// </Link>
<UserCenter/> <UserCenter/>
) )
} }
@@ -215,11 +234,13 @@ export default function Provider(props: ProviderProps) {
className={[ className={[
`shadow-lg`, `shadow-lg`,
`overflow-hidden bg-[#fffe] backdrop-blur-sm`, `overflow-hidden bg-[#fffe] backdrop-blur-sm`,
`transition-[opacity,padding,height] transition-discrete duration-200 ease-in-out`, `transition-all duration-200 ease-in-out`,
menu menu
? `delay-[0s,0s,0s] opacity-100 py-8 h-auto` ? `opacity-100 py-8 h-auto visible`
: `delay-[0s,0s,0.2s] opacity-0 py-0 h-0`, : `opacity-0 py-0 h-0 invisible`,
isMobile && `max-lg:transition-all max-lg:duration-300`, isMobile
? `max-lg:fixed max-lg:top-65 max-lg:left-0 max-lg:right-0 max-lg:w-full max-lg:z-[60] max-lg:overflow-y-auto max-lg:max-h-[calc(100vh-5rem)]`
: `absolute top-full left-0 right-0`,
].join(' ')} ].join(' ')}
onPointerEnter={() => !isMobile && setMenu(true)} onPointerEnter={() => !isMobile && setMenu(true)}
onPointerLeave={() => !isMobile && setMenu(false)} onPointerLeave={() => !isMobile && setMenu(false)}