首页菜单栏下拉页面调整高度1024适配

This commit is contained in:
Eamon-meng
2025-06-12 18:51:54 +08:00
parent 831daeb46e
commit 8bbc0fc5f5
6 changed files with 152 additions and 42 deletions

View File

@@ -8,7 +8,7 @@ import banner from '@/assets/header/help/banner.webp'
export default function HelpMenu() { export default function HelpMenu() {
return ( return (
<Wrap className="w-full grid grid-cols-4 gap-4 justify-items-center"> <Wrap className="w-full grid grid-cols-4 gap-4 justify-items-center h-75">
<Column <Column
icon={h01} icon={h01}
title="提取IP" title="提取IP"

View File

@@ -31,14 +31,16 @@ export function LinkItem(props: {
export function MenuItem(props: { export function MenuItem(props: {
text: string text: string
active: boolean active: boolean
onEnter: () => void onEnter: (e: React.PointerEvent) => void
onLeave: () => void onLeave: (e: React.PointerEvent) => void
onTouchStart?: (e: React.TouchEvent) => void
}) { }) {
return ( return (
<li className="group relative"> <li className="group relative">
<button <button
onPointerEnter={props.onEnter} onPointerEnter={props.onEnter}
onPointerLeave={props.onLeave} onPointerLeave={props.onLeave}
onTouchStart={props.onTouchStart}
className={[ className={[
`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`,
@@ -70,3 +72,26 @@ export function MenuItem(props: {
</li> </li>
) )
} }
// 移动端
export function MobileLinkItem(props: {
text: string
href: string
onClick?: () => void
}) {
return (
<li>
<Link
href={props.href}
onClick={props.onClick}
className={[
`block px-4 py-2 text-lg rounded-md`,
`transition-colors duration-200 ease-in-out`,
`hover:bg-blue-50 hover:text-blue-600`,
].join(' ')}
>
{props.text}
</Link>
</li>
)
}

View File

@@ -9,6 +9,7 @@ import prod from '@/assets/header/product/prod.svg'
import custom from '@/assets/header/product/custom.svg' import custom from '@/assets/header/product/custom.svg'
import {HeaderContext} from '@/app/(home)/@header/_client/provider' import {HeaderContext} from '@/app/(home)/@header/_client/provider'
import {useSearchParams} from 'next/navigation' import {useSearchParams} from 'next/navigation'
import {useRouter} from 'next/navigation'
type TabType = 'domestic' | 'oversea' type TabType = 'domestic' | 'oversea'
@@ -100,9 +101,20 @@ export default function ProductMenu() {
const urlType = searchParams?.get('type') const urlType = searchParams?.get('type')
console.log('URL参数:', urlType) console.log('URL参数:', urlType)
}, [searchParams]) }, [searchParams])
// 响应式布局
const [isMobile, setIsMobile] = useState(false)
useEffect(() => {
const checkMobile = () => {
setIsMobile(window.innerWidth <= 1024)
}
checkMobile()
window.addEventListener('resize', checkMobile)
return () => window.removeEventListener('resize', checkMobile)
}, [])
return ( return (
<Wrap className="flex"> <Wrap className="flex h-75">
<ul role="tablist" className="w-48"> <ul role="tablist" className="w-48">
<Tab selected={type === 'domestic'} onSelect={() => setType('domestic')}></Tab> <Tab selected={type === 'domestic'} onSelect={() => setType('domestic')}></Tab>
<Tab selected={type === 'oversea'} onSelect={() => setType('oversea')}></Tab> <Tab selected={type === 'oversea'} onSelect={() => setType('oversea')}></Tab>
@@ -145,12 +157,21 @@ export function DomesticLink(props: {
active?: boolean active?: boolean
}) { }) {
const ctx = useContext(HeaderContext) const ctx = useContext(HeaderContext)
const router = useRouter()
if (!ctx) { if (!ctx) {
throw new Error(`HeaderContext not found`) throw new Error(`HeaderContext not found`)
} }
const onClick = () => { // const onClick = () => {
// ctx.setMenu(false)
// }
const onClick = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
ctx.setMenu(false) ctx.setMenu(false)
setTimeout(() => {
router.push(props.href)
}, ctx.isMobile ? 100 : 0)
} }
return ( return (

View File

@@ -1,5 +1,5 @@
'use client' 'use client'
import {createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState} from 'react' import {createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react'
import Link from 'next/link' import Link from 'next/link'
import Image from 'next/image' import Image from 'next/image'
import {LinkItem, MenuItem} from './navs' import {LinkItem, MenuItem} from './navs'
@@ -14,6 +14,7 @@ import UserCenter from '@/components/composites/user-center'
export const HeaderContext = createContext<{ export const HeaderContext = createContext<{
setMenu: (value: boolean) => void setMenu: (value: boolean) => void
isMobile: boolean
} | null>(null) } | null>(null)
export type ProviderProps = {} export type ProviderProps = {}
@@ -22,7 +23,6 @@ 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 handleScroll = useCallback(() => { const handleScroll = useCallback(() => {
@@ -44,6 +44,55 @@ export default function Provider(props: ProviderProps) {
const [menu, setMenu] = useState(false) const [menu, setMenu] = useState(false)
const [page, setPage] = useState(0) const [page, setPage] = useState(0)
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])
const pages = useMemo(() => [ const pages = useMemo(() => [
<ProductMenu key="product"/>, <ProductMenu key="product"/>,
<SolutionMenu key="solution"/>, <SolutionMenu key="solution"/>,
@@ -60,16 +109,30 @@ export default function Provider(props: ProviderProps) {
// render // render
// ====================== // ======================
// 屏幕1024时响应式处理
useEffect(() => {
const checkMobile = () => {
const mobile = window.innerWidth <= 1024
setIsMobile(mobile)
// 移除自动关闭菜单的逻辑,让交互逻辑处理菜单状态
}
checkMobile()
window.addEventListener('resize', checkMobile)
return () => window.removeEventListener('resize', checkMobile)
}, [])
return ( return (
<HeaderContext.Provider value={{setMenu}}> <HeaderContext.Provider value={{setMenu, isMobile}}>
<div className={[ <div
`transition-[background, shadow] duration-200 ease-in-out`, ref={menuRef}
menu className={[
? `bg-[#fffe] backdrop-blur-sm` `transition-[background, shadow] duration-200 ease-in-out`,
: scroll menu
? `bg-[#fffe] backdrop-blur-sm shadow-lg` ? `bg-[#fffe] backdrop-blur-sm`
: `bg-transparent shadow-none`, : scroll
].join(' ')}> ? `bg-[#fffe] backdrop-blur-sm shadow-lg`
: `bg-transparent shadow-none`,
].join(' ')}>
<Wrap className="h-20 max-md:h-16 flex justify-between"> <Wrap className="h-20 max-md:h-16 flex justify-between">
<div className="flex justify-between gap-8"> <div className="flex justify-between gap-8">
{/* logo */} {/* logo */}
@@ -84,35 +147,23 @@ export default function Provider(props: ProviderProps) {
<MenuItem <MenuItem
text="产品订购" text="产品订购"
active={menu && page === 0} active={menu && page === 0}
onEnter={() => { onEnter={() => handleMenuEnter(0)}
setMenu(true) onLeave={handleMenuLeave}
setPage(0) onTouchStart={() => handleMenuEnter(0)}
}}
onLeave={() => {
return setMenu(false)
}}
/> />
<MenuItem <MenuItem
text="业务场景" text="业务场景"
active={menu && page === 1} active={menu && page === 1}
onEnter={() => { onEnter={() => handleMenuEnter(1)}
setMenu(true) onLeave={handleMenuLeave}
setPage(1) onTouchStart={() => handleMenuEnter(1)}
}}
onLeave={() => {
return setMenu(false)
}}
/> />
<MenuItem <MenuItem
text="帮助中心" text="帮助中心"
active={menu && page === 2} active={menu && page === 2}
onEnter={() => { onEnter={() => handleMenuEnter(2)}
setMenu(true) onLeave={handleMenuLeave}
setPage(2) onTouchStart={() => handleMenuEnter(2)}
}}
onLeave={() => {
return setMenu(false)
}}
/> />
<LinkItem <LinkItem
text="企业服务" text="企业服务"
@@ -168,9 +219,10 @@ export default function Provider(props: ProviderProps) {
menu menu
? `delay-[0s,0s,0s] opacity-100 py-8 h-auto` ? `delay-[0s,0s,0s] opacity-100 py-8 h-auto`
: `delay-[0s,0s,0.2s] opacity-0 py-0 h-0`, : `delay-[0s,0s,0.2s] opacity-0 py-0 h-0`,
isMobile && `max-lg:transition-all max-lg:duration-300`,
].join(' ')} ].join(' ')}
onPointerEnter={() => setMenu(true)} onPointerEnter={() => !isMobile && setMenu(true)}
onPointerLeave={() => setMenu(false)} onPointerLeave={() => !isMobile && setMenu(false)}
> >
{pages[page]} {pages[page]}
</div> </div>

View File

@@ -12,7 +12,7 @@ import {StaticImageData} from 'next/image'
export default function SolutionMenu() { export default function SolutionMenu() {
return ( return (
<Wrap className="grid grid-cols-4 auto-rows-fr gap-4"> <Wrap className="grid grid-cols-4 auto-rows-fr gap-4 h-75">
<SolutionItem <SolutionItem
icon={s01} icon={s01}
title="数据抓取" title="数据抓取"

View File

@@ -1,11 +1,13 @@
'use client' 'use client'
import {ReactNode} from 'react' import {ReactNode, useContext} from 'react'
import {merge} from '@/lib/utils' import {merge} from '@/lib/utils'
import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/components/ui/tabs' import {Tabs, TabsContent, TabsList, TabsTrigger} from '@/components/ui/tabs'
import LongForm from '@/components/composites/purchase/long/form' import LongForm from '@/components/composites/purchase/long/form'
import ShortForm from '@/components/composites/purchase/short/form' import ShortForm from '@/components/composites/purchase/short/form'
import Link from 'next/link' import Link from 'next/link'
import {useSearchParams} from 'next/navigation' import {useSearchParams} from 'next/navigation'
import {HeaderContext} from '@/app/(home)/@header/_client/provider'
import {useRouter} from 'next/navigation'
export type TabType = 'short' | 'long' | 'fixed' | 'custom' export type TabType = 'short' | 'long' | 'fixed' | 'custom'
@@ -38,6 +40,15 @@ function Tab(props: {
value: string value: string
children: ReactNode children: ReactNode
}) { }) {
const ctx = useContext(HeaderContext)
const router = useRouter()
const handleClick = (e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
if (ctx) {
ctx.setMenu(false)
}
}
return ( return (
<Link <Link
href={{ href={{
@@ -52,7 +63,8 @@ function Tab(props: {
`w-36 h-12 text-base font-normal flex-none`, `w-36 h-12 text-base font-normal flex-none`,
`data-[state=active]:text-primary data-[state=active]:bg-primary-muted`, `data-[state=active]:text-primary data-[state=active]:bg-primary-muted`,
)} )}
value={props.value}> value={props.value}
onClick={handleClick}>
{props.children} {props.children}
</TabsTrigger> </TabsTrigger>
</Link> </Link>