Files
juip-web/src/home/product/index.tsx
2026-08-12 13:49:04 +08:00

143 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react"
import { useSearchParams } from "react-router-dom"
import Wrap from "@/components/wrap"
import { PRODUCT_DATA } from "@/lib/models/product"
import { cn } from "@/lib/utils"
import ProductGrid from "./productGrid"
import ProductSidebar from "./productSidebar"
import ProductTabs from "./productTabs"
import PurchaseInfo from "./purchaseInfo"
const TAB_ALIAS: Record<string, string> = {
short: "dynamic",
long: "dynamic",
http: "http",
tunnel: "dynamic",
exclusive: "static",
}
export default function ProductPage() {
const [searchParams, setSearchParams] = useSearchParams()
const [activeVersionId, setActiveVersionId] = useState<string>("v1")
const urlTab = searchParams.get("tab") || "dynamic"
const activeTabId = TAB_ALIAS[urlTab] || urlTab
const activeBrandId = searchParams.get("brand") || ""
const currentTab =
PRODUCT_DATA.find(t => t.id === activeTabId) || PRODUCT_DATA[0]
const currentBrand =
currentTab.brands.find(b => b.id === activeBrandId) || currentTab.brands[0]
const handleTabChange = (tabId: string) => {
const targetTab = PRODUCT_DATA.find(t => t.id === tabId)
const firstBrand = targetTab?.brands[0]
setSearchParams({ tab: tabId, brand: firstBrand?.id || "" })
setActiveVersionId("v1")
}
const handleBrandSelect = (brandId: string) => {
setSearchParams({ tab: activeTabId, brand: brandId })
setActiveVersionId("v1")
}
const getDisplayProducts = () => {
if (!currentBrand) return []
if (currentBrand.versions && currentBrand.versions.length > 0) {
return currentBrand.products.filter(p => p.version === activeVersionId)
}
return currentBrand.products
}
const displayProducts = getDisplayProducts()
const isPurchaseInfo = currentBrand?.id === "info"
return (
<Wrap className="flex flex-col gap-16 pt-30 bg-white">
<div className="flex flex-col">
<ProductTabs
tabs={PRODUCT_DATA}
activeId={activeTabId}
onTabChange={handleTabChange}
/>
<div className="flex mt-8 mb-8 mx-auto w-full">
<div className="shrink-0 border-r border-gray-200">
<ProductSidebar
brands={currentTab.brands}
activeId={currentBrand?.id || ""}
onSelect={handleBrandSelect}
/>
</div>
<div className="flex-1 flex flex-col">
{!isPurchaseInfo ? (
<>
{" "}
<div className="grid grid-cols-2 pl-3 text-sm gap-2 text-gray-600 leading-relaxed">
<ul className="space-y-2">
<li className="flex items-start gap-2">
<span className="w-2 h-2 mt-1.5 bg-emerald-400 rounded-full shrink-0" />
线
</li>
<li className="flex items-start gap-2">
<span className="w-2 h-2 mt-1.5 bg-emerald-400 rounded-full shrink-0" />
</li>
</ul>
<ul className="space-y-2">
<li className="flex items-start gap-2">
<span className="w-2 h-2 mt-1.5 bg-emerald-400 rounded-full shrink-0" />
IP池超大且纯净线
</li>
<li className="flex items-start gap-2">
<span className="w-2 h-2 mt-1.5 bg-emerald-400 rounded-full shrink-0" />
230+500+线
</li>
</ul>
</div>
<div className="bg-orange-50 border gap-3 border-orange-100 text-orange-400 p-3 text-center font-bold mt-3 mb-3 text-sm w-full">
5
</div>
{currentBrand?.versions && currentBrand.versions.length > 0 && (
<div className="w-full mt-2 pl-6">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{currentBrand.versions.map(v => (
<button
key={v.id}
onClick={() => setActiveVersionId(v.id)}
className={cn(
"w-full py-2 rounded-full text-sm font-medium transition-all cursor-pointer text-center",
activeVersionId === v.id
? "bg-linear-to-r from-blue-500 to-cyan-400 text-white shadow-sm"
: "bg-gray-100 text-gray-600 hover:bg-gray-200",
)}
>
{v.label}
</button>
))}
</div>
</div>
)}
<main className="pl-6">
{displayProducts.length > 0 ? (
<ProductGrid products={displayProducts} />
) : (
<div className="text-center py-20 text-gray-400">
</div>
)}
</main>
</>
) : (
<div className="pl-6 pr-6">
<PurchaseInfo />
</div>
)}
</div>
</div>
</div>
</Wrap>
)
}