314 lines
9.7 KiB
TypeScript
314 lines
9.7 KiB
TypeScript
'use client'
|
||
|
||
import {useContext, useEffect, useState} from 'react'
|
||
import {useRouter} from 'next/navigation'
|
||
import {X} from 'lucide-react'
|
||
import {HeaderContext} from './common'
|
||
import Image, {StaticImageData} from 'next/image'
|
||
import prod from '@/assets/header/product/prod.svg'
|
||
import custom from '@/assets/header/product/custom.svg'
|
||
import s01 from '@/assets/header/solution/01.svg'
|
||
import s02 from '@/assets/header/solution/02.svg'
|
||
import s03 from '@/assets/header/solution/03.svg'
|
||
import s04 from '@/assets/header/solution/04.svg'
|
||
import s05 from '@/assets/header/solution/05.svg'
|
||
import s06 from '@/assets/header/solution/06.svg'
|
||
import s07 from '@/assets/header/solution/07.svg'
|
||
import s08 from '@/assets/header/solution/08.svg'
|
||
import h01 from '@/assets/header/help/01.svg'
|
||
import h02 from '@/assets/header/help/02.svg'
|
||
import h03 from '@/assets/header/help/03.svg'
|
||
import {merge} from '@/lib/utils'
|
||
import Link from 'next/link'
|
||
import logo from '@/assets/logo.webp'
|
||
import {Product} from '@/lib/models/product'
|
||
import {listProductHome} from '@/actions/product'
|
||
|
||
export type MobileMenuProps = {}
|
||
|
||
export default function MobileMenu(props: MobileMenuProps) {
|
||
const ctx = useContext(HeaderContext)
|
||
const router = useRouter()
|
||
const [productTab, setProductTab] = useState<'domestic' | 'oversea'>('domestic')
|
||
|
||
if (!ctx) {
|
||
throw new Error(`HeaderContext not found`)
|
||
}
|
||
|
||
const navigate = (href: string) => {
|
||
ctx.setMenu(false)
|
||
router.push(href)
|
||
}
|
||
const [productList, setProductList] = useState<Product[]>([])
|
||
useEffect(() => {
|
||
const fetchProducts = async () => {
|
||
const res = await listProductHome({})
|
||
if (res.success) {
|
||
setProductList(res.data)
|
||
}
|
||
}
|
||
fetchProducts()
|
||
}, [])
|
||
const shortProduct = productList.find(p => p.name?.includes('短效') || p.code === 'short')
|
||
const longProduct = productList.find(p => p.name?.includes('长效') || p.code === 'long')
|
||
return (
|
||
<div className="h-full flex flex-col bg-white">
|
||
<div className="flex items-center justify-between px-4 h-16 border-b border-gray-100">
|
||
{/* logo */}
|
||
<Link href="/" className="flex items-center">
|
||
<Image src={logo} alt="logo" height={40} className="translate-y-0.5"/>
|
||
</Link>
|
||
<button
|
||
type="button"
|
||
className="rounded-md p-2 text-gray-400 hover:text-gray-600 hover:bg-gray-50 transition-colors"
|
||
onClick={() => ctx.setMenu(false)}
|
||
aria-label="关闭菜单"
|
||
>
|
||
<X className="h-5 w-5"/>
|
||
</button>
|
||
</div>
|
||
|
||
<div className="flex-1 overflow-y-auto px-4 py-6 space-y-8">
|
||
<div className="space-y-3">
|
||
<h3 className="text-sm font-semibold text-gray-500 tracking-wide">
|
||
产品订购
|
||
</h3>
|
||
<div className="flex rounded-lg bg-gray-100">
|
||
<button
|
||
className={merge(
|
||
'flex-1 py-2.5 text-sm font-medium rounded-md transition-all',
|
||
productTab === 'domestic'
|
||
? 'bg-white text-blue-600 shadow-sm'
|
||
: 'text-gray-600 hover:text-gray-900',
|
||
)}
|
||
onClick={() => setProductTab('domestic')}
|
||
>
|
||
国内代理
|
||
</button>
|
||
<button
|
||
className={merge(
|
||
'flex-1 py-2.5 text-sm font-medium rounded-md transition-all',
|
||
productTab === 'oversea'
|
||
? 'bg-white text-blue-600 shadow-sm'
|
||
: 'text-gray-600 hover:text-gray-900',
|
||
)}
|
||
onClick={() => setProductTab('oversea')}
|
||
>
|
||
海外代理
|
||
</button>
|
||
</div>
|
||
|
||
{productTab === 'domestic' && (
|
||
<div className="space-y-2">
|
||
{shortProduct && (
|
||
<ProductItem
|
||
icon={prod}
|
||
label="短效动态IP"
|
||
badge="最低4.5折"
|
||
href={`/product?type=${shortProduct.code}`}
|
||
onNavigate={navigate}
|
||
/>
|
||
)}
|
||
{longProduct && (
|
||
<ProductItem
|
||
icon={prod}
|
||
label="长效静态IP"
|
||
badge="最低4.5折"
|
||
href={`/product?type=${longProduct.code}`}
|
||
onNavigate={navigate}
|
||
/>
|
||
)}
|
||
<ProductItem
|
||
icon={custom}
|
||
label="优质/企业/精选IP"
|
||
badge="专属定制"
|
||
href="/custom"
|
||
onNavigate={navigate}
|
||
/>
|
||
</div>
|
||
)}
|
||
|
||
{productTab === 'oversea' && (
|
||
<div className="mt-4 p-4 bg-blue-50 rounded-lg">
|
||
<p className="text-sm text-blue-600 text-center">
|
||
更多海外节点即将上线,敬请期待~
|
||
</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<MenuSection title="业务场景">
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<SolutionItem
|
||
icon={s01}
|
||
label="数据采集"
|
||
href="/data-capture"
|
||
onNavigate={navigate}
|
||
/>
|
||
<SolutionItem
|
||
icon={s02}
|
||
label="电商运营"
|
||
href="/e-commerce"
|
||
onNavigate={navigate}
|
||
/>
|
||
<SolutionItem
|
||
icon={s03}
|
||
label="市场调研"
|
||
href="/market-research"
|
||
onNavigate={navigate}
|
||
/>
|
||
<SolutionItem
|
||
icon={s04}
|
||
label="SEO优化"
|
||
href="/seo-optimization"
|
||
onNavigate={navigate}
|
||
/>
|
||
<SolutionItem
|
||
icon={s05}
|
||
label="社交媒体"
|
||
href="/social-media"
|
||
onNavigate={navigate}
|
||
/>
|
||
<SolutionItem
|
||
icon={s06}
|
||
label="广告投放"
|
||
href="/advertising"
|
||
onNavigate={navigate}
|
||
/>
|
||
<SolutionItem
|
||
icon={s07}
|
||
label="账号管理"
|
||
href="/account-management"
|
||
onNavigate={navigate}
|
||
/>
|
||
<SolutionItem
|
||
icon={s08}
|
||
label="网络测试"
|
||
href="/network-testing"
|
||
onNavigate={navigate}
|
||
/>
|
||
</div>
|
||
</MenuSection>
|
||
|
||
<MenuSection title="帮助中心">
|
||
<div className="space-y-2">
|
||
<HelpItem
|
||
icon={h01}
|
||
label="短效IP提取"
|
||
onClick={() => navigate('/collect?type=short')}
|
||
/>
|
||
<HelpItem
|
||
icon={h02}
|
||
label="操作指南"
|
||
onClick={() => navigate('/docs/profile-settings')}
|
||
/>
|
||
<HelpItem
|
||
icon={h03}
|
||
label="平台教程"
|
||
onClick={() => navigate('/docs/ios-proxy')}
|
||
/>
|
||
</div>
|
||
</MenuSection>
|
||
|
||
<div className="space-y-2 pt-2">
|
||
<OtherLink
|
||
label="业务定制"
|
||
href="/custom"
|
||
onNavigate={navigate}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function MenuSection(props: {title: string, children: React.ReactNode}) {
|
||
return (
|
||
<div className="space-y-3">
|
||
<h3 className="text-sm font-semibold text-gray-500 tracking-wide">
|
||
{props.title}
|
||
</h3>
|
||
{props.children}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function ProductItem(props: {
|
||
icon: StaticImageData
|
||
label: string
|
||
badge?: string
|
||
href: string
|
||
onNavigate: (href: string) => void
|
||
}) {
|
||
return (
|
||
<button
|
||
type="button"
|
||
className="w-full flex items-center gap-3 rounded-lg border border-gray-100 bg-white px-4 py-3 text-left transition-all hover:border-blue-200 hover:shadow-sm"
|
||
onClick={() => props.onNavigate(props.href)}
|
||
>
|
||
<div className="shrink-0 w-8 h-8 bg-linear-to-br from-blue-50 to-cyan-50 rounded-lg flex items-center justify-center">
|
||
<Image src={props.icon} alt="" width={20} height={20} className="opacity-80"/>
|
||
</div>
|
||
<span className="flex-1 font-medium text-sm text-gray-900">{props.label}</span>
|
||
{props.badge && (
|
||
<span className="text-xs text-orange-600 bg-orange-50 px-2 py-1 rounded-full">
|
||
{props.badge}
|
||
</span>
|
||
)}
|
||
</button>
|
||
)
|
||
}
|
||
|
||
function SolutionItem(props: {
|
||
icon: StaticImageData
|
||
label: string
|
||
href: string
|
||
onNavigate: (href: string) => void
|
||
}) {
|
||
return (
|
||
<button
|
||
type="button"
|
||
className="flex flex-col items-center gap-2 p-3 rounded-lg border border-gray-100 hover:border-blue-200 hover:bg-blue-50/50 transition-all"
|
||
onClick={() => props.onNavigate(props.href)}
|
||
>
|
||
<div className="w-10 h-10 bg-linear-to-br from-blue-50 to-cyan-50 rounded-full flex items-center justify-center">
|
||
<Image src={props.icon} alt="" width={20} height={20} className="opacity-80"/>
|
||
</div>
|
||
<span className="text-xs font-medium text-gray-700">{props.label}</span>
|
||
</button>
|
||
)
|
||
}
|
||
|
||
function HelpItem(props: {
|
||
icon: StaticImageData
|
||
label: string
|
||
onClick: () => void
|
||
}) {
|
||
return (
|
||
<button
|
||
type="button"
|
||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg hover:bg-gray-50 transition-colors"
|
||
onClick={props.onClick}
|
||
>
|
||
<Image src={props.icon} alt="" width={20} height={20} className="opacity-70"/>
|
||
<span className="text-sm text-gray-700">{props.label}</span>
|
||
</button>
|
||
)
|
||
}
|
||
|
||
function OtherLink(props: {
|
||
label: string
|
||
href: string
|
||
onNavigate: (href: string) => void
|
||
}) {
|
||
return (
|
||
<button
|
||
type="button"
|
||
className="w-full flex items-center px-3 py-2.5 text-sm text-gray-700 rounded-lg hover:bg-gray-50 transition-colors"
|
||
onClick={() => props.onNavigate(props.href)}
|
||
>
|
||
{props.label}
|
||
</button>
|
||
)
|
||
}
|