完善购买产品二级路由页面
This commit is contained in:
@@ -116,7 +116,7 @@ function AdminMenuItem({
|
|||||||
role="button"
|
role="button"
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed transition-all duration-200 z-[9999]",
|
"fixed transition-all duration-200 z-9999",
|
||||||
isOpen
|
isOpen
|
||||||
? "opacity-100 visible translate-y-0"
|
? "opacity-100 visible translate-y-0"
|
||||||
: "opacity-0 invisible -translate-y-2 pointer-events-none",
|
: "opacity-0 invisible -translate-y-2 pointer-events-none",
|
||||||
@@ -129,7 +129,7 @@ function AdminMenuItem({
|
|||||||
onMouseEnter={handleMouseEnter}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={handleMouseLeave}
|
onMouseLeave={handleMouseLeave}
|
||||||
>
|
>
|
||||||
<div className="bg-white rounded-xl shadow-2xl border border-gray-100/80 p-4 w-[560px]">
|
<div className="bg-white rounded-xl shadow-2xl border border-gray-100/80 p-4 w-140">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
@@ -159,13 +159,13 @@ function ProductDropdown() {
|
|||||||
title="新HTTP/S5"
|
title="新HTTP/S5"
|
||||||
desc="IP可用时长从数小时到365天全覆盖"
|
desc="IP可用时长从数小时到365天全覆盖"
|
||||||
tag="稳定"
|
tag="稳定"
|
||||||
onClick={() => handleClick("/product?tab=long")}
|
onClick={() => window.open("https://lanhuip.com", "_blank")}
|
||||||
/>
|
/>
|
||||||
<ProductCard
|
<ProductCard
|
||||||
title="HTTP/S5"
|
title="HTTP/S5"
|
||||||
desc="海量全球住宅优质IP,4G/5G真实手机IP"
|
desc="海量全球住宅优质IP,4G/5G真实手机IP"
|
||||||
tag="全球"
|
tag="全球"
|
||||||
onClick={() => handleClick("/product?tab=global")}
|
onClick={() => handleClick("/product?tab=http")}
|
||||||
/>
|
/>
|
||||||
<ProductCard
|
<ProductCard
|
||||||
title="软路由成本价"
|
title="软路由成本价"
|
||||||
@@ -175,12 +175,7 @@ function ProductDropdown() {
|
|||||||
<ProductCard
|
<ProductCard
|
||||||
title="动态Vps"
|
title="动态Vps"
|
||||||
desc="IP独享,高带宽,灵活控制存活周期"
|
desc="IP独享,高带宽,灵活控制存活周期"
|
||||||
onClick={() => handleClick("/product?tab=exclusive")}
|
onClick={() => window.open("http://vps.juip.com", "_blank")}
|
||||||
/>
|
|
||||||
<ProductCard
|
|
||||||
title="静态独享IP"
|
|
||||||
desc="纯净IP,更符合跨境卖家需求的云主机"
|
|
||||||
onClick={() => handleClick("/product?tab=static")}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export function EditInfoDialog({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
<DialogContent className="sm:max-w-[480px] p-6">
|
<DialogContent className="sm:max-w-120 p-6">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle className="text-xl font-semibold text-center">
|
<DialogTitle className="text-xl font-semibold text-center">
|
||||||
编辑信息
|
编辑信息
|
||||||
|
|||||||
42
src/components/ui/radio-group.tsx
Normal file
42
src/components/ui/radio-group.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { RadioGroup as RadioGroupPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils/index"
|
||||||
|
|
||||||
|
function RadioGroup({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Root
|
||||||
|
data-slot="radio-group"
|
||||||
|
className={cn("grid w-full gap-2", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function RadioGroupItem({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<RadioGroupPrimitive.Item
|
||||||
|
data-slot="radio-group-item"
|
||||||
|
className={cn(
|
||||||
|
"group/radio-group-item peer relative flex aspect-square size-4 shrink-0 rounded-full border border-input outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 aria-invalid:aria-checked:border-primary dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:border-primary data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<RadioGroupPrimitive.Indicator
|
||||||
|
data-slot="radio-group-indicator"
|
||||||
|
className="flex size-4 items-center justify-center"
|
||||||
|
>
|
||||||
|
<span className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" />
|
||||||
|
</RadioGroupPrimitive.Indicator>
|
||||||
|
</RadioGroupPrimitive.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { RadioGroup, RadioGroupItem }
|
||||||
10
src/home/_assets/alipay.svg
Normal file
10
src/home/_assets/alipay.svg
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_98_223)">
|
||||||
|
<path d="M20.001 13.692V3.845C20.0005 2.82523 19.5951 1.8474 18.8739 1.12641C18.1527 0.405415 17.1748 0.000264944 16.155 0L3.845 0C2.82541 0.000529935 1.84772 0.405797 1.12676 1.12676C0.405797 1.84772 0.000529935 2.82541 0 3.845V16.155C0.000265092 17.1747 0.405447 18.1525 1.12647 18.8735C1.84749 19.5946 2.82532 19.9997 3.845 20H16.155C17.057 19.9994 17.9301 19.682 18.622 19.1034C19.3139 18.5248 19.7808 17.7216 19.941 16.834C18.921 16.392 14.501 14.484 12.198 13.384C10.446 15.507 8.61 16.781 5.844 16.781C3.078 16.781 1.231 15.077 1.453 12.991C1.599 11.623 2.538 9.386 6.615 9.769C8.765 9.971 9.748 10.372 11.501 10.951C11.954 10.119 12.331 9.204 12.617 8.231H4.845V7.461H8.691V6.077H4V5.23H8.69V3.235C8.69 3.235 8.732 2.923 9.077 2.923H11V5.23H16V6.078H11V7.46H15.079C14.7272 8.91647 14.1692 10.3152 13.422 11.614C14.607 12.044 20 13.692 20 13.692H20.001ZM5.538 15.461C2.615 15.461 2.153 13.616 2.308 12.845C2.461 12.077 3.308 11.075 4.933 11.075C6.8 11.075 8.473 11.553 10.481 12.531C9.071 14.367 7.338 15.461 5.538 15.461Z" fill="#00A5F1"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_98_223">
|
||||||
|
<rect width="20" height="20" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
4
src/home/_assets/balance.svg
Normal file
4
src/home/_assets/balance.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M9.99996 0C4.4771 0 0 4.47714 0 10C0 15.5229 4.47712 20 9.99996 20C15.5228 20 19.9999 15.5228 19.9999 10C19.9999 4.47716 15.5228 0 9.99996 0ZM7.05159 4.29727C7.78297 3.68016 9.40572 4.98294 9.97711 4.93723C10.5485 4.98294 12.1713 3.68016 12.9026 4.29727C13.634 4.91437 12.017 6.325 12.017 6.325H7.93688C7.93688 6.325 6.32021 4.91437 7.05159 4.29727ZM12.1713 7.05639C12.1713 7.25852 12.0077 7.42208 11.8056 7.42208H8.14866C7.9465 7.42208 7.78297 7.25852 7.78297 7.05639C7.78297 6.85425 7.9465 6.6907 8.14866 6.6907H11.8056C12.0077 6.6907 12.1713 6.85425 12.1713 7.05639ZM9.97711 15.8251C7.14941 15.8251 4.85744 16.0801 4.85744 13.6881C4.85744 11.9096 6.12522 9.10908 7.93688 7.78775H12.017C13.829 9.10911 15.0968 11.9096 15.0968 13.6881C15.0968 16.0801 12.8048 15.8251 9.97711 15.8251Z" fill="#FF6B00"/>
|
||||||
|
<path d="M10.0331 10.4641C10.1948 10.4641 10.321 10.5147 10.4125 10.6152C10.504 10.7158 10.5501 10.8605 10.5501 11.0487H11.5651C11.5651 10.7314 11.457 10.4759 11.2417 10.2807C11.0263 10.0854 10.7292 9.96605 10.3514 9.92196V9.375H9.82754V9.91174C9.43225 9.94399 9.11936 10.0511 8.88808 10.2334C8.65681 10.4157 8.54116 10.6486 8.54116 10.9315C8.54116 11.2434 8.6553 11.4833 8.88429 11.6511C9.11333 11.8189 9.46855 11.969 9.94998 12.1013C10.1714 12.1846 10.3249 12.268 10.4103 12.3519C10.4956 12.4358 10.5387 12.5498 10.5387 12.6939C10.5387 12.8349 10.4972 12.9473 10.4126 13.0322C10.3279 13.1167 10.1971 13.1592 10.0188 13.1592C9.82683 13.1592 9.6726 13.1113 9.55846 13.0167C9.44432 12.922 9.38689 12.7634 9.38689 12.5412H8.39678L8.38921 12.5541C8.37939 12.915 8.50335 13.1888 8.7611 13.3755C9.01882 13.5621 9.35064 13.6712 9.75495 13.7035V14.2043H10.2825V13.7009C10.6778 13.6686 10.9892 13.5653 11.2167 13.3894C11.4442 13.2136 11.5583 12.9801 11.5583 12.6887C11.5583 12.3783 11.4411 12.1352 11.2076 11.9588C10.974 11.7824 10.6234 11.6334 10.1563 11.5113C9.92274 11.4183 9.76476 11.3317 9.68312 11.2521C9.60152 11.1725 9.56073 11.0665 9.55995 10.9348C9.55995 10.7922 9.597 10.6781 9.6703 10.5927C9.74364 10.5072 9.86457 10.4641 10.0331 10.4641Z" fill="#FF6B00"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
16
src/home/_assets/wechat.svg
Normal file
16
src/home/_assets/wechat.svg
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#clip0_98_228)">
|
||||||
|
<path d="M3.6 0H16.4C18.8 0 20 1.2 20 3.6V16.4C20 18.8 18.8 20 16.4 20H3.6C1.2 20 0 18.8 0 16.4V3.6C0 1.2 1.2 0 3.6 0Z" fill="#28D846"/>
|
||||||
|
<path d="M12.9534 7.94922C10.4386 7.94922 8.39999 9.65422 8.39999 11.7574C8.39999 13.8606 10.4386 15.5656 12.9534 15.5656C13.5434 15.5658 14.1294 15.4683 14.6876 15.2772L15.9912 15.9716L15.8158 14.7192C16.3212 14.4045 16.7403 13.969 17.0355 13.452C17.3307 12.9349 17.4927 12.3526 17.5068 11.7574C17.5066 9.65422 15.468 7.94922 12.9534 7.94922Z" fill="white"/>
|
||||||
|
<path d="M7.89999 3.80078C10.576 3.80078 12.7884 5.44078 13.2938 7.61478C12.3402 7.53638 7.39379 8.06938 8.17939 12.9148C7.46839 12.9168 6.49939 12.8856 5.80539 12.6518L4.23059 13.4912L4.44259 11.9784C3.21699 11.1678 2.39999 9.86418 2.39999 8.40078C2.39999 5.86078 4.86239 3.80078 7.89999 3.80078Z" fill="white"/>
|
||||||
|
<path d="M5.39999 6.88016C5.39999 7.07111 5.47585 7.25425 5.61088 7.38927C5.7459 7.5243 5.92904 7.60016 6.11999 7.60016C6.31095 7.60016 6.49408 7.5243 6.62911 7.38927C6.76414 7.25425 6.83999 7.07111 6.83999 6.88016C6.83999 6.6892 6.76414 6.50607 6.62911 6.37104C6.49408 6.23601 6.31095 6.16016 6.11999 6.16016C5.92904 6.16016 5.7459 6.23601 5.61088 6.37104C5.47585 6.50607 5.39999 6.6892 5.39999 6.88016Z" fill="#28D846"/>
|
||||||
|
<path d="M9 6.88016C9 7.07111 9.07586 7.25425 9.21088 7.38927C9.34591 7.5243 9.52904 7.60016 9.72 7.60016C9.91096 7.60016 10.0941 7.5243 10.2291 7.38927C10.3641 7.25425 10.44 7.07111 10.44 6.88016C10.44 6.6892 10.3641 6.50607 10.2291 6.37104C10.0941 6.23601 9.91096 6.16016 9.72 6.16016C9.52904 6.16016 9.34591 6.23601 9.21088 6.37104C9.07586 6.50607 9 6.6892 9 6.88016Z" fill="#28D846"/>
|
||||||
|
<path d="M10.8 10.64C10.8 10.8097 10.8674 10.9725 10.9874 11.0925C11.1075 11.2126 11.2702 11.28 11.44 11.28C11.6097 11.28 11.7725 11.2126 11.8925 11.0925C12.0126 10.9725 12.08 10.8097 12.08 10.64C12.08 10.4703 12.0126 10.3075 11.8925 10.1875C11.7725 10.0674 11.6097 10 11.44 10C11.2702 10 11.1075 10.0674 10.9874 10.1875C10.8674 10.3075 10.8 10.4703 10.8 10.64Z" fill="#28D846"/>
|
||||||
|
<path d="M13.84 10.64C13.84 10.8097 13.9074 10.9725 14.0274 11.0925C14.1475 11.2126 14.3103 11.28 14.48 11.28C14.6497 11.28 14.8125 11.2126 14.9325 11.0925C15.0526 10.9725 15.12 10.8097 15.12 10.64C15.12 10.4703 15.0526 10.3075 14.9325 10.1875C14.8125 10.0674 14.6497 10 14.48 10C14.3103 10 14.1475 10.0674 14.0274 10.1875C13.9074 10.3075 13.84 10.4703 13.84 10.64Z" fill="#28D846"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="clip0_98_228">
|
||||||
|
<rect width="20" height="20" fill="white"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -251,28 +251,23 @@ const ProductDropdown = ({ onNavigate }: { onNavigate: () => void }) => {
|
|||||||
title="新HTTP/S5"
|
title="新HTTP/S5"
|
||||||
desc="IP可用时长从数小时到365天全覆盖"
|
desc="IP可用时长从数小时到365天全覆盖"
|
||||||
tag="稳定"
|
tag="稳定"
|
||||||
onClick={() => handleClick("/product?tab=long")}
|
onClick={() => window.open("https://lanhuip.com", "_blank")}
|
||||||
/>
|
/>
|
||||||
<ProductCard
|
<ProductCard
|
||||||
title="HTTP/S5"
|
title="HTTP/S5"
|
||||||
desc="海量全球住宅优质IP,4G/5G真实手机IP"
|
desc="海量全球住宅优质IP,4G/5G真实手机IP"
|
||||||
tag="全球"
|
tag="全球"
|
||||||
onClick={() => handleClick("/product?tab=global")}
|
onClick={() => handleClick("/product/http")}
|
||||||
/>
|
/>
|
||||||
<ProductCard
|
<ProductCard
|
||||||
title="软路由成本价"
|
title="软路由成本价"
|
||||||
desc="云端自动切换IP,更易于调用与开发"
|
desc="云端自动切换IP,更易于调用与开发"
|
||||||
onClick={() => handleClick("/product?tab=tunnel")}
|
onClick={() => handleClick("/product/routeros")}
|
||||||
/>
|
/>
|
||||||
<ProductCard
|
<ProductCard
|
||||||
title="动态Vps"
|
title="动态Vps"
|
||||||
desc="IP独享,高带宽,灵活控制存活周期"
|
desc="IP独享,高带宽,灵活控制存活周期"
|
||||||
onClick={() => handleClick("/product?tab=exclusive")}
|
onClick={() => window.open("http://vps.juip.com", "_blank")}
|
||||||
/>
|
|
||||||
<ProductCard
|
|
||||||
title="静态独享IP"
|
|
||||||
desc="纯净IP,更符合跨境卖家需求的云主机"
|
|
||||||
onClick={() => handleClick("/product?tab=static")}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -138,8 +138,63 @@ export default function HomePage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex mt-10 lg:mt-20 items-center justify-center">
|
<div className="flex flex-col mt-10 lg:mt-20">
|
||||||
<img src={s9} alt="图片" className="max-w-full h-auto" />
|
<div className="relative flex items-center justify-center">
|
||||||
|
<img src={s9} alt="图片" className="max-w-full h-auto" />
|
||||||
|
<div className="absolute left-0 top-0 w-[30%] h-full flex flex-col justify-between">
|
||||||
|
<div className="text-left pr-4 pt-0">
|
||||||
|
<h4 className="text-sm lg:text-base font-semibold text-gray-800 mb-1">
|
||||||
|
爬虫抓取
|
||||||
|
</h4>
|
||||||
|
<p className="text-[10px] lg:text-xs text-gray-500 leading-relaxed">
|
||||||
|
3000W高匿名IP,可以轻松抓取企业信息、分类信息、房地产信息、电商信息
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-left pr-4">
|
||||||
|
<h4 className="text-sm lg:text-base font-semibold text-gray-800 mb-1">
|
||||||
|
数据采集
|
||||||
|
</h4>
|
||||||
|
<p className="text-[10px] lg:text-xs text-gray-500 leading-relaxed">
|
||||||
|
用于大数据采集的多样化利用场景,快速采集SEO数据优化、金融理财、地域信息数据
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-left pr-4 pb-0">
|
||||||
|
<h4 className="text-sm lg:text-base font-semibold text-gray-800 mb-1">
|
||||||
|
营销推广
|
||||||
|
</h4>
|
||||||
|
<p className="text-[10px] lg:text-xs text-gray-500 leading-relaxed">
|
||||||
|
论坛发帖、问答推广、网上购物、投票点赞等,根据渠道操作要求,定制IP完成营销推广
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute right-0 top-0 w-[30%] h-full flex flex-col justify-between">
|
||||||
|
<div className="text-right pl-4 pt-0">
|
||||||
|
<h4 className="text-sm lg:text-base font-semibold text-gray-800 mb-1">
|
||||||
|
电子商务
|
||||||
|
</h4>
|
||||||
|
<p className="text-[10px] lg:text-xs text-gray-500 leading-relaxed">
|
||||||
|
可帮助用户切换网络,模拟多个独立账号,有助于用户参与限时促销活动,避免购物效率低下的困扰
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-right pl-4">
|
||||||
|
<h4 className="text-sm lg:text-base font-semibold text-gray-800 mb-1">
|
||||||
|
游戏工作室
|
||||||
|
</h4>
|
||||||
|
<p className="text-[10px] lg:text-xs text-gray-500 leading-relaxed">
|
||||||
|
保证IP稳定,不掉线,在有效时长内完成游戏试玩、升级、完成游戏工作室操作要求
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-right pl-4 pb-0">
|
||||||
|
<h4 className="text-sm lg:text-base font-semibold text-gray-800 mb-1">
|
||||||
|
自媒体运营
|
||||||
|
</h4>
|
||||||
|
<p className="text-[10px] lg:text-xs text-gray-500 leading-relaxed">
|
||||||
|
可帮助自媒体运营者维护账号独立性,为每个账号赋予独立IP,避免IP地址关联带来的负面影响
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col pt-12 lg:pt-24 ">
|
<div className="flex flex-col pt-12 lg:pt-24 ">
|
||||||
|
|||||||
817
src/home/product/http.tsx
Normal file
817
src/home/product/http.tsx
Normal file
@@ -0,0 +1,817 @@
|
|||||||
|
import { useState } from "react"
|
||||||
|
import { Toaster, toast } from "sonner"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
|
||||||
|
import Wrap from "@/components/wrap"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import alipay from "../_assets/alipay.svg"
|
||||||
|
import wechat from "../_assets/wechat.svg"
|
||||||
|
|
||||||
|
const TABS = [
|
||||||
|
{ id: "prepaid", label: "预储值" },
|
||||||
|
{ id: "unlimited", label: "短效无限量" },
|
||||||
|
{ id: "daily", label: "短效包天" },
|
||||||
|
{ id: "volume", label: "短效包量" },
|
||||||
|
{ id: "game", label: "长效游戏" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const PREPAID_PLANS = [
|
||||||
|
{ amount: 200, bonus: "5%", total: 210, tag: "送 ¥5%" },
|
||||||
|
{ amount: 500, bonus: "10%", total: 550, tag: "送 ¥10%" },
|
||||||
|
{ amount: 1000, bonus: "15%", total: 1150, tag: "送 ¥15%" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const PREPAID_PLANS_LARGE = [
|
||||||
|
{ amount: 2000, bonus: "20%", total: 2400, tag: "多送20%" },
|
||||||
|
{ amount: 5000, bonus: "25%", total: 6250, tag: "多送25%" },
|
||||||
|
{ amount: 10000, bonus: "30%", total: 13000, tag: "多送30%" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const DURATION_OPTIONS = [
|
||||||
|
{ value: "5", label: "1~5分钟" },
|
||||||
|
{ value: "25", label: "5~25分钟" },
|
||||||
|
{ value: "180", label: "25分钟~3小时" },
|
||||||
|
{ value: "360", label: "3小时~6小时" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const PERIOD_OPTIONS = [
|
||||||
|
{ value: "1", label: "按天" },
|
||||||
|
{ value: "7", label: "按周" },
|
||||||
|
{ value: "30", label: "按月" },
|
||||||
|
{ value: "90", label: "按季" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const IP_AMOUNT_OPTIONS = [
|
||||||
|
{ value: 5000, label: "每日最多使用 5,000 个IP" },
|
||||||
|
{ value: 10000, label: "每日最多使用 10,000 个IP" },
|
||||||
|
{ value: 30000, label: "每日最多使用 30,000 个IP" },
|
||||||
|
{ value: 50000, label: "每日最多使用 50,000 个IP" },
|
||||||
|
{ value: 100000, label: "每日最多使用 100,000 个IP" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const VOLUME_AMOUNTS = [1, 10, 20, 50, 80, 100, 200, 500, 1000]
|
||||||
|
|
||||||
|
const BANDWIDTH_OPTIONS = [
|
||||||
|
{ value: "1", label: "1M" },
|
||||||
|
{ value: "2", label: "2M" },
|
||||||
|
{ value: "5", label: "5M" },
|
||||||
|
{ value: "10", label: "10M" },
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function HttpPage() {
|
||||||
|
const [activeTab, setActiveTab] = useState("prepaid")
|
||||||
|
const [customAmount, setCustomAmount] = useState("")
|
||||||
|
const [customIpAmount, setCustomIpAmount] = useState("")
|
||||||
|
|
||||||
|
const [duration, setDuration] = useState(DURATION_OPTIONS[0].value)
|
||||||
|
const [period, setPeriod] = useState(PERIOD_OPTIONS[0].value)
|
||||||
|
const [periodCount, setPeriodCount] = useState(1)
|
||||||
|
const [whitelistCount, setWhitelistCount] = useState(1)
|
||||||
|
const [ipAmount, setIpAmount] = useState(5000)
|
||||||
|
const [volumeAmount, setVolumeAmount] = useState(1)
|
||||||
|
const [bandwidth, setBandwidth] = useState(BANDWIDTH_OPTIONS[0].value)
|
||||||
|
|
||||||
|
// 支付弹窗状态
|
||||||
|
const [payDialogOpen, setPayDialogOpen] = useState(false)
|
||||||
|
const [payMethod, setPayMethod] = useState("2")
|
||||||
|
const [isRecharge] = useState(true)
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
// 计算价格
|
||||||
|
const calculatePrice = () => {
|
||||||
|
// 根据不同的 tab 计算价格
|
||||||
|
let price = 0
|
||||||
|
switch (activeTab) {
|
||||||
|
case "prepaid":
|
||||||
|
price = Number(customAmount) || 0
|
||||||
|
break
|
||||||
|
case "unlimited":
|
||||||
|
// 简化计算
|
||||||
|
price = whitelistCount * periodCount * 0.5
|
||||||
|
break
|
||||||
|
case "daily":
|
||||||
|
price = ipAmount * periodCount * 0.01
|
||||||
|
break
|
||||||
|
case "volume":
|
||||||
|
price = volumeAmount || Number(customAmount) || 0
|
||||||
|
break
|
||||||
|
case "game":
|
||||||
|
price = periodCount * 5
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
price = 0
|
||||||
|
}
|
||||||
|
return Number(price.toFixed(2))
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalPrice = calculatePrice()
|
||||||
|
|
||||||
|
// 处理支付
|
||||||
|
const handlePay = () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
// 模拟支付请求
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsLoading(false)
|
||||||
|
setPayDialogOpen(false)
|
||||||
|
toast.success(`支付成功!支付金额:¥${totalPrice.toFixed(2)}`)
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开支付弹窗
|
||||||
|
const openPayDialog = () => {
|
||||||
|
if (totalPrice <= 0) {
|
||||||
|
toast.error("请输入有效的充值金额")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setPayDialogOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Toaster position="top-center" richColors />
|
||||||
|
<Wrap className="bg-white">
|
||||||
|
<section className="py-20">
|
||||||
|
<div className="text-center mb-6">
|
||||||
|
<h2 className="text-2xl font-bold text-gray-800">
|
||||||
|
<span className="block">
|
||||||
|
套餐多元,自主选择,城市覆盖广性价比高,服务态度都说好
|
||||||
|
</span>
|
||||||
|
<span className="block text-xl font-normal text-muted-foreground mt-2">
|
||||||
|
预存享超值优惠 海量IP池随心用
|
||||||
|
</span>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="max-w-6xl mx-auto px-4">
|
||||||
|
<ul className="flex gap-2 justify-center flex-wrap mb-6">
|
||||||
|
{TABS.map(tab => (
|
||||||
|
<li key={tab.id}>
|
||||||
|
<button
|
||||||
|
onClick={() => setActiveTab(tab.id)}
|
||||||
|
className={cn(
|
||||||
|
"px-6 py-2.5 rounded-md text-sm font-bold transition-all cursor-pointer",
|
||||||
|
activeTab === tab.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",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{tab.label}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{activeTab === "prepaid" && (
|
||||||
|
<div>
|
||||||
|
<h3 className="text-center text-lg font-bold text-gray-700 mb-2">
|
||||||
|
充的越多送的越多
|
||||||
|
</h3>
|
||||||
|
<p className="text-center mb-8">
|
||||||
|
<a
|
||||||
|
href="https://lanhuip.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 hover:text-blue-600 underline"
|
||||||
|
>
|
||||||
|
点击前往购买新http/socks5产品、IP池更大、网速更快
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6 mb-6 mt-8">
|
||||||
|
<PrepaidCardCustom
|
||||||
|
amount={customAmount}
|
||||||
|
onAmountChange={setCustomAmount}
|
||||||
|
onPay={openPayDialog}
|
||||||
|
/>
|
||||||
|
{PREPAID_PLANS.map(plan => (
|
||||||
|
<PrepaidCard
|
||||||
|
key={plan.amount}
|
||||||
|
plan={plan}
|
||||||
|
onPay={openPayDialog}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
{PREPAID_PLANS_LARGE.map(plan => (
|
||||||
|
<PrepaidCardLarge
|
||||||
|
key={plan.amount}
|
||||||
|
plan={plan}
|
||||||
|
onPay={openPayDialog}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "unlimited" && (
|
||||||
|
<div className="max-w-3xl mx-auto flex flex-col gap-3">
|
||||||
|
<p className="text-center text-sm mb-4">
|
||||||
|
适用于每天需要大量高匿IP的用户使用,支持HTTP(S)、S5协议,IP可通过API获得集成在程序或软件中使用
|
||||||
|
</p>
|
||||||
|
<p className="text-center">
|
||||||
|
<a
|
||||||
|
href="https://lanhuip.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 hover:text-blue-600 underline"
|
||||||
|
>
|
||||||
|
点击前往购买新http/socks5产品、IP池更大、网速更快
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<OptionBox label="有效时长">
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{DURATION_OPTIONS.map(opt => (
|
||||||
|
<OptionBtn
|
||||||
|
key={opt.value}
|
||||||
|
active={duration === opt.value}
|
||||||
|
onClick={() => setDuration(opt.value)}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</OptionBtn>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="套餐周期">
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{PERIOD_OPTIONS.map(opt => (
|
||||||
|
<OptionBtn
|
||||||
|
key={opt.value}
|
||||||
|
active={period === opt.value}
|
||||||
|
onClick={() => setPeriod(opt.value)}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</OptionBtn>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="购买时长">
|
||||||
|
<Stepper
|
||||||
|
value={periodCount}
|
||||||
|
unit="小时"
|
||||||
|
onChange={setPeriodCount}
|
||||||
|
/>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="白名单数量">
|
||||||
|
<div className="flex items-center gap-4 flex-wrap">
|
||||||
|
<Stepper
|
||||||
|
value={whitelistCount}
|
||||||
|
unit=""
|
||||||
|
onChange={v => setWhitelistCount(v)}
|
||||||
|
min={1}
|
||||||
|
max={1000}
|
||||||
|
/>
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
您的白名单上限{whitelistCount}个,
|
||||||
|
{whitelistCount === 1 ? (
|
||||||
|
<span className="text-red-500">
|
||||||
|
价格<span className="font-medium">无折扣</span>
|
||||||
|
</span>
|
||||||
|
) : whitelistCount <= 5 ? (
|
||||||
|
<span className="text-red-500 font-medium">
|
||||||
|
{11 - whitelistCount}折
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-red-500 font-medium">6折</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<PriceBar price={totalPrice} onPay={openPayDialog} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "daily" && (
|
||||||
|
<div className="max-w-3xl mx-auto flex flex-col gap-3">
|
||||||
|
<p className="text-center text-sm">
|
||||||
|
适用于每天需要大量高匿IP的用户使用,支持HTTP(S)、S5协议,IP可通过API获得集成在程序或软件中使用
|
||||||
|
</p>
|
||||||
|
<p className="text-center">
|
||||||
|
<a
|
||||||
|
href="https://lanhuip.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 hover:text-blue-600 underline"
|
||||||
|
>
|
||||||
|
点击前往购买新http/socks5产品、IP池更大、网速更快
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<OptionBox label="选择每日可使用IP数">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
||||||
|
{IP_AMOUNT_OPTIONS.map(opt => (
|
||||||
|
<OptionBtn
|
||||||
|
key={opt.value}
|
||||||
|
active={ipAmount === opt.value}
|
||||||
|
onClick={() => setIpAmount(opt.value)}
|
||||||
|
className="justify-center"
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</OptionBtn>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex items-center gap-3 flex-wrap">
|
||||||
|
<span className="inline-flex items-center gap-2 px-4 py-2 rounded-full border bg-white text-sm">
|
||||||
|
每日最多使用
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={customIpAmount}
|
||||||
|
onChange={e => setCustomIpAmount(e.target.value)}
|
||||||
|
placeholder="IP数"
|
||||||
|
className="w-28 h-7 inline-block text-center"
|
||||||
|
/>
|
||||||
|
个IP
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="有效时长">
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{DURATION_OPTIONS.map(opt => (
|
||||||
|
<OptionBtn
|
||||||
|
key={opt.value}
|
||||||
|
active={duration === opt.value}
|
||||||
|
onClick={() => setDuration(opt.value)}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</OptionBtn>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="套餐周期">
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{PERIOD_OPTIONS.slice(0, 3).map(opt => (
|
||||||
|
<OptionBtn
|
||||||
|
key={opt.value}
|
||||||
|
active={period === opt.value}
|
||||||
|
onClick={() => setPeriod(opt.value)}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</OptionBtn>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="购买时长">
|
||||||
|
<Stepper
|
||||||
|
value={periodCount}
|
||||||
|
unit="天"
|
||||||
|
onChange={setPeriodCount}
|
||||||
|
/>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<PriceBar price={totalPrice} onPay={openPayDialog} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "volume" && (
|
||||||
|
<div className="max-w-3xl mx-auto">
|
||||||
|
<h3 className="text-center text-lg font-bold text-gray-700">
|
||||||
|
提取免费,使用才扣费。节约成本,按使用次数扣费
|
||||||
|
</h3>
|
||||||
|
<p className="text-center p-3">
|
||||||
|
<a
|
||||||
|
href="https://lanhuip.com"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="text-blue-500 hover:text-blue-600 underline"
|
||||||
|
>
|
||||||
|
点击前往购买新http/socks5产品、IP池更大、网速更快
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<OptionBox label="充值金额">
|
||||||
|
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
|
||||||
|
{VOLUME_AMOUNTS.map(amount => (
|
||||||
|
<OptionBtn
|
||||||
|
key={amount}
|
||||||
|
active={volumeAmount === amount}
|
||||||
|
onClick={() => setVolumeAmount(amount)}
|
||||||
|
className="justify-center"
|
||||||
|
>
|
||||||
|
{amount}元
|
||||||
|
</OptionBtn>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-4 flex items-center gap-3 flex-wrap">
|
||||||
|
<span className="inline-flex items-center gap-2 px-4 py-2 rounded-full border bg-white text-sm">
|
||||||
|
自定义
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={customAmount}
|
||||||
|
onChange={e => setCustomAmount(e.target.value)}
|
||||||
|
placeholder="金额"
|
||||||
|
className="w-28 h-7 inline-block text-center"
|
||||||
|
/>
|
||||||
|
元
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="有效时长">
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{DURATION_OPTIONS.map(opt => (
|
||||||
|
<OptionBtn
|
||||||
|
key={opt.value}
|
||||||
|
active={duration === opt.value}
|
||||||
|
onClick={() => setDuration(opt.value)}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</OptionBtn>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="IP数量">
|
||||||
|
<div className="text-sm text-muted-foreground">
|
||||||
|
可使用{" "}
|
||||||
|
<span className="text-xl font-bold text-blue-500">
|
||||||
|
{Math.floor(
|
||||||
|
(volumeAmount || customAmount
|
||||||
|
? Number(customAmount || volumeAmount)
|
||||||
|
: 0) * 100,
|
||||||
|
)}
|
||||||
|
</span>{" "}
|
||||||
|
个IP,有效期1年,提取免费,使用才扣费。
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<PriceBar price={totalPrice} onPay={openPayDialog} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "game" && (
|
||||||
|
<div className="max-w-3xl mx-auto space-y-5">
|
||||||
|
<h3 className="text-center text-lg font-bold text-gray-700">
|
||||||
|
高匿 · 高质 · 长效 · 稳定 · HTTP · SOCKS5
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<OptionBox label="类型">
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<OptionBtn active>国内游戏</OptionBtn>
|
||||||
|
<OptionBtn>国际游戏</OptionBtn>
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="模式">
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<OptionBtn active>共享线路</OptionBtn>
|
||||||
|
<OptionBtn>独享游戏</OptionBtn>
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="运营商选择">
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<OptionBtn active>普通线路</OptionBtn>
|
||||||
|
<OptionBtn>电信</OptionBtn>
|
||||||
|
<OptionBtn>移动</OptionBtn>
|
||||||
|
<OptionBtn>联通</OptionBtn>
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="带宽">
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{BANDWIDTH_OPTIONS.map(opt => (
|
||||||
|
<OptionBtn
|
||||||
|
key={opt.value}
|
||||||
|
active={bandwidth === opt.value}
|
||||||
|
onClick={() => setBandwidth(opt.value)}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</OptionBtn>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="套餐周期">
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{PERIOD_OPTIONS.map(opt => (
|
||||||
|
<OptionBtn
|
||||||
|
key={opt.value}
|
||||||
|
active={period === opt.value}
|
||||||
|
onClick={() => setPeriod(opt.value)}
|
||||||
|
>
|
||||||
|
{opt.label}
|
||||||
|
</OptionBtn>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="IP数量">
|
||||||
|
<Stepper
|
||||||
|
value={periodCount}
|
||||||
|
unit="个"
|
||||||
|
onChange={setPeriodCount}
|
||||||
|
/>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<OptionBox label="购买时长">
|
||||||
|
<Stepper
|
||||||
|
value={whitelistCount}
|
||||||
|
unit="天"
|
||||||
|
onChange={setWhitelistCount}
|
||||||
|
/>
|
||||||
|
</OptionBox>
|
||||||
|
|
||||||
|
<PriceBar price={totalPrice} onPay={openPayDialog} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* 支付弹窗 */}
|
||||||
|
<Dialog open={payDialogOpen} onOpenChange={setPayDialogOpen}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="text-center">
|
||||||
|
待支付:¥{totalPrice.toFixed(2)}元
|
||||||
|
</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="py-4">
|
||||||
|
{isRecharge ? (
|
||||||
|
<>
|
||||||
|
<p className="text-sm text-gray-600 mb-4">选择支付方式:</p>
|
||||||
|
<RadioGroup
|
||||||
|
value={payMethod}
|
||||||
|
onValueChange={setPayMethod}
|
||||||
|
className="flex justify-center gap-8 py-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="2" id="alipay" />
|
||||||
|
<Label
|
||||||
|
htmlFor="alipay"
|
||||||
|
className="flex items-center gap-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
<img src={alipay} alt="支付宝" className="w-6 h-6" />
|
||||||
|
支付宝支付
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<RadioGroupItem value="3" id="wechat" />
|
||||||
|
<Label
|
||||||
|
htmlFor="wechat"
|
||||||
|
className="flex items-center gap-2 cursor-pointer"
|
||||||
|
>
|
||||||
|
<img src={wechat} alt="微信" className="w-6 h-6" />
|
||||||
|
微信支付
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-6">
|
||||||
|
<p className="text-gray-600">
|
||||||
|
个人剩余H币:{}
|
||||||
|
<a href="/" className="text-blue-500 hover:underline ml-2">
|
||||||
|
去充值
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p className="text-gray-600 mt-2">
|
||||||
|
本次支付H币:{totalPrice.toFixed(2)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
onClick={handlePay}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="w-full bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90 py-3"
|
||||||
|
>
|
||||||
|
{isLoading ? "支付中..." : "立即支付"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</Wrap>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PrepaidCardCustom({
|
||||||
|
amount,
|
||||||
|
onAmountChange,
|
||||||
|
onPay,
|
||||||
|
}: {
|
||||||
|
amount: string
|
||||||
|
onAmountChange: (v: string) => void
|
||||||
|
onPay: () => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Card className="relative text-center border-0 shadow-md overflow-visible">
|
||||||
|
<CardContent className="pt-6 space-y-4">
|
||||||
|
<div className="text-3xl font-bold">
|
||||||
|
¥
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={amount || "1"}
|
||||||
|
onChange={e => onAmountChange(e.target.value)}
|
||||||
|
className="inline-block w-24 h-auto text-2xl font-bold text-center border-0 border-b-2 border-blue-200 rounded-none focus-visible:ring-0 px-0 mx-1"
|
||||||
|
placeholder="金额"
|
||||||
|
min="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="py-3 px-4 bg-blue-50 rounded-lg">
|
||||||
|
<span className="text-blue-600 font-bold">
|
||||||
|
实到{amount || "1"}H币
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||||
|
<li>国内长效月卡低至9.9元</li>
|
||||||
|
<li>国际长效月卡低至10元</li>
|
||||||
|
<li>多充多送更超值</li>
|
||||||
|
<li>省时省力更方便</li>
|
||||||
|
<li>支持购买任意套餐</li>
|
||||||
|
</ul>
|
||||||
|
<Button
|
||||||
|
onClick={onPay}
|
||||||
|
className="w-full rounded-md bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90"
|
||||||
|
>
|
||||||
|
自定义充值
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
function PrepaidCard({
|
||||||
|
plan,
|
||||||
|
onPay,
|
||||||
|
}: {
|
||||||
|
plan: { amount: number; bonus: string; total: number; tag: string }
|
||||||
|
onPay: () => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Card className="relative text-center border-0 shadow-md overflow-visible">
|
||||||
|
<Badge className="absolute -top-3 right-4 bg-yellow-400 text-white hover:bg-yellow-400 border-0 text-xs px-3 py-0.5 z-10">
|
||||||
|
{plan.tag}
|
||||||
|
</Badge>
|
||||||
|
<CardContent className="pt-8 space-y-4">
|
||||||
|
<div className="text-3xl font-bold">¥{plan.amount}</div>
|
||||||
|
<div className="py-3 px-4 bg-blue-50 rounded-lg">
|
||||||
|
<span className="text-blue-600 font-bold">实到{plan.total}H币</span>
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-1 text-sm text-muted-foreground">
|
||||||
|
<li>国内长效月卡低至9.9元</li>
|
||||||
|
<li>国际长效月卡低至10元</li>
|
||||||
|
<li>多充多送更超值</li>
|
||||||
|
<li>省时省力更方便</li>
|
||||||
|
<li>支持购买任意套餐</li>
|
||||||
|
</ul>
|
||||||
|
<Button
|
||||||
|
onClick={onPay}
|
||||||
|
className="w-full rounded-md bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90"
|
||||||
|
>
|
||||||
|
立即充值
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PrepaidCardLarge({
|
||||||
|
plan,
|
||||||
|
onPay,
|
||||||
|
}: {
|
||||||
|
plan: { amount: number; bonus: string; total: number; tag: string }
|
||||||
|
onPay: () => void
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Card className="relative text-center border-0 shadow-md bg-white overflow-visible">
|
||||||
|
<Badge className="absolute -top-3 right-4 bg-yellow-400 text-white hover:bg-yellow-400 border-0 text-xs px-3 py-0.5 z-10">
|
||||||
|
{plan.tag}
|
||||||
|
</Badge>
|
||||||
|
<CardContent className="pt-8 pb-6 flex flex-col items-center gap-3">
|
||||||
|
<div className="text-3xl font-bold">¥{plan.amount}</div>
|
||||||
|
<p className="text-sm text-muted-foreground">实到{plan.total}H币</p>
|
||||||
|
<Button
|
||||||
|
onClick={onPay}
|
||||||
|
className="rounded-md bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90 px-10"
|
||||||
|
>
|
||||||
|
立即充值
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function OptionBox({
|
||||||
|
label,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
label: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
|
<div className="p-5">
|
||||||
|
<h5 className="text-sm font-medium text-gray-700 mb-4">{label}</h5>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function OptionBtn({
|
||||||
|
active = false,
|
||||||
|
onClick,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
active?: boolean
|
||||||
|
onClick?: () => void
|
||||||
|
className?: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant={active ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={onClick}
|
||||||
|
className={cn(
|
||||||
|
"rounded-md text-sm font-medium transition-all",
|
||||||
|
active &&
|
||||||
|
"bg-linear-to-r from-blue-500 to-cyan-400 text-white border-transparent shadow-sm hover:opacity-90",
|
||||||
|
!active &&
|
||||||
|
"border-gray-200 bg-white text-gray-600 hover:border-blue-300 hover:text-blue-500",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Stepper({
|
||||||
|
value,
|
||||||
|
unit,
|
||||||
|
onChange,
|
||||||
|
min = 1,
|
||||||
|
max,
|
||||||
|
}: {
|
||||||
|
value: number
|
||||||
|
unit: string
|
||||||
|
onChange: (v: number) => void
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<span className="inline-flex items-center rounded-md border overflow-hidden">
|
||||||
|
<button
|
||||||
|
onClick={() => onChange(Math.max(min, value - 1))}
|
||||||
|
disabled={value <= min}
|
||||||
|
className="px-3 py-2 bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90 disabled:opacity-40 transition-opacity cursor-pointer"
|
||||||
|
>
|
||||||
|
-
|
||||||
|
</button>
|
||||||
|
<span className="px-5 py-2 text-sm font-medium tabular-nums bg-white min-w-20 text-center">
|
||||||
|
{value}
|
||||||
|
{unit}
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
onClick={() => onChange(max ? Math.min(max, value + 1) : value + 1)}
|
||||||
|
disabled={max !== undefined && value >= max}
|
||||||
|
className="px-3 py-2 bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90 disabled:opacity-40 transition-opacity cursor-pointer"
|
||||||
|
>
|
||||||
|
+
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PriceBar({ price, onPay }: { price: number; onPay: () => void }) {
|
||||||
|
return (
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
|
||||||
|
<div className="p-5 flex items-center justify-between gap-4 flex-wrap">
|
||||||
|
<span className="text-sm font-medium text-gray-700">
|
||||||
|
价格:<span className="text-lg font-bold">¥{price.toFixed(2)}</span>
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
onClick={onPay}
|
||||||
|
size="lg"
|
||||||
|
className="rounded-md bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90"
|
||||||
|
>
|
||||||
|
实付 ¥{price.toFixed(2)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import PurchaseInfo from "./purchaseInfo"
|
|||||||
const TAB_ALIAS: Record<string, string> = {
|
const TAB_ALIAS: Record<string, string> = {
|
||||||
short: "dynamic",
|
short: "dynamic",
|
||||||
long: "dynamic",
|
long: "dynamic",
|
||||||
global: "dynamic",
|
http: "http",
|
||||||
tunnel: "dynamic",
|
tunnel: "dynamic",
|
||||||
exclusive: "static",
|
exclusive: "static",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,13 @@ export default function ProductGrid({ products }: ProductGridProps) {
|
|||||||
className="relative overflow-visible rounded-xl shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white p-6 flex flex-col min-h-60"
|
className="relative overflow-visible rounded-xl shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white p-6 flex flex-col min-h-60"
|
||||||
>
|
>
|
||||||
{product.tag && (
|
{product.tag && (
|
||||||
<div className="absolute -top-5 -right-5 flex items-center gap-1 text-xs font-bold px-3 py-1">
|
<div className="absolute -top-5 -right-5">
|
||||||
<img src={vector} alt="打折" />
|
<div className="relative">
|
||||||
{product.tag}
|
<img src={vector} alt="打折" />
|
||||||
|
<span className="absolute inset-0 flex items-center justify-center text-xs font-bold">
|
||||||
|
{product.tag}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
408
src/home/product/routeros.tsx
Normal file
408
src/home/product/routeros.tsx
Normal file
@@ -0,0 +1,408 @@
|
|||||||
|
import { useState } from "react"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent } from "@/components/ui/card"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
import Wrap from "@/components/wrap"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import alipay from "../_assets/alipay.svg"
|
||||||
|
import balance from "../_assets/balance.svg"
|
||||||
|
import wechat from "../_assets/wechat.svg"
|
||||||
|
|
||||||
|
const PRODUCT_VARIANTS = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: "Mikrotik百兆5G双频-1拖40",
|
||||||
|
describe: "千兆双频",
|
||||||
|
price: 299,
|
||||||
|
lineprice: 399,
|
||||||
|
src: "https://gd4.alicdn.com/imgextra/i2/2212500574675/O1CN015GmSuk1kPCJNLBMl9_!!2212500574675.jpg_400x400.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: "ros系统千兆5G双频版-1拖40",
|
||||||
|
describe: "千兆双频增强",
|
||||||
|
price: 399,
|
||||||
|
lineprice: 499,
|
||||||
|
src: "https://mp4.juip.com/%E8%B7%AF%E7%94%B1%E5%99%A8.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: "热销款-ros系统千兆5G双频版1拖30",
|
||||||
|
describe: "企业级性能",
|
||||||
|
price: 499,
|
||||||
|
lineprice: 599,
|
||||||
|
src: "https://gd1.alicdn.com/imgextra/i2/2212500574675/O1CN010VprRs1kPCJG5ks8l_!!2212500574675.jpg_400x400.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
name: "Mikrotik千兆5G双频版-1拖30",
|
||||||
|
describe: "五口千兆",
|
||||||
|
price: 599,
|
||||||
|
lineprice: 699,
|
||||||
|
src: "https://gd1.alicdn.com/imgextra/i2/2212500574675/O1CN010VprRs1kPCJG5ks8l_!!2212500574675.jpg_400x400.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5,
|
||||||
|
name: "千兆5G双频PRO版-1拖60",
|
||||||
|
describe: "五口千兆",
|
||||||
|
price: 599,
|
||||||
|
lineprice: 699,
|
||||||
|
src: "https://gd4.alicdn.com/imgextra/i4/2212500574675/O1CN01blV7JL1kPCJcWJgyY_!!2212500574675.jpg_400x400.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "千兆5G旗舰版-1拖100",
|
||||||
|
describe: "五口千兆",
|
||||||
|
price: 599,
|
||||||
|
lineprice: 699,
|
||||||
|
src: "https://gd1.alicdn.com/imgextra/i3/2212500574675/O1CN01DdOPmM1kPCJHFrtcS_!!2212500574675.jpg_400x400.jpg",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "全协议千兆双频软路由一拖100+AP",
|
||||||
|
describe: "五口千兆",
|
||||||
|
price: 599,
|
||||||
|
lineprice: 699,
|
||||||
|
src: "https://mp4.juip.com/%E5%85%A8%E5%8D%8F%E8%AE%AE%E5%8D%83%E5%85%86%E5%8F%8C%E9%A2%91%E8%BD%AF%E8%B7%AF%E7%94%B1%E4%B8%80%E6%8B%96100%2BAP%20.jpg",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const PAYMENT_METHODS = [
|
||||||
|
{ id: "alipay", label: "支付宝支付", icon: alipay },
|
||||||
|
{ id: "wechat", label: "微信支付", icon: wechat },
|
||||||
|
{ id: "balance", label: "余额支付", icon: balance },
|
||||||
|
]
|
||||||
|
|
||||||
|
const SPECS = [
|
||||||
|
[
|
||||||
|
{ label: "品牌", value: "Mikrotik" },
|
||||||
|
{ label: "售后服务", value: "店铺三包" },
|
||||||
|
{ label: "无线传输速率", value: "1000Mbps" },
|
||||||
|
{ label: "是否无线", value: "是" },
|
||||||
|
{ label: "适用场景", value: "中小企业/游戏路由" },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ label: "型号", value: "AC2" },
|
||||||
|
{ label: "USB接口", value: "1个" },
|
||||||
|
{ label: "无线速度", value: "1167M" },
|
||||||
|
{ label: "上市时间", value: "2012-08-22" },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{ label: "成色", value: "全新" },
|
||||||
|
{ label: "有线传输率", value: "千兆端口" },
|
||||||
|
{ label: "无线频率", value: "2.4G&5G" },
|
||||||
|
{ label: "版本类型", value: "中国大陆" },
|
||||||
|
{ label: "保修期", value: "1年" },
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function RouterosPage() {
|
||||||
|
const [selectedVariant, setSelectedVariant] = useState(0)
|
||||||
|
const [payMethod, setPayMethod] = useState("alipay")
|
||||||
|
const [userInfo, setUserInfo] = useState({
|
||||||
|
name: "",
|
||||||
|
phone: "",
|
||||||
|
address: "",
|
||||||
|
})
|
||||||
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
|
||||||
|
const product = PRODUCT_VARIANTS[selectedVariant]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Wrap className="flex flex-col gap-10 pt-30 pb-16 bg-white">
|
||||||
|
<div className="flex flex-col lg:flex-row gap-10">
|
||||||
|
<div className="lg:w-5/12">
|
||||||
|
<Card className="overflow-hidden border-0 shadow-md">
|
||||||
|
<img
|
||||||
|
src={product.src}
|
||||||
|
alt={product.name}
|
||||||
|
className="w-full aspect-square object-cover"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
<div className="flex gap-3 mt-4 flex-wrap">
|
||||||
|
{PRODUCT_VARIANTS.map((variant, index) => (
|
||||||
|
<button
|
||||||
|
key={variant.id}
|
||||||
|
onClick={() => setSelectedVariant(index)}
|
||||||
|
className={cn(
|
||||||
|
"size-16 rounded-lg cursor-pointer border-2 transition-all overflow-hidden p-0 bg-white",
|
||||||
|
index === selectedVariant
|
||||||
|
? "border-blue-500 shadow-md scale-105"
|
||||||
|
: "border-gray-200 hover:border-blue-300",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={variant.src}
|
||||||
|
alt={variant.name}
|
||||||
|
className="w-full h-full object-cover"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="lg:w-7/12 space-y-6">
|
||||||
|
<h4 className="text-2xl font-bold text-gray-800 leading-relaxed">
|
||||||
|
ros千兆软路由 一拖100矩阵 抖音快手单机单IP 魔硬软改 工作室试玩
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<Card className="bg-orange-50/60 border-0">
|
||||||
|
<CardContent className="space-y-3 pt-4">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">商品信息</span>
|
||||||
|
<span className="font-bold text-gray-700">{product.name}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">价格</span>
|
||||||
|
<span className="line-through text-gray-400">
|
||||||
|
¥{product.lineprice}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-muted-foreground text-sm">会员价</span>
|
||||||
|
<Badge
|
||||||
|
variant="destructive"
|
||||||
|
className="text-xl font-bold px-3 py-1 h-auto"
|
||||||
|
>
|
||||||
|
¥{product.price}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
<div className="text-right text-sm text-muted-foreground">
|
||||||
|
{product.describe}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label className="text-sm font-medium">颜色分类</Label>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{PRODUCT_VARIANTS.map((variant, index) => (
|
||||||
|
<Button
|
||||||
|
key={variant.id}
|
||||||
|
variant={index === selectedVariant ? "default" : "outline"}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setSelectedVariant(index)}
|
||||||
|
className={cn(
|
||||||
|
"rounded-full text-sm font-medium transition-all",
|
||||||
|
index === selectedVariant &&
|
||||||
|
"bg-linear-to-r from-blue-500 to-cyan-400 text-white border-transparent hover:opacity-90",
|
||||||
|
index !== selectedVariant &&
|
||||||
|
"border-gray-200 hover:border-blue-300",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{variant.name}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-4 text-sm">
|
||||||
|
<span className="text-muted-foreground w-20 shrink-0">
|
||||||
|
收货人
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-800 font-medium">
|
||||||
|
{userInfo.name || "未设置"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4 text-sm">
|
||||||
|
<span className="text-muted-foreground w-20 shrink-0">
|
||||||
|
电话号码
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-800 font-medium">
|
||||||
|
{userInfo.phone || "未设置"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4 text-sm">
|
||||||
|
<span className="text-muted-foreground w-20 shrink-0">
|
||||||
|
收货地址
|
||||||
|
</span>
|
||||||
|
<span className="text-gray-800 font-medium">
|
||||||
|
{userInfo.address || "未设置"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Label className="text-sm font-medium">支付方式</Label>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{PAYMENT_METHODS.map(method => (
|
||||||
|
<label
|
||||||
|
key={method.id}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2 px-4 py-2 rounded-full text-sm font-medium cursor-pointer border transition-all",
|
||||||
|
payMethod === method.id
|
||||||
|
? "bg-blue-50 border-blue-400 text-blue-600"
|
||||||
|
: "bg-white border-gray-200 text-muted-foreground hover:border-blue-300",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="payMethod"
|
||||||
|
value={method.id}
|
||||||
|
checked={payMethod === method.id}
|
||||||
|
onChange={() => setPayMethod(method.id)}
|
||||||
|
className="hidden"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={method.icon}
|
||||||
|
alt={method.label}
|
||||||
|
className="w-5 h-5"
|
||||||
|
/>
|
||||||
|
<span>{method.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-4">
|
||||||
|
<Button
|
||||||
|
size="lg"
|
||||||
|
className="flex-1 rounded-full bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90 font-bold"
|
||||||
|
>
|
||||||
|
立即购买
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
onClick={() => setShowModal(true)}
|
||||||
|
className="rounded-full"
|
||||||
|
>
|
||||||
|
修改收货信息
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-bold text-gray-800 mb-6 text-center">
|
||||||
|
产品规格
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||||
|
{SPECS.map((col, colIdx) => (
|
||||||
|
<Card key={colIdx} className="border-0 bg-muted/30">
|
||||||
|
<CardContent className="space-y-2 pt-4">
|
||||||
|
{col.map((spec, idx) => (
|
||||||
|
<div
|
||||||
|
key={idx}
|
||||||
|
className="flex justify-between text-sm py-1.5 border-b border-border/50 last:border-0"
|
||||||
|
>
|
||||||
|
<span className="text-muted-foreground">{spec.label}</span>
|
||||||
|
<span className="text-foreground font-medium">
|
||||||
|
{spec.value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-bold text-gray-800 mb-6 text-center">
|
||||||
|
产品展示
|
||||||
|
</h2>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
|
{[
|
||||||
|
"https://img.alicdn.com/imgextra/i4/2212500574675/O1CN01RYiT0X1kPCJFZqcIn_!!2212500574675.jpg",
|
||||||
|
"https://img.alicdn.com/imgextra/i2/2212500574675/O1CN01K3pAdS1kPCJFZr5Pv_!!2212500574675.jpg",
|
||||||
|
"https://img.alicdn.com/imgextra/i2/2212500574675/O1CN01fLjEYo1kPCJMaaMkj_!!2212500574675.jpg",
|
||||||
|
"https://img.alicdn.com/imgextra/i1/2212500574675/O1CN01TN50Rn1kPCJCp9gBl_!!2212500574675.jpg",
|
||||||
|
"https://img.alicdn.com/imgextra/i1/2212500574675/O1CN018Iy0Xg1kPCJJP5Zn5_!!2212500574675.jpg",
|
||||||
|
"https://img.alicdn.com/imgextra/i2/2212500574675/O1CN01fQQIT81kPCJLg5xrB_!!2212500574675.png",
|
||||||
|
"https://img.alicdn.com/imgextra/i3/2212500574675/O1CN01oAjpI01kPCJGyE0JZ_!!2212500574675.png",
|
||||||
|
"https://img.alicdn.com/imgextra/i3/2212500574675/O1CN01unvVq91kPCJFZq52D_!!2212500574675.png",
|
||||||
|
].map((src, index) => (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
className="overflow-hidden border-0 shadow-sm hover:shadow-md transition-shadow cursor-pointer"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt={`产品图${index + 1}`}
|
||||||
|
className="w-full aspect-square object-cover"
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog open={showModal} onOpenChange={setShowModal}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>修改收货信息</DialogTitle>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="name">收货人</Label>
|
||||||
|
<Input
|
||||||
|
id="name"
|
||||||
|
value={userInfo.name}
|
||||||
|
onChange={e =>
|
||||||
|
setUserInfo({ ...userInfo, name: e.target.value })
|
||||||
|
}
|
||||||
|
placeholder="请输入收货人"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="phone">电话</Label>
|
||||||
|
<Input
|
||||||
|
id="phone"
|
||||||
|
value={userInfo.phone}
|
||||||
|
onChange={e =>
|
||||||
|
setUserInfo({ ...userInfo, phone: e.target.value })
|
||||||
|
}
|
||||||
|
placeholder="请输入电话"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="address">地址</Label>
|
||||||
|
<Input
|
||||||
|
id="address"
|
||||||
|
value={userInfo.address}
|
||||||
|
onChange={e =>
|
||||||
|
setUserInfo({ ...userInfo, address: e.target.value })
|
||||||
|
}
|
||||||
|
placeholder="请输入地址"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setShowModal(false)}
|
||||||
|
className="rounded-full"
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => setShowModal(false)}
|
||||||
|
className="rounded-full bg-linear-to-r from-blue-500 to-cyan-400 text-white hover:opacity-90"
|
||||||
|
>
|
||||||
|
提交更改
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</Wrap>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -14,6 +14,8 @@ import IpLinePage from "@/home/ipLine"
|
|||||||
import LayoutPage from "@/home/layout"
|
import LayoutPage from "@/home/layout"
|
||||||
import LoginPage from "@/home/login"
|
import LoginPage from "@/home/login"
|
||||||
import ProductPage from "@/home/product"
|
import ProductPage from "@/home/product"
|
||||||
|
import HttpPage from "@/home/product/http"
|
||||||
|
import RouterosPage from "@/home/product/routeros"
|
||||||
import SoftDownloadPage from "@/home/softDownload"
|
import SoftDownloadPage from "@/home/softDownload"
|
||||||
export const router = createBrowserRouter([
|
export const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -22,6 +24,8 @@ export const router = createBrowserRouter([
|
|||||||
children: [
|
children: [
|
||||||
{ index: true, element: <HomePage /> },
|
{ index: true, element: <HomePage /> },
|
||||||
{ path: "product", element: <ProductPage /> },
|
{ path: "product", element: <ProductPage /> },
|
||||||
|
{ path: "product/http", element: <HttpPage /> },
|
||||||
|
{ path: "product/routeros", element: <RouterosPage /> },
|
||||||
{ path: "ipline", element: <IpLinePage /> },
|
{ path: "ipline", element: <IpLinePage /> },
|
||||||
{ path: "softdownload", element: <SoftDownloadPage /> },
|
{ path: "softdownload", element: <SoftDownloadPage /> },
|
||||||
{ path: "help", element: <HelpPage /> },
|
{ path: "help", element: <HelpPage /> },
|
||||||
|
|||||||
Reference in New Issue
Block a user