Initial commit from Create vite

This commit is contained in:
Eamon
2026-07-24 18:00:22 +08:00
commit 0361853732
79 changed files with 4803 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

BIN
src/home/_assets/s1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
src/home/_assets/s10.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
src/home/_assets/s11.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

BIN
src/home/_assets/s12.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
src/home/_assets/s2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
src/home/_assets/s3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
src/home/_assets/s4.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
src/home/_assets/s5.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
src/home/_assets/s6.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
src/home/_assets/s7.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
src/home/_assets/s8.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
src/home/_assets/s9.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 840 B

View File

@@ -0,0 +1,137 @@
import { useState } from "react"
import gzh from "../assets/gzh.webp"
import phone from "../assets/phone.webp"
import qq from "../assets/qq.webp"
import top from "../assets/top.webp"
import wx from "../assets/wx.webp"
export default function Common() {
const [activePopup, setActivePopup] = useState<"gzh" | "tel" | null>(null)
// QQ点击跳转
const handleQQClick = () => {
window.location.href =
"https://admin.qidian.qq.com/template/blue/mp/menu/qr-code-jump.html?linkType=0&env=ol&kfuin=2852138148&fid=299&key=f377ec024ca45115a03a7632c7bda230&cate=1&source=&isLBS=&isCustomEntry=&type=16&ftype=1&_type=wpa&qidian=true"
}
// 微信点击跳转
const handleWxClick = () => {
window.open("https://work.weixin.qq.com/kfid/kfc458bc58e79e5093f", "_blank")
}
return (
<div className="fixed right-4 bottom-20 z-50 flex flex-col w-18 overflow-visible rounded-xl shadow-lg bg-linear-to-b from-blue-700 to-cyan-300">
{/* QQ */}
<div
onClick={handleQQClick}
onKeyDown={e => {
if (e.key === "Enter" || e.key === " ") {
handleQQClick()
}
}}
role="button"
tabIndex={0}
className="flex flex-col items-center justify-center py-2 text-white border-b border-white/15 hover:bg-white/10 transition-colors cursor-pointer"
>
<img src={qq} alt="QQ" className="w-7 h-7 object-contain" />
<span className="text-xs mt-1.5">QQ</span>
</div>
{/* 微信 */}
<div
onClick={handleWxClick}
onKeyDown={e => {
if (e.key === "Enter" || e.key === " ") {
handleWxClick()
}
}}
role="button"
tabIndex={0}
className="flex flex-col items-center justify-center py-2 text-white border-b border-white/15 hover:bg-white/10 transition-colors cursor-pointer"
>
<img src={wx} alt="微信" className="w-7 h-7 object-contain" />
<span className="text-xs mt-1.5"></span>
</div>
{/* 公众号 */}
<div
className="relative flex flex-col items-center justify-center py-2 text-white border-b border-white/15 hover:bg-white/10 transition-colors cursor-pointer"
role="button"
tabIndex={0}
aria-haspopup="true"
aria-expanded={activePopup === "gzh"}
onMouseEnter={() => setActivePopup("gzh")}
onMouseLeave={() => setActivePopup(null)}
onFocus={() => setActivePopup("gzh")}
onBlur={() => setActivePopup(null)}
onKeyDown={e => {
if (e.key === "Enter" || e.key === " ") {
// toggle popup with keyboard
setActivePopup(prev => (prev === "gzh" ? null : "gzh"))
}
}}
>
<img src={gzh} alt="公众号" className="w-7 h-7 object-contain" />
<span className="text-xs mt-1.5"></span>
{/* 公众号悬浮弹窗 */}
{activePopup === "gzh" && (
<div className="absolute right-full mr-3 bottom-0 bg-white rounded-lg shadow-xl p-4 w-48 text-center border border-gray-100">
<img
src="/img/ewm.png"
alt="公众号二维码"
className="w-full aspect-square object-contain"
/>
<p className="text-orange-500 text-xs mt-2">
线 8:30-23:00
</p>
<p className="text-orange-500 text-xs">
</p>
</div>
)}
</div>
{/* 电话 */}
<div
className="relative flex flex-col items-center justify-center py-2 text-white border-b border-white/15 hover:bg-white/10 transition-colors cursor-pointer"
role="button"
tabIndex={0}
aria-haspopup="true"
aria-expanded={activePopup === "tel"}
onMouseEnter={() => setActivePopup("tel")}
onMouseLeave={() => setActivePopup(null)}
onFocus={() => setActivePopup("tel")}
onBlur={() => setActivePopup(null)}
onKeyDown={e => {
if (e.key === "Enter" || e.key === " ") {
setActivePopup(prev => (prev === "tel" ? null : "tel"))
}
}}
>
<img src={phone} alt="电话" className="w-7 h-7 object-contain" />
<span className="text-xs mt-1.5"></span>
{/* 电话悬浮弹窗 */}
{activePopup === "tel" && (
<div className="absolute right-full mr-3 bottom-0 bg-white rounded-lg shadow-xl p-4 w-48 text-center border border-gray-100">
<p className="font-bold text-lg text-gray-800">400 800 9925</p>
<p className="text-sm text-gray-600"></p>
<p className="text-xs text-gray-500 mt-1">
8:30-23:00
</p>
</div>
)}
</div>
{/* 回到顶部 */}
<button
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
className="flex flex-col items-center justify-center py-2 text-white hover:bg-white/10 transition-colors"
>
<img src={top} alt="回到顶部" className="w-7 h-7 object-contain" />
<span className="text-xs mt-1.5"></span>
</button>
</div>
)
}

View File

@@ -0,0 +1,16 @@
import { NavLink } from "react-router-dom"
export default function MenuClient() {
return (
<NavLink
to="/client"
className={({ isActive }) =>
isActive
? "text-blue-500 border-b-2 border-blue-500 py-1"
: "text-slate-700 py-1"
}
>
</NavLink>
)
}

View File

@@ -0,0 +1,16 @@
import { NavLink } from "react-router-dom"
export default function MenuDownload() {
return (
<NavLink
to="/soft-download"
className={({ isActive }) =>
isActive
? "text-blue-500 border-b-2 border-blue-500 py-1"
: "text-slate-700 py-1"
}
>
</NavLink>
)
}

View File

@@ -0,0 +1,16 @@
import { NavLink } from "react-router-dom"
export default function MenuHelp() {
return (
<NavLink
to="/help"
className={({ isActive }) =>
isActive
? "text-blue-500 border-b-2 border-blue-500 py-1"
: "text-slate-700 py-1"
}
>
/
</NavLink>
)
}

View File

@@ -0,0 +1,16 @@
import { NavLink } from "react-router-dom"
export default function MenuIp() {
return (
<NavLink
to="/ip-line"
className={({ isActive }) =>
isActive
? "text-blue-500 border-b-2 border-blue-500 py-1"
: "text-slate-700 py-1"
}
>
IP线路表
</NavLink>
)
}

View File

@@ -0,0 +1,16 @@
import { NavLink } from "react-router-dom"
export default function MenuProduct() {
return (
<NavLink
to="/product"
className={({ isActive }) =>
isActive
? "text-blue-500 border-b-2 border-blue-500 py-1"
: "text-slate-700 py-1"
}
>
</NavLink>
)
}

View File

@@ -0,0 +1,9 @@
import Wrap from "@/components/wrap"
export default function ClientPage() {
return (
<Wrap className="flex flex-col gap-16 mt-20">
<h1></h1>
</Wrap>
)
}

132
src/home/foot.tsx Normal file
View File

@@ -0,0 +1,132 @@
import Wrap from "@/components/wrap"
export default function Foot() {
return (
<footer className="bg-gray-900 text-white z-20 overflow-hidden">
<Wrap className="flex flex-col p-4 pt-12">
<div className="flex-auto overflow-hidden flex flex-wrap justify-between">
<div className="flex flex-col lg:items-center gap-2 max-lg:w-1/2">
<img
src="/img/qrcode.jpg"
alt="logo"
width={80}
height={80}
className="flex-none size-20 sm:size-44 bg-gray-100"
/>
<span className="text-sm text-gray-400"></span>
</div>
<div className="flex flex-col gap-2 lg:gap-4 max-lg:w-1/2">
<h3></h3>
<p className="text-sm text-gray-400">/177 9666 8888</p>
<p className="text-sm text-gray-400">QQ 70177252</p>
<h3 className="hidden sm:block"></h3>
<a
href="https://work.weixin.qq.com/kfid/kfc458bc58e79e5093f"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-gray-400 hidden sm:block cursor-pointer hover:text-white transition-colors"
>
</a>
<a
href="https://work.weixin.qq.com/kfid/kfc458bc58e79e5093f"
target="_blank"
rel="noopener noreferrer"
className="text-sm text-gray-400 hidden sm:block cursor-pointer hover:text-white transition-colors"
>
</a>
</div>
<SiteNavList
title="站点导航"
items={[
{ name: `产品订购`, href: `/product` },
{ name: `获取代理`, href: `/collect` },
{ name: `帮助中心`, href: `/docs/faq-general` },
{ name: `企业服务`, href: `/custom` },
]}
/>
<SiteNavList
title="国内代理"
items={[
{ name: `短效代理`, href: `/product?type=short` },
{ name: `长效代理`, href: `/product?type=long` },
{ name: `固定IP代理`, href: `/product?type=fixed` },
]}
/>
<SiteNavList
title="使用案例"
items={[
{ name: `数据采集`, href: `/data-capture` },
{ name: `电商运营`, href: `/e-commerce` },
{ name: `市场调研`, href: `/market-research` },
{ name: `SEO优化`, href: `/seo-optimization` },
{ name: `社交媒体`, href: `/social-media` },
{ name: `广告投放`, href: `/advertising` },
{ name: `账号管理`, href: `/account-management` },
{ name: `网络测试`, href: `/network-testing` },
]}
/>
<SiteNavList
title="帮助文档"
items={[
{ name: `产品功能`, href: `/docs/product-overview` },
{ name: `使用教程`, href: `/docs/browser-proxy` },
{ name: `行业资讯`, href: `/docs/news-latest` },
]}
/>
</div>
<div className="flex-none mt-6 pt-6 border-t border-gray-700 flex text-center flex-col text-gray-300">
<p className="text-xs">
IP服务使IP从事的任何行为均不代表蓝狐代理IP的意志和观点
<br />
使
</p>
<p className={`text-xs mt-3 `}>
|
<a
href="https://beian.miit.gov.cn/#/Integrated/index"
target="_blank"
rel="noopener noreferrer"
className="hover:underline"
>
ICP备17004061号-17
</a>
| B1-20190663
</p>
</div>
</Wrap>
</footer>
)
}
function SiteNavList(props: {
title: string
items: {
name: string
href: string
}[]
}) {
return (
<div className="max-lg:mt-4 w-full lg:w-auto">
<h3>{props.title}</h3>
<ul
className={[
`mt-1 flex flex-wrap gap-2 `,
`lg:mt-4 lg:flex-col lg:gap-4 lg:gap-x-6`,
].join(" ")}
>
{props.items.map((item, index) => (
<li key={index}>
<a href={item.href} className="text-sm text-gray-400">
{item.name}
</a>
</li>
))}
</ul>
</div>
)
}

91
src/home/header.tsx Normal file
View File

@@ -0,0 +1,91 @@
import { useSyncExternalStore } from "react"
import { NavLink } from "react-router-dom"
import Wrap from "@/components/wrap"
import { cn } from "@/lib/utils"
export default function Header() {
const scroll = useSyncExternalStore(
callback => {
window.addEventListener("scroll", callback)
return () => {
window.removeEventListener("scroll", callback)
}
},
() => window.scrollY > 48,
() => false,
)
return (
<header className={cn("fixed top-0 left-0 w-screen z-10 flex flex-col ")}>
<div
className={cn(
`flex-none pointer-events-auto`,
`transition-[background,shadow] duration-200 ease-in-out`,
scroll
? `bg-[#fffe] backdrop-blur-sm shadow-lg`
: `bg-transparent shadow-none`,
)}
>
<Wrap className="h-20 max-md:h-16 flex justify-between pl-15 pr-15">
<nav className="flex items-center justify-between gap-4 lg:gap-8">
<ul className="h-full items-stretch hidden lg:flex">
<NavItem to="/"></NavItem>
<NavItem to="/product"></NavItem>
<NavItem to="/ipline">IP线路表</NavItem>
<NavItem to="/softDownload"></NavItem>
<NavItem to="/help">/</NavItem>
<NavItem to="/client"></NavItem>
</ul>
</nav>
<ProfileOrLogin />
</Wrap>
</div>
</header>
)
}
const NavItem = ({
to,
children,
}: {
to: string
children: React.ReactNode
}) => (
<NavLink
to={to}
className={({ isActive }) =>
`h-full flex items-center relative px-3 ${
isActive
? "text-blue-500 after:absolute after:bottom-0 after:left-0 after:right-0 after:h-0.5 after:bg-blue-500"
: "text-slate-700"
}`
}
>
{children}
</NavLink>
)
function ProfileOrLogin() {
return (
<div className="flex items-center">
<NavLink
to="/login"
state={{ tab: "login" }}
className="w-24 h-12 flex items-center justify-center lg:text-lg"
>
<span></span>
</NavLink>
<NavLink
to="/login"
state={{ tab: "register" }}
className={[
`w-20 lg:w-24 h-10 lg:h-12 bg-linear-to-r rounded-full flex items-center justify-center lg:text-lg text-white`,
`transition-colors duration-200 ease-in-out`,
`from-blue-500 to-cyan-400 hover:from-blue-500 hover:to-cyan-300`,
].join(" ")}
>
<span></span>
</NavLink>
</div>
)
}

9
src/home/help/index.tsx Normal file
View File

@@ -0,0 +1,9 @@
import Wrap from "@/components/wrap"
export default function HelpPage() {
return (
<Wrap className="flex flex-col gap-16 mt-20">
<h1></h1>
</Wrap>
)
}

310
src/home/index.tsx Normal file
View File

@@ -0,0 +1,310 @@
import type { ReactNode } from "react"
import Wrap from "@/components/wrap"
import banner from "./_assets/banner.webp"
import s1 from "./_assets/s1.webp"
import s2 from "./_assets/s2.webp"
import s3 from "./_assets/s3.webp"
import s4 from "./_assets/s4.webp"
import s5 from "./_assets/s5.webp"
import s6 from "./_assets/s6.webp"
import s7 from "./_assets/s7.webp"
import s8 from "./_assets/s8.webp"
import s9 from "./_assets/s9.webp"
import s10 from "./_assets/s10.webp"
import s11 from "./_assets/s11.webp"
import s12 from "./_assets/s12.webp"
import Common from "./_components/header/common"
export default function HomePage() {
return (
<>
<Common />
<section className="w-full relative">
<div
className="absolute inset-0 z-0"
style={{
backgroundImage: `url(${banner})`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
}}
/>
<Wrap className="relative pt-64 pb-48 max-md:pt-32 max-md:pb-24">
<h1 className="text-4xl md:text-5xl font-bold text-center md:text-left">
IP产品IP
</h1>
<p className="mt-6 text-gray-600 text-center md:text-left">
<span className="lg:text-lg">
500+2000亿IP
</span>
</p>
<div className="mt-8 max-w-3xl">
<p className="text-gray-600 text-center md:text-left">
<span className="lg:text-base">
IP
</span>
</p>
</div>
<div className="mt-12 flex justify-center md:justify-start">
<button
type="button"
className="w-36 h-14 rounded-full shadow-lg bg-linear-to-r from-blue-500 to-cyan-400 text-white text-xl font-medium hover:opacity-90 transition-opacity cursor-pointer"
>
</button>
</div>
</Wrap>
</section>
<section className="w-full relative shadow-blue-50 bg-white">
<Wrap className="relative pt-32 pb-48 max-md:pt-32 max-md:pb-24">
<PageSection title="为什么我们是你的最佳选择">
<p className="text-gray-600 text-center text-sm">
IP资源
</p>
<ul className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-6 pt-16">
<ProductTypeCard icon={s1} title="全网产品最多IP库最大" />
<ProductTypeCard icon={s2} title="支持全设备及协议" />
<ProductTypeCard icon={s3} title="用户独享宽带" />
<ProductTypeCard icon={s4} title="价低质优,免费测试" />
<ProductTypeCard icon={s5} title="专属化产品定制" />
<ProductTypeCard icon={s6} title="专业的服务团队" />
</ul>
<div className="flex justify-between items-center gap-10 flex-wrap mt-16 p-20 shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white">
<div className="flex flex-col">
<h3 className="text-2xl font-bold mb-4">
IP库最大
</h3>
<p className="text-gray-600 w-150 text-sm">
IP产品
</p>
</div>
<div className="flex">
<img src={s7} alt="产品" className="w-20 h-20 rounded-lg" />
</div>
</div>
<div className="flex flex-col pt-24 ">
<h2 className="text-center text-3xl font-bold mb-8 lg:mb-24">
IP代理资源
</h2>
<p className="text-gray-600 text-center text-sm">
</p>
</div>
<div className="pt-20">
<ul className="shadow-[0_0_20px_4px] shadow-blue-50 p-8 flex max-lg:flex-col">
<li className="flex-1 flex flex-col items-center justify-center max-lg:mb-4 ">
<p className="mt-9 max-lg:mt-2 text-3xl bg-linear-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">
10+
</p>
<p className="text-sm"></p>
</li>
<li className="flex-1 flex flex-col items-center justify-center max-lg:mb-4">
<p className="mt-9 max-lg:mt-2 text-3xl bg-linear-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">
100+
</p>
<p className="text-sm">IP池</p>
</li>
<li className="flex-1 flex flex-col items-center justify-center max-lg:mb-4">
<p className="mt-9 max-lg:mt-2 text-3xl bg-linear-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">
10+
</p>
<p className="text-sm">线IP数</p>
</li>
<li className="flex-1 flex flex-col items-center justify-center">
<p className="mt-9 max-lg:mt-2 text-3xl bg-linear-to-t from-blue-500 to-cyan-400 bg-clip-text text-transparent font-bold pb-2 -mb-2">
99%
</p>
<p className="text-sm">IP可用率</p>
</li>
</ul>
</div>
<div className="flex flex-col pt-24 ">
<h2 className="text-center text-3xl font-bold mb-8 lg:mb-24">
</h2>
<p className="text-gray-600 text-center text-sm">
</p>
</div>
<div className="flex mt-20 items-center justify-center">
<img src={s9} alt="图片" />
</div>
<div className="flex flex-col pt-24 ">
<h2 className="text-center text-3xl font-bold mb-8 lg:mb-24">
</h2>
<p className="text-gray-600 text-center text-sm">
IP资源
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-12 pt-20">
<div className="flex flex-col shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white p-8">
<div className="flex w-full mb-5 bg-blue-50 items-center justify-center">
<img src={s10} alt="售前咨询" className="p-6" />
</div>
<h2 className="text-3xl font-bold mb-8 lg:mb-24"></h2>
<p className="text-xs text-gray-600">
线
</p>
</div>
<div className="flex flex-col shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white p-8">
<div className="flex w-full mb-5 bg-blue-50 items-center justify-center">
<img src={s11} alt="售中服务" className="p-6" />
</div>
<h2 className="text-3xl font-bold mb-8 lg:mb-24"></h2>
<p className="text-xs text-gray-600">
100+IP代理资源免费测试使
</p>
</div>
<div className="flex flex-col shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white p-8">
<div className="flex w-full mb-5 bg-blue-50 items-center justify-center">
<img src={s12} alt="售后保障" className="p-6" />
</div>
<h2 className="text-3xl font-bold mb-8 lg:mb-24"></h2>
<p className="text-xs text-gray-600">
1V1专属售后答疑7*24线
</p>
</div>
</div>
<div className="flex flex-col pt-24 ">
<h2 className="text-center text-3xl font-bold mb-8 lg:mb-24">
IP的产品
</h2>
<p className="text-gray-600 text-center text-sm">
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 pt-30">
<div className="flex flex-col shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white p-8">
<div className="mb-3">
<h4 className="text-xl font-semibold mb-2 group-hover:text-blue-600 transition-colors">
IP资源
</h4>
<p className="text-gray-500 text-xs leading-relaxed">
100+IP资源线IP
</p>
</div>
<div className="mb-3">
<h4 className="text-xl font-semibold mb-2 group-hover:text-blue-600 transition-colors">
</h4>
<p className="text-gray-500 text-xs leading-relaxed">
访
</p>
</div>
<div className="mb-2">
<h4 className="text-xl font-semibold mb-2 group-hover:text-blue-600 transition-colors">
</h4>
<p className="text-gray-500 text-xs leading-relaxed">
</p>
</div>
<button
type="button"
className="w-full h-12 mt-12 rounded-full shadow-lg bg-linear-to-r from-blue-500 to-cyan-400 text-white text-xl font-medium hover:opacity-90 transition-opacity cursor-pointer"
>
IP线路表
</button>
</div>
<div className="flex flex-col shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white p-8">
<h4 className="text-xl font-semibold mb-3 group-hover:text-blue-600 transition-colors">
IP
</h4>
<div className="flex justify-between">
<div>
<p className="text-gray-500 text-xs leading-relaxed">
{" "}
<span className="text-green-500 text-xl font-bold">
0.1
</span>
</p>
<div className="bg-orange-100 text-orange-500 text-[10px] w-12 text-center mt-2">
</div>
</div>
<img src={s8} alt="图片" className="w-20 h-20" />
</div>
<ul className="space-y-4 text-gray-600">
<li className="flex items-start gap-3">
<span className="inline-block w-1.5 h-1.5 rounded-full bg-green-500 mt-2.5 shrink-0"></span>
<span className="text-xs leading-relaxed">
240+480+
</span>
</li>
<li className="flex items-start gap-3">
<span className="inline-block w-1.5 h-1.5 rounded-full bg-green-500 mt-2.5 shrink-0"></span>
<span className="text-xs leading-relaxed">
IP
</span>
</li>
<li className="flex items-start gap-3">
<span className="inline-block w-1.5 h-1.5 rounded-full bg-green-500 mt-2.5 shrink-0"></span>
<span className="text-xs leading-relaxed">
</span>
</li>
</ul>
<button
type="button"
className="w-full h-12 mt-14 rounded-full shadow-lg bg-linear-to-r from-blue-500 to-cyan-400 text-white text-xl font-medium hover:opacity-90 transition-opacity cursor-pointer"
>
</button>
</div>
<div className="flex flex-col shadow-[4px_4px_20px_4px] shadow-blue-50 bg-white p-8 items-start justify-center ">
<h4 className="text-xl font-semibold mb-3 group-hover:text-blue-600 transition-colors">
IP
</h4>
<p className="text-gray-500 text-xs leading-relaxed">
IP
</p>
<p className="text-gray-500 text-xs leading-relaxed">
260+500+
</p>
<p className="text-gray-500 text-xs leading-relaxed">
</p>
</div>
</div>
</PageSection>
</Wrap>
</section>
</>
)
}
function PageSection(props: { title: string; children: ReactNode }) {
return (
<section>
<div className="max-w-308 mx-auto px-4 flex flex-col items-stretch">
<h2 className="text-center text-3xl mb-8 lg:mb-24">{props.title}</h2>
{props.children}
</div>
</section>
)
}
function ProductTypeCard(props: { icon: string; title: string }) {
return (
<li className="flex flex-col items-center gap-5rounded-lg text-center gap-6">
<img
src={props.icon}
alt={props.title}
className="w-20 h-20 object-cover"
/>
<p className="text-sm">{props.title}</p>
</li>
)
}

346
src/home/ipLine/index.tsx Normal file
View File

@@ -0,0 +1,346 @@
import { Button } from "@/components/ui/button"
import Wrap from "@/components/wrap"
export default function IpLinePage() {
return (
<Wrap className="flex flex-col gap-4 mt-30 max-w-6xl mx-auto px-4 pb-20">
<div className="grid grid-cols-3">
<div></div>
<h3 className="text-3xl font-bold text-gray-800 text-center">
IP线路表
</h3>
<div className="flex gap-4 text-sm justify-end items-end text-blue-500">
<a
href="/softDownload"
className="no-underline hover:text-blue-700 active:no-underline focus:no-underline"
>
</a>
<a
href="/help"
className="no-underline hover:text-blue-700 active:no-underline focus:no-underline"
>
&amp;
</a>
</div>
</div>
<ul className="flex justify-between gap-4 text-xs text-gray-600 px-2">
<li className="flex items-center gap-1.5">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full" />
线使
</li>
<li className="flex items-center gap-1.5">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full" />
</li>
<li className="flex items-center gap-1.5">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full" />
线
</li>
</ul>
<div className="flex flex-wrap items-center gap-3">
<Button className="px-6 py-1.5 rounded-full text-white bg-linear-to-r from-cyan-400 to-blue-500 shadow-md shadow-blue-200 font-medium">
IP
</Button>
<Button className="px-6 py-1.5 rounded-full text-gray-600 border border-gray-200 hover:border-blue-400 transition-colors bg-white">
IP
</Button>
<Button className="px-6 py-1.5 rounded-full text-gray-600 border border-gray-200 hover:border-blue-400 transition-colors bg-white">
IP
</Button>
<Button className="px-6 py-1.5 rounded-full text-gray-600 border border-gray-200 hover:border-blue-400 transition-colors bg-white">
IP
</Button>
<Button className="px-6 py-1.5 rounded-full text-gray-600 border border-gray-200 hover:border-blue-400 transition-colors bg-white">
IP
</Button>
<Button className="px-6 py-1.5 rounded-full text-gray-600 border border-gray-200 hover:border-blue-400 transition-colors bg-white">
IP
</Button>
<Button className="px-6 py-1.5 rounded-full text-gray-600 border border-gray-200 hover:border-blue-400 transition-colors bg-white">
IP
</Button>
</div>
<div className="flex items-center justify-center gap-4 w-full">
<div className="relative w-1/2">
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
/>
</svg>
</div>
<input
type="text"
placeholder="混拨"
className="w-full pl-10 pr-4 py-2 border border-gray-200 rounded-full focus:outline-none focus:ring-2 focus:ring-cyan-400 text-sm"
/>
</div>
<button className="px-8 py-2 bg-linear-to-r from-cyan-400 to-blue-500 text-white rounded-full text-sm font-medium shadow-md shadow-blue-200">
线
</button>
<button className="flex items-center gap-1.5 px-4 py-2 border border-blue-400 text-blue-500 rounded text-sm bg-white hover:bg-blue-50 transition-colors">
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
</button>
</div>
<div className="bg-orange-50 border border-orange-100 rounded-lg p-2 flex justify-center items-center gap-8 text-sm font-medium">
<div className="flex items-center gap-2 text-orange-500">
<span>L2TP密钥</span>
<span className="text-orange-600">1234</span>
</div>
<div className="flex items-center gap-2 text-orange-500">
<span>STTP端口</span>
<span className="text-orange-600">4430</span>
</div>
</div>
<div className="flex items-center gap-6 text-xs text-gray-600 px-1">
<div className="flex items-center gap-2">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full" />
<span>
线<span className="font-bold text-gray-800">486</span>
</span>
</div>
<div className="flex items-center gap-2">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full" />
<span>
线<span className="font-bold text-gray-800">486</span>
</span>
</div>
<div className="flex items-center gap-2">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full" />
<span className="text-gray-400">
IP- (5,访) :{" "}
</span>
</div>
</div>
<div className="border border-gray-200 rounded overflow-hidden">
<table className="w-full text-center text-sm">
<thead className="bg-gray-50 border-b border-gray-200 text-gray-600 font-medium">
<tr>
<th className="py-3 px-4 border-r border-gray-200 w-24"></th>
<th className="py-3 px-4 border-r border-gray-200 w-32"></th>
<th className="py-3 px-4 border-r border-gray-200 w-48">
</th>
<th className="py-3 px-4 border-r border-gray-200"></th>
<th className="py-3 px-4 border-r border-gray-200 w-20">IP量</th>
<th className="py-3 px-4 border-r border-gray-200 w-24">
</th>
<th className="py-3 px-4 border-r border-gray-200 w-24">
</th>
<th className="py-3 px-4 w-24"></th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-100">
<tr>
<td
className="py-3 px-4 border-r border-gray-200 text-gray-500"
rowSpan={10}
>
</td>
<td
className="py-3 px-4 border-r border-gray-200 text-green-600 font-medium"
rowSpan={7}
>
</td>
<td className="py-3 px-4 border-r border-gray-200 text-gray-500">
//
</td>
<td className="py-3 px-4 border-r border-gray-200">
<span>hb1.jhip.net</span>
<span className="ml-2 text-green-500 cursor-pointer hover:text-green-700">
</span>
</td>
<td className="py-3 px-4 border-r border-gray-200">100M</td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4"></td>
</tr>
<tr>
<td className="py-3 px-4 border-r border-gray-200 text-gray-500">
//
</td>
<td className="py-3 px-4 border-r border-gray-200">
<span>hb2.jhip.net</span>
<span className="ml-2 text-green-500 cursor-pointer hover:text-green-700">
</span>
</td>
<td className="py-3 px-4 border-r border-gray-200">100M</td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4"></td>
</tr>
<tr>
<td className="py-3 px-4 border-r border-gray-200 text-gray-500">
//
</td>
<td className="py-3 px-4 border-r border-gray-200">
<span>hb3.jhip.net</span>
<span className="ml-2 text-green-500 cursor-pointer hover:text-green-700">
</span>
</td>
<td className="py-3 px-4 border-r border-gray-200">100M</td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4"></td>
</tr>
<tr>
<td className="py-3 px-4 border-r border-gray-200 text-gray-500">
//
</td>
<td className="py-3 px-4 border-r border-gray-200">
<span>hb4.jhip.net</span>
<span className="ml-2 text-green-500 cursor-pointer hover:text-green-700">
</span>
</td>
<td className="py-3 px-4 border-r border-gray-200">100M</td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4"></td>
</tr>
<tr>
<td className="py-3 px-4 border-r border-gray-200 text-gray-500">
//
</td>
<td className="py-3 px-4 border-r border-gray-200">
<span>hb5.jhip.net</span>
<span className="ml-2 text-green-500 cursor-pointer hover:text-green-700">
</span>
</td>
<td className="py-3 px-4 border-r border-gray-200">100M</td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4"></td>
</tr>
<tr>
<td className="py-3 px-4 border-r border-gray-200 text-gray-500">
//
</td>
<td className="py-3 px-4 border-r border-gray-200">
<span>hbdx2.jhip.net</span>
<span className="ml-2 text-green-500 cursor-pointer hover:text-green-700">
</span>
</td>
<td className="py-3 px-4 border-r border-gray-200">100M</td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4"></td>
</tr>
<tr>
<td className="py-3 px-4 border-r border-gray-200 text-gray-500">
//
</td>
<td className="py-3 px-4 border-r border-gray-200">
<span>hbdx1.jhip.net</span>
<span className="ml-2 text-green-500 cursor-pointer hover:text-green-700">
</span>
</td>
<td className="py-3 px-4 border-r border-gray-200">100M</td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4"></td>
</tr>
<tr>
<td
className="py-3 px-4 border-r border-gray-200 text-green-600 font-medium"
rowSpan={2}
>
</td>
<td className="py-3 px-4 border-r border-gray-200 text-gray-500">
//
</td>
<td className="py-3 px-4 border-r border-gray-200">
<span>bj1.jhip.net</span>
<span className="ml-2 text-green-500 cursor-pointer hover:text-green-700">
</span>
</td>
<td className="py-3 px-4 border-r border-gray-200">100M</td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4"></td>
</tr>
<tr>
<td className="py-3 px-4 border-r border-gray-200 text-gray-500">
//
</td>
<td className="py-3 px-4 border-r border-gray-200">
<span>bj2.jhip.net</span>
<span className="ml-2 text-green-500 cursor-pointer hover:text-green-700">
</span>
</td>
<td className="py-3 px-4 border-r border-gray-200">100M</td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4"></td>
</tr>
<tr>
<td
className="py-3 px-4 border-r border-gray-200 text-green-600 font-medium"
rowSpan={1}
>
</td>
<td className="py-3 px-4 border-r border-gray-200 text-gray-500">
//
</td>
<td className="py-3 px-4 border-r border-gray-200">
<span>sh1.jhip.net</span>
<span className="ml-2 text-green-500 cursor-pointer hover:text-green-700">
</span>
</td>
<td className="py-3 px-4 border-r border-gray-200">100M</td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4 border-r border-gray-200"></td>
<td className="py-3 px-4"></td>
</tr>
</tbody>
</table>
</div>
</Wrap>
)
}

30
src/home/layout.tsx Normal file
View File

@@ -0,0 +1,30 @@
import { useEffect, useState } from "react"
import { Outlet, useLocation } from "react-router-dom"
import AnnouncementModal from "@/components/composites/dialogs/AnnouncementModal"
import Foot from "./foot"
import Header from "./header"
export default function LayoutPage() {
const location = useLocation()
const [isOpen, setIsOpen] = useState(false)
useEffect(() => {
if (location.pathname === "/") {
setIsOpen(true)
} else {
setIsOpen(false)
}
}, [location.pathname])
return (
<div className="bg-blue-50 flex flex-col items-stretch relative min-h-screen">
<Header />
<main className="flex-1">
<Outlet />
</main>
<Foot />
<AnnouncementModal open={isOpen} onOpenChange={setIsOpen} />
</div>
)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

172
src/home/login/index.tsx Normal file
View File

@@ -0,0 +1,172 @@
import { useEffect, useState } from "react"
import { useLocation, useNavigate } from "react-router-dom"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import { cn } from "@/lib/utils"
import bg from "./_assets/bg.webp"
export default function LoginPage() {
const [tab, setTab] = useState<"login" | "register">("login")
const location = useLocation()
useEffect(() => {
const initTab = location.state?.tab
if (initTab === "login" || initTab === "register") {
setTab(initTab)
}
}, [location.state])
return (
<main
className={cn(
`relative`,
`h-screen w-screen xl:pr-64 bg-cover bg-left`,
`flex justify-center xl:justify-end items-center`,
)}
style={{ backgroundImage: `url(${bg})` }}
>
<Card className="w-96 mx-4 shadow-lg relative z-20 py-8">
<CardContent className="px-8">
<div className="flex mb-6 rounded-lg bg-gray-100 p-1">
<Button
variant="ghost"
onClick={() => setTab("login")}
className={`flex-1 py-2 rounded-md transition-all ${
tab === "login"
? "bg-white shadow-sm text-blue-600 font-medium"
: "text-gray-600 hover:text-gray-900"
}`}
>
</Button>
<Button
variant="ghost"
onClick={() => setTab("register")}
className={`flex-1 py-2 rounded-md transition-all ${
tab === "register"
? "bg-white shadow-sm text-blue-600 font-medium"
: "text-gray-600 hover:text-gray-900"
}`}
>
</Button>
</div>
{tab === "login" ? <LoginForm /> : <RegisterForm />}
</CardContent>
</Card>
</main>
)
}
function LoginForm() {
const navigate = useNavigate()
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
navigate("/admin")
}
return (
<form className="space-y-4" onSubmit={handleSubmit}>
<div className="space-y-1">
<label htmlFor="account" className="text-sm font-medium text-gray-700">
</label>
<Input
id="account"
type="text"
placeholder="请输入淘宝名/会员手机号码"
className="w-full"
/>
</div>
<div className="space-y-1">
<label htmlFor="password" className="text-sm font-medium text-gray-700">
</label>
<Input
id="password"
type="password"
placeholder="请输入密码8-12位数字或字母组合"
className="w-full"
/>
</div>
<div className="flex items-center justify-between text-sm">
<div className="flex items-center space-x-2">
<Checkbox id="remember" />
<label htmlFor="remember" className="text-gray-600 cursor-pointer">
</label>
</div>
<span className="text-gray-400 text-xs">
IP账号IP账号
</span>
</div>
<Button
type="submit"
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
>
</Button>
<p className="text-center text-xs text-gray-400 mt-2"></p>
</form>
)
}
function RegisterForm() {
return (
<form className="space-y-4">
<div className="space-y-1">
<label
htmlFor="reg-account"
className="text-sm font-medium text-gray-700"
>
</label>
<Input
id="reg-account"
type="text"
placeholder="请输入手机号码"
className="w-full"
/>
</div>
<div className="space-y-1">
<label
htmlFor="reg-password"
className="text-sm font-medium text-gray-700"
>
</label>
<Input
id="reg-password"
type="password"
placeholder="8-12位数字或字母组合"
className="w-full"
/>
</div>
<div className="space-y-1">
<label
htmlFor="reg-confirm"
className="text-sm font-medium text-gray-700"
>
</label>
<Input
id="reg-confirm"
type="password"
placeholder="再次输入密码"
className="w-full"
/>
</div>
<Button
type="submit"
className="w-full bg-blue-600 hover:bg-blue-700 text-white"
>
</Button>
<p className="text-center text-xs text-gray-400 mt-2">
</p>
</form>
)
}

133
src/home/product/index.tsx Normal file
View File

@@ -0,0 +1,133 @@
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"
export default function ProductPage() {
const [searchParams, setSearchParams] = useSearchParams()
const [activeVersionId, setActiveVersionId] = useState<string>("v1")
const activeTabId = searchParams.get("tab") || "dynamic"
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>
)
}

View File

@@ -0,0 +1,79 @@
import { Button } from "@/components/ui/button"
import type { Product } from "@/lib/models/product"
import vector from "../_assets/vector.webp"
type ProductGridProps = {
products: Product[]
}
export default function ProductGrid({ products }: ProductGridProps) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 mt-6 gap-6">
{products.map(product => (
<div
key={product.id}
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 && (
<div className="absolute -top-5 -right-5 flex items-center gap-1 text-xs font-bold px-3 py-1">
<img src={vector} alt="打折" />
{product.tag}
</div>
)}
<div className="text-lg font-bold text-center text-gray-700">
{product.title}
</div>
{product.desc ? (
<div className="py-2 text-sm text-center text-orange-400 font-bold">
{product.desc}
</div>
) : (
<div className="py-2 text-sm text-center font-bold text-orange-400">
¥ {product.price.toFixed(2)}
</div>
)}
<div className="text-xs text-gray-400 flex flex-col space-y-0.5 mt-10">
{product.card && (
<div className="flex justify-between">
<span></span>
<span>{product.card}</span>
</div>
)}
{product.originalPrice !== undefined && (
<div className="flex justify-between">
<span></span>
<span>¥{product.originalPrice.toFixed(2)}</span>
</div>
)}
{product.duration && (
<div className="flex justify-between">
<span></span>
<span>{product.duration}</span>
</div>
)}
</div>
{product.features && product.features.length > 0 && (
<div className="text-xs text-gray-400 text-center space-y-0.5 mt-1">
{product.features.map((feature, index) => (
<div key={index}>{feature}</div>
))}
</div>
)}
<div className="pt-4 mt-auto">
<Button
variant="outline"
className="w-full text-blue-500 border-blue-400 hover:bg-blue-50"
>
</Button>
</div>
</div>
))}
</div>
)
}

View File

@@ -0,0 +1,33 @@
import type { Brand } from "@/lib/models/product"
import { cn } from "@/lib/utils"
type ProductSidebarProps = {
brands: Brand[]
activeId: string
onSelect: (id: string) => void
}
export default function ProductSidebar({
brands,
activeId,
onSelect,
}: ProductSidebarProps) {
return (
<aside className="w-30">
{brands.map(brand => (
<button
key={brand.id}
onClick={() => onSelect(brand.id)}
className={cn(
"w-full text-left px-4 py-3 rounded-lg text-sm transition-colors",
activeId === brand.id
? "bg-blue-50 text-blue-600 font-semibold"
: "text-gray-600 hover:bg-gray-50 hover:text-gray-900",
)}
>
{brand.name}
</button>
))}
</aside>
)
}

View File

@@ -0,0 +1,36 @@
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
import type { Tab } from "@/lib/models/product"
import { cn } from "@/lib/utils"
type ProductTabsProps = {
tabs: Tab[]
activeId: string
onTabChange: (id: string) => void
}
export default function ProductTabs({
tabs,
activeId,
onTabChange,
}: ProductTabsProps) {
return (
<Tabs value={activeId} onValueChange={onTabChange} className="w-full">
<TabsList className="grid w-full grid-cols-3 bg-slate-100 rounded-xl h-14">
{tabs.map(tab => (
<TabsTrigger
key={tab.id}
value={tab.id}
className={cn(
"w-full py-2 rounded-full text-sm font-medium transition-all cursor-pointer text-center outline-none border-0 ring-0",
activeId === tab.id
? "bg-linear-to-r from-blue-500 to-cyan-400 text-white! shadow-none"
: "bg-gray-100 text-gray-600 hover:bg-gray-200",
)}
>
{tab.label}
</TabsTrigger>
))}
</TabsList>
</Tabs>
)
}

View File

@@ -0,0 +1,85 @@
export default function PurchaseInfo() {
return (
<div className="flex w-full max-w-6xl mx-auto">
<div className="flex-1 space-y-3 pr-3 border-r">
<div>
<h3 className="text-lg font-bold text-orange-400 mb-3"></h3>
<ul className="space-y-2 text-sm text-gray-600">
<li className="flex items-start gap-2 whitespace-nowrap">
<span className="w-2 h-2 mt-1.5 bg-emerald-400 rounded-full shrink-0" />
IPIP池子最大的动态产品240IP广1
</li>
<li className="flex items-start gap-2 whitespace-nowrap">
<span className="w-2 h-2 mt-1.5 bg-emerald-400 rounded-full shrink-0" />
IPIP网速最快的动态产品260IP广1
</li>
</ul>
</div>
<div>
<h3 className="text-lg font-bold text-orange-400 mb-3"></h3>
<ul className="space-y-2 text-sm text-gray-600">
<li className="flex items-start gap-2">
<span className="w-2 h-2 mt-1.5 bg-emerald-400 rounded-full shrink-0" />
5
</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>
<li className="flex items-start gap-2">
<span className="w-2 h-2 mt-1.5 bg-emerald-400 rounded-full shrink-0" />
QQ请到个人中心签订协议
</li>
</ul>
</div>
<div>
<h3 className="text-lg font-bold text-orange-400 mb-3"></h3>
{/* 这里可以放入产品对比表格 */}
</div>
</div>
<div className="flex-none w-90 pl-6 space-y-6">
<h3 className="text-lg font-bold text-orange-400 mb-2">
1V1定制化企业级业务场景
</h3>
<div className="">
<span className="block text-sm font-medium text-gray-800 mb-2">
</span>
<p className="text-xs text-gray-500 m-0 leading-relaxed">
IP服务AI大模型训练IP解决方案
</p>
</div>
<div className="border-t border-gray-200 pt-6">
<span className="block text-sm font-medium text-gray-800 mb-2">
IP资源
</span>
<p className="text-xs text-gray-500 m-0 leading-relaxed">
IP池IP地址均经过严格筛选和测试
</p>
</div>
<div className="border-t border-gray-200 pt-6">
<span className="block text-sm font-medium text-gray-800 mb-2">
</span>
<p className="text-xs text-gray-500 m-0 leading-relaxed">
IP致力于保护所有用户的隐私和数据安全
</p>
</div>
<div className="border-t border-gray-200 pt-6">
<span className="block text-sm font-medium text-gray-800 mb-2">
</span>
<p className="text-xs text-gray-500 m-0 leading-relaxed">
7×24IP行业多年的专业人士使
</p>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,248 @@
import { Apple, Monitor, Smartphone } from "lucide-react"
import type { ReactNode } from "react"
import Wrap from "@/components/wrap"
export default function SoftDownloadPage() {
const DownloadBtn = ({
href,
icon,
label,
}: {
href: string
icon: ReactNode
label: string
}) => (
<a
href={href}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-center gap-2 w-full py-2.5 px-4 bg-white border border-blue-400 text-blue-500 rounded-full text-sm font-medium hover:bg-blue-50 transition-colors"
>
{icon} {label}
</a>
)
return (
<Wrap className="flex flex-col gap-6 mt-30">
<div className="grid grid-cols-3">
<div></div>
<h3 className="text-3xl font-bold text-gray-800 text-center">
</h3>
<div className="flex gap-4 text-sm justify-end items-end text-blue-500">
<a
href="/softDownload"
className="no-underline hover:text-blue-700 active:no-underline focus:no-underline"
>
</a>
<a
href="/help"
className="no-underline hover:text-blue-700 active:no-underline focus:no-underline"
>
&amp;
</a>
</div>
</div>
<ul className="flex justify-between gap-4 text-xs text-gray-600 px-2">
<li className="flex items-center gap-1.5">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full" />
使
</li>
<li className="flex items-center gap-1.5">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full" />
使360
</li>
<li className="flex items-center gap-1.5">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full" />
线
</li>
</ul>
<div className="grid grid-cols-3 gap-8">
<div className="flex flex-col gap-3">
<h4 className="text-center font-bold text-gray-700 text-base mb-1">
IP
</h4>
<div className="flex flex-col gap-3">
<DownloadBtn
href="https://mp4.juip.com/jh-dl.zip"
icon={<Monitor className="w-4 h-4" />}
label="Windows客户端"
/>
<DownloadBtn
href="https://mp4.juip.com/dl.apk"
icon={<Smartphone className="w-4 h-4" />}
label="安卓APP"
/>
<DownloadBtn
href="https://www.juip.com/article/index?Catalog=4"
icon={<Apple className="w-4 h-4" />}
label="苹果直连教程"
/>
</div>
</div>
<div className="flex flex-col gap-3">
<h4 className="text-center font-bold text-gray-700 text-base mb-1">
IP
</h4>
<div className="flex flex-col gap-3">
<DownloadBtn
href="https://mp4.juip.com/jg-dl.zip"
icon={<Monitor className="w-4 h-4" />}
label="Windows客户端"
/>
<DownloadBtn
href="https://mp4.juip.com/jgdl.apk"
icon={<Smartphone className="w-4 h-4" />}
label="安卓APP"
/>
<DownloadBtn
href="https://www.juip.com/article/index?Catalog=4"
icon={<Apple className="w-4 h-4" />}
label="苹果直连教程"
/>
</div>
</div>
<div className="flex flex-col gap-3">
<h4 className="text-center font-bold text-gray-700 text-base mb-1">
IP
</h4>
<div className="flex flex-col gap-3">
<DownloadBtn
href="https://mp4.juip.com/%E8%98%91%E8%8F%87IP-2-17.zip"
icon={<Monitor className="w-4 h-4" />}
label="Windows客户端"
/>
<DownloadBtn
href="https://mp4.juip.com/mogudl.apk"
icon={<Smartphone className="w-4 h-4" />}
label="安卓APP"
/>
<DownloadBtn
href="https://www.juip.com/article/index?Catalog=4"
icon={<Apple className="w-4 h-4" />}
label="苹果直连教程"
/>
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-8">
<div className="flex flex-col gap-3">
<h4 className="text-center font-bold text-gray-700 text-base mb-1">
IP
</h4>
<div className="flex flex-col gap-3">
<DownloadBtn
href="https://mp4.juip.com/%E9%BA%92%E9%BA%9F%E7%94%B5%E8%84%91HZ%E7%89%88.exe"
icon={<Monitor className="w-4 h-4" />}
label="Windows客户端"
/>
<DownloadBtn
href="https://mp4.juip.com/%E9%BA%92%E9%BA%9F%E5%AE%89%E5%8D%93HZ%E7%89%88.apk"
icon={<Smartphone className="w-4 h-4" />}
label="安卓APP"
/>
<DownloadBtn
href="https://www.juip.com/article/index?Catalog=4"
icon={<Apple className="w-4 h-4" />}
label="苹果直连教程"
/>
</div>
</div>
<div className="flex flex-col gap-3">
<h4 className="text-center font-bold text-gray-700 text-base mb-1">
IP
</h4>
<div className="flex flex-col gap-3">
<DownloadBtn
href="https://mp4.juip.com/lb-dl.zip"
icon={<Monitor className="w-4 h-4" />}
label="Windows客户端"
/>
<DownloadBtn
href="https://mp4.juip.com/%E7%8C%8E%E8%B1%B9%E5%AE%89%E5%8D%93ikev2%E7%89%88.apk"
icon={<Smartphone className="w-4 h-4" />}
label="安卓APP"
/>
<DownloadBtn
href="https://mp4.juip.com/lbdlsstp.apk"
icon={<Apple className="w-4 h-4" />}
label="苹果直连教程"
/>
</div>
</div>
</div>
<div className="flex flex-col gap-6 mb-20">
<h4 className="text-center font-bold text-gray-700 text-base">
</h4>
<div className="grid grid-cols-3 gap-4">
<DownloadBtn
href="https://mp4.juip.com/jh-dl.zip"
icon={<Monitor className="w-4 h-4" />}
label="极狐IP电脑端"
/>
<DownloadBtn
href="https://mp4.juip.com/dl.apk"
icon={<Smartphone className="w-4 h-4" />}
label="极狐IP安卓版"
/>
<DownloadBtn
href="https://mp4.juip.com/%E8%98%91%E8%8F%87IP-2-17.zip"
icon={<Monitor className="w-4 h-4" />}
label="蘑菇IP电脑端"
/>
<DownloadBtn
href="https://mp4.juip.com/mogudl.apk"
icon={<Smartphone className="w-4 h-4" />}
label="蘑菇IP安卓版"
/>
<DownloadBtn
href="https://mp4.juip.com/jg-dl.zip"
icon={<Monitor className="w-4 h-4" />}
label="极光IP电脑端"
/>
<DownloadBtn
href="https://mp4.juip.com/jgdl.apk"
icon={<Smartphone className="w-4 h-4" />}
label="极光IP安卓版"
/>
<DownloadBtn
href="https://mp4.juip.com/%E9%BA%92%E9%BA%9F%E7%94%B5%E8%84%91HZ%E7%89%88.exe"
icon={<Monitor className="w-4 h-4" />}
label="麒麟IP电脑端"
/>
<DownloadBtn
href="https://mp4.juip.com/%E9%BA%92%E9%BA%9F%E5%AE%89%E5%8D%93HZ%E7%89%88.apk"
icon={<Smartphone className="w-4 h-4" />}
label="麒麟IP安卓版"
/>
<DownloadBtn
href="https://mp4.juip.com/lb-dl.zip"
icon={<Monitor className="w-4 h-4" />}
label="猎豹IP电脑端"
/>
<DownloadBtn
href="https://mp4.juip.com/lbdlsstp.apk"
icon={<Smartphone className="w-4 h-4" />}
label="猎豹IP安卓ikev2"
/>
<DownloadBtn
href="https://mp4.juip.com/lbdlsstp.apk"
icon={<Smartphone className="w-4 h-4" />}
label="猎豹IP安卓sstp"
/>
</div>
</div>
</Wrap>
)
}