163 lines
5.1 KiB
TypeScript
163 lines
5.1 KiB
TypeScript
'use client'
|
||
import {ReactNode, useContext, useEffect, useState} from 'react'
|
||
import Wrap from '@/components/wrap'
|
||
import Image, {StaticImageData} from 'next/image'
|
||
import Link from 'next/link'
|
||
import {merge} from '@/lib/utils'
|
||
import prod from '@/assets/header/product/prod.svg'
|
||
import custom from '@/assets/header/product/custom.svg'
|
||
import {useRouter} from 'next/navigation'
|
||
import {HeaderContext} from './common'
|
||
import {Product} from '@/lib/models/product'
|
||
import {listProductHome} from '@/actions/product'
|
||
|
||
export type ProductItem = Product
|
||
type TabType = 'domestic' | 'oversea'
|
||
|
||
export default function ProductMenu() {
|
||
const [type, setType] = useState<TabType>('domestic')
|
||
|
||
return (
|
||
<Wrap className="flex flex-col items-stretch lg:flex-row gap-4 lg:gap-0">
|
||
<ul role="tablist" className="flex-none lg:basis-36 flex lg:flex-col">
|
||
<Tab selected={type === 'domestic'} onSelect={() => setType('domestic')}>国内代理</Tab>
|
||
<Tab selected={type === 'oversea'} onSelect={() => setType('oversea')}>海外代理</Tab>
|
||
</ul>
|
||
{type === 'domestic'
|
||
? (
|
||
<Domestic/>
|
||
) : (
|
||
<Oversea/>
|
||
)
|
||
}
|
||
|
||
</Wrap>
|
||
)
|
||
}
|
||
|
||
export function Tab(props: {
|
||
selected: boolean
|
||
onSelect: () => void
|
||
children: ReactNode
|
||
}) {
|
||
return (
|
||
<li role="tab" className="flex-1 lg:flex-none">
|
||
<button
|
||
className={[
|
||
`w-full p-4 lg:p-6 text-base lg:text-lg cursor-pointer border-b lg:border-b-0 lg:border-r flex justify-center`,
|
||
props.selected ? `bg-linear-to-b lg:bg-linear-to-r from-transparent to-blue-200 border-blue-400` : `border-gray-200`,
|
||
].join(' ')}
|
||
onClick={props.onSelect}
|
||
>
|
||
{props.children}
|
||
</button>
|
||
</li>
|
||
)
|
||
}
|
||
|
||
export function Domestic(props: {}) {
|
||
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 (
|
||
<section role="tabpanel" className="flex-auto">
|
||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
||
<div className="grid grid-cols-1 gap-3">
|
||
{shortProduct && (
|
||
<ProductCard
|
||
icon={prod}
|
||
label="短效动态 IP"
|
||
discount="最低4.5折"
|
||
desc="全国 300+ 城市级定位节点,IP 池资源充足,自动高频切换。适用于数据采集、市场调研、SEO 优化等高并发场景。稳定可靠,响应迅速,助力业务高效运转。"
|
||
href={`/product?type=${shortProduct.code}`}
|
||
/>
|
||
)}
|
||
{longProduct && (
|
||
<ProductCard
|
||
icon={prod}
|
||
label="长效动态 IP"
|
||
discount="最低4.5折"
|
||
desc="IP 存活时长可达数小时至数天,连接稳定不掉线。适用于账号养号、社交运营、电商管理等需要持续在线的场景。优质线路保障,为您的长期业务保驾护航。"
|
||
href={`/product?type=${longProduct.code}`}
|
||
/>
|
||
)}
|
||
</div>
|
||
<div className="flex flex-col gap-3">
|
||
<ProductCard
|
||
icon={custom}
|
||
label="业务定制"
|
||
discount="1V1 专属服务"
|
||
desc="超 1000 家企业共同信赖之选!大客户经理全程 1 对 1 沟通,随时为您排忧解难,提供 24 小时不间断支持"
|
||
href="/custom"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
)
|
||
}
|
||
|
||
export function Oversea(props: {}) {
|
||
return (
|
||
<section role="tabpanel" className="flex-auto flex items-center justify-center">
|
||
<p className="text-2xl text-primary">产品即将上线,敬请期待~</p>
|
||
</section>
|
||
)
|
||
}
|
||
|
||
export function ProductCard(props: {
|
||
icon: StaticImageData
|
||
label: string
|
||
discount: string
|
||
desc: string
|
||
href: string
|
||
}) {
|
||
const router = useRouter()
|
||
const ctx = useContext(HeaderContext)
|
||
if (!ctx) {
|
||
throw new Error(`HeaderContext not found`)
|
||
}
|
||
|
||
const onClick = () => {
|
||
ctx.setMenu(false)
|
||
router.push(props.href)
|
||
}
|
||
|
||
return (
|
||
<Link
|
||
href={props.href}
|
||
className={merge(
|
||
`transition-colors duration-150 ease-in-out`,
|
||
`p-4 rounded-lg flex flex-col gap-1 hover:bg-blue-50`,
|
||
)}
|
||
onClick={onClick}
|
||
>
|
||
<div className="flex items-start gap-3">
|
||
<div className="flex-none">
|
||
<Image src={props.icon} alt="" width={30} height={30}/>
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="flex items-center justify-between gap-3">
|
||
<span className="font-bold">{props.label}</span>
|
||
<span className="text-xs font-medium text-orange-600 bg-orange-50 px-2 py-1 rounded-full">
|
||
{props.discount}
|
||
</span>
|
||
</div>
|
||
<div className="mt-2 text-sm text-gray-400 space-y-1">
|
||
{props.desc}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</Link>
|
||
)
|
||
}
|