diff --git a/src/home/product/buyStatic.tsx b/src/home/product/buyStatic.tsx index d867b20..7fae06e 100644 --- a/src/home/product/buyStatic.tsx +++ b/src/home/product/buyStatic.tsx @@ -55,8 +55,7 @@ function randomDigits(length: number): string { return result } -// 静态产品 product_id 固定为 1;线路品质固定为优质尊享(等级 100) -const PRODUCT_ID = 1 +// 线路品质固定为优质尊享(等级 100) const LINE_LEVEL = "100" function ispLabel(isp: number): string { @@ -139,7 +138,13 @@ function provinceLetter(name: string): string { } type BuyStaticState = { - pool?: { id?: string; title?: string; packageId?: number } + pool?: { + id?: string + title?: string + packageId?: number + // static-拨号线路(静态):product_id=1、type=0;solid-水滴专线(固态):product_id=2、type=1 + productType?: "static" | "solid" + } } // ============ 独享类型切换 Tab ============ @@ -283,7 +288,13 @@ export default function BuyStaticPage() { const navigate = useNavigate() const state = (location.state ?? {}) as BuyStaticState - const poolTitle = state.pool?.title || "多拨池" + const poolTitle = state.pool?.title || "拨号线路(静态)" + + // 产品类型:static-拨号线路(静态) product_id=1/type=0;solid-水滴专线(固态) product_id=2/type=1 + const isSolid = state.pool?.productType === "solid" + const productId = isSolid ? 2 : 1 + const packageType: 1 | 2 = isSolid ? 2 : 1 + const orderType: 0 | 1 = isSolid ? 1 : 0 // 三个后端接口数据:游戏列表 / 套餐列表 / 线路列表 const [games, setGames] = useState([]) @@ -320,10 +331,12 @@ export default function BuyStaticPage() { const [wechatOpen, setWechatOpen] = useState(false) const [wechatPayUrl, setWechatPayUrl] = useState("") - // 账户余额(.NET /user/ApiIndex -> UserModel.RestAmount) + // 账户余额与剩余免费测试次数(.NET /user/ApiIndex -> UserModel) const [restAmount, setRestAmount] = useState(0) + const [restTest, setRestTest] = useState(null) - // 初始加载:游戏列表 + 静态套餐(type=1)+ 账户余额,并生成随机账号密码 + // 初始加载:游戏列表 + 套餐(静态type=1/固态type=2)+ 账户余额/测试次数, + // 并生成随机账号密码 useEffect(() => { setAccount(randomChars(2) + randomDigits(4)) setPwd(randomDigits(3)) @@ -332,18 +345,26 @@ export default function BuyStaticPage() { if (res.success) setGames(res.data) else toast.error(res.message) }) - fetchShuidiPackages(1).then(res => { + fetchShuidiPackages(packageType).then(res => { if (res.success) setPackages(res.data) else toast.error(res.message) }) fetchApiIndex().then(res => { if (!res.success || !res.data) return - const amount = res.data.UserModel?.RestAmount - if (typeof amount === "number" && Number.isFinite(amount)) { - setRestAmount(amount) + const user = res.data.UserModel + if (user) { + const amount = user.RestAmount + if (typeof amount === "number" && Number.isFinite(amount)) { + setRestAmount(amount) + } + const limit = user.TestCountLimit + const used = user.UseTestCount + if (typeof limit === "number" && typeof used === "number") { + setRestTest(Math.max(0, limit - used)) + } } }) - }, []) + }, [packageType]) // 线路列表:跟随独享类型/项目变化重新拉取 // 序列号防止快速切换时旧响应覆盖新数据 @@ -352,7 +373,7 @@ export default function BuyStaticPage() { const seq = ++regionSeqRef.current setRegionLoading(true) const res = await fetchShuidiRegions({ - product_id: PRODUCT_ID, + product_id: productId, is_unshar: type === "project" ? 0 : 1, game_id: type === "project" ? gameId : "", }) @@ -369,7 +390,7 @@ export default function BuyStaticPage() { toast.error(res.message) } setRegionLoading(false) - }, [type, gameId]) + }, [type, gameId, productId]) useEffect(() => { loadRegions() @@ -538,7 +559,20 @@ export default function BuyStaticPage() { setBandwidth(key) setPackageId("") const group = type === "project" ? packages?.game : packages?.line - setPackageInfoList(group?.[key] ?? []) + const list = group?.[key] ?? [] + // 免费测试次数用完时,测试套餐按 1 元计费 + if ( + key === "test" && + restTest !== null && + restTest < 1 && + list.length > 0 + ) { + setPackageInfoList( + list.map((item, i) => (i === 0 ? { ...item, Price: "1" } : item)), + ) + } else { + setPackageInfoList(list) + } } const handleDurationClick = (item: ShuidiPackage) => { @@ -558,7 +592,7 @@ export default function BuyStaticPage() { setBuyNum(Math.max(Math.floor(value), sum)) } - // 校验与 juipnet pay_page 一致;下单与 juipnet pay() 一致 + // 校验与 pay_page 一致;下单与 pay() 一致 const handlePay = async () => { if (surplusOutTotal < buyNum) { toast.error("节点数量不足!") @@ -587,10 +621,10 @@ export default function BuyStaticPage() { productInfo[key] = String(count) } - // 免费(价格0)时固定余额支付,与 juipnet pay_page 一致 + // 免费(价格0)时固定余额支付 const orderInfo: ShuidiOrderInfo = { pay_type: totalPrice <= 0 ? 10 : Number(payType), - type: 0, + type: orderType, package_id: packageId, game_id: type === "project" ? gameId : "", buy_num: buyNum, @@ -679,6 +713,26 @@ export default function BuyStaticPage() { )} + {/* 静态/固态说明 */} +
+ {isSolid ? ( +

+ 水滴专线(固态):IP永久固定不变、更稳定,上下行同速,项目/直播首选。 + + 线路切换单日限制为20次,如余量不足请联系客服上架。 + +

+ ) : ( +

+ 拨号线路(静态):运营商专线宽带、自营机房,高带宽出口,IP 3-7 + 天周期性变化。 + + 线路切换单日限制为20次,如余量不足请联系客服上架。 + +

+ )} +
+
{/* 项目选择(可搜索下拉) */}
@@ -933,7 +987,7 @@ export default function BuyStaticPage() { -
+
{bandwidthKeys.map(key => (
@@ -1073,7 +1134,7 @@ export default function BuyStaticPage() {
- {/* 微信扫码支付弹窗(与 juipnet staticBackdrop 一致) */} + {/* 微信扫码支付弹窗 */} diff --git a/src/home/product/index.tsx b/src/home/product/index.tsx index 2115d9f..89a6d83 100644 --- a/src/home/product/index.tsx +++ b/src/home/product/index.tsx @@ -132,7 +132,7 @@ export default function ProductPage() { const isShuidi = currentBrand?.id === "水滴独享IP" const handlePoolBuy = (pool: PoolModule) => { - if (pool.id !== "dial") return + if (!pool.canBuy) return navigate("/product/buy-static", { state: { pool } }) } diff --git a/src/home/product/shuidiPools.tsx b/src/home/product/shuidiPools.tsx index 66f0940..1147243 100644 --- a/src/home/product/shuidiPools.tsx +++ b/src/home/product/shuidiPools.tsx @@ -1,43 +1,52 @@ -import { Button } from "@/components/ui/button" export type PoolModule = { id: string - title: string - features: string[] + mainTitle: string + leftTitle: string + rightTitle: string + features: { + title: string + desc: string + }[] canBuy?: boolean + productType?: "static" | "solid" } const POOLS: PoolModule[] = [ - { - id: "house", - title: "纯住宅池", - features: [ - "分散式真实住宅IP", - "带宽充足,IP段优质分布广", - "适合对IP要求高的业务", - "IP3-7天周期性变化", - "线路切换每天20次", - ], - }, { id: "dial", - title: "多拨池", + mainTitle: "拨号线路(静态)", + leftTitle: "静态专线介绍", + rightTitle: "传统socks5", features: [ - "集中拨号住宅,带宽较高", - "IP段较多、稳定性较高", - "IP3-7天周期性变化", - "线路切换每天20次", + { title: "运营商专线宽带,自营机房", desc: "天翼云,各种云线路" }, + { title: "大出口流量,直播/项目首选", desc: "低带宽出口,共享严重,线路拥挤" }, + { title: "全新vpn隧道,安全可靠", desc: "传统协议,波动大,延迟大" }, + { title: "全局代理不易发现", desc: "系统局部代理,易发现,易封号" }, + { title: "IP周期性变化", desc: "-" }, + { title: "精选超20000条不同线路", desc: "容量一般只有2-3000线路" }, + { title: "Ros/爱快/路由器/手机/电脑", desc: "电脑/手机" }, + { title: "华为级维护专员7*24小时服务", desc: "售后困难,处理时间漫长" }, ], canBuy: true, + productType: "static", }, { id: "fixed", - title: "固定池", + mainTitle: "水滴专线(固态)", + leftTitle: "固态专线介绍", + rightTitle: "传统socks5", features: [ - "天翼云节点", - "IP长久有效IP不变化,稳定性高", - "线路切换单日限制为20次", + { title: "运营商专线宽带,更稳定", desc: "天翼云,各种云线路" }, + { title: "全新vpn隧道,安全可靠", desc: "传统socks5协议,波动大,延迟大" }, + { title: "全局代理,为项目而生", desc: "系统局部代理,易发现,易封号" }, + { title: "IP永久固定不变", desc: "-" }, + { title: "精选超10000条不同线路", desc: "容量一般只有2-3000线路" }, + { title: "Ros/爱快/路由器/手机/电脑", desc: "电脑/手机" }, + { title: "华为级维护专员7*24小时服务", desc: "售后困难,处理时间漫长" }, ], + canBuy: true, + productType: "solid", }, ] @@ -47,39 +56,72 @@ type ShuidiPoolsProps = { export default function ShuidiPools({ onBuy }: ShuidiPoolsProps) { return ( -
+
{POOLS.map(pool => (
-
- {pool.title} +
+
+ {pool.mainTitle} +
-
- {pool.features.map((feature, index) => ( -
- {feature} +
+
+
+ {pool.leftTitle}
- ))} +
+ {pool.features.map((feature, index) => ( +
+
+ {feature.title} +
+
+ ✓ +
+
+ ))} +
+
+ +
+
+ {pool.rightTitle} +
+
+ {pool.features.map((feature, index) => ( +
+
+ ✗ +
+
+ {feature.desc} +
+
+ ))} +
+
+
-
- -
+
))}
) -} +} \ No newline at end of file