IP线路表 & 软件下载 页面手机端适配

This commit is contained in:
Eamon
2026-08-27 14:06:22 +08:00
parent ff56fbd7c3
commit 171f5bc44a
5 changed files with 127 additions and 100 deletions

View File

@@ -40,6 +40,21 @@ function Pagination({
const [currentPage, setCurrentPage] = useState(page)
const totalPages = Math.ceil(total / size)
// 移动端检测:小屏幕减少页码按钮数量,避免溢出
const [isMobile, setIsMobile] = useState(
() =>
typeof window !== "undefined" &&
window.matchMedia("(max-width: 640px)").matches,
)
useEffect(() => {
const mql = window.matchMedia("(max-width: 640px)")
const handleChange = () => setIsMobile(mql.matches)
handleChange()
mql.addEventListener("change", handleChange)
return () => mql.removeEventListener("change", handleChange)
}, [])
// 同步外部 page 变化
useEffect(() => {
setCurrentPage(page)
@@ -47,12 +62,13 @@ function Pagination({
// 分页器逻辑
const generatePaginationItems = () => {
// 最多显示7个页码,其余用省略号
const SIBLINGS = 1 // 当前页左右各显示的页码数
// 桌面端最多显示7个页码,移动端最多显示5个,其余用省略号
const SIBLINGS = isMobile ? 0 : 1 // 当前页左右各显示的页码数
const MAX_VISIBLE = isMobile ? 5 : 7
const DOTS = -1 // 省略号标记
if (totalPages <= 7) {
// 总页数少于7,全部显示
if (totalPages <= MAX_VISIBLE) {
// 总页数较少,全部显示
return Array.from({ length: totalPages }, (_, i) => i + 1)
}
@@ -121,8 +137,13 @@ function Pagination({
const paginationItems = generatePaginationItems()
return (
<div className={`flex flex-wrap items-center gap-4 ${className || ""}`}>
<div className="flex-none flex items-center gap-2 text-sm text-muted-foreground">
<div
className={cn(
"flex flex-wrap items-center justify-center gap-2 md:justify-start md:gap-4",
className,
)}
>
<div className="flex-none flex items-center gap-2 text-xs sm:text-sm text-muted-foreground">
共 {total} 条记录,每页
<Select value={size.toString()} onValueChange={handlePageSizeChange}>
<SelectTrigger className="h-8 w-20">
@@ -209,7 +230,10 @@ function PaginationContent({
return (
<ul
data-slot="pagination-content"
className={cn("flex flex-row items-center gap-1", className)}
className={cn(
"flex flex-row flex-wrap items-center justify-center gap-1",
className,
)}
{...props}
/>
)
@@ -234,7 +258,7 @@ function PaginationLink({
data-slot="pagination-link"
data-active={isActive}
className={cn(
"inline-flex items-center justify-center text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-9 w-9 rounded-md border border-input hover:bg-secondary hover:text-secondary-foreground",
"inline-flex items-center justify-center text-xs sm:text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 h-8 w-8 sm:h-9 sm:w-9 rounded-md border border-input hover:bg-secondary hover:text-secondary-foreground",
`bg-card`,
isActive &&
"bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground",
@@ -283,7 +307,10 @@ function PaginationEllipsis({
<span
aria-hidden
data-slot="pagination-ellipsis"
className={cn("flex size-9 items-center justify-center", className)}
className={cn(
"flex size-8 sm:size-9 items-center justify-center",
className,
)}
{...props}
>
<MoreHorizontalIcon className="size-4" />

View File

@@ -53,7 +53,7 @@ export default function Header() {
: `bg-transparent shadow-none`,
)}
>
<Wrap className="h-20 max-md:h-20 flex text-center justify-between max-md:pl-4 max-md:pr-4">
<Wrap className="h-20 max-md:h-20 flex text-center justify-between max-md:pl-4 max-md:pr-4 md:bg-transparent bg-white ">
<nav className="flex items-center gap-4 lg:gap-8">
{/* logo + 网站名称 */}
<NavLink to="/" className="flex items-center gap-2 shrink-0">

View File

@@ -193,13 +193,13 @@ export default function IpLinePage() {
return (
<>
<Toaster position="top-center" richColors />
<Wrap className="flex flex-col gap-4 mt-20 max-w-6xl mx-auto px-4 pb-20 bg-white">
<div className="grid grid-cols-3 mt-3">
<Wrap className="flex flex-col gap-4 mt-24 max-w-6xl mx-auto px-4 pb-20 bg-white">
<div className="grid md:grid-cols-3 grid-cols-1 mt-3">
<div></div>
<h3 className="text-3xl font-bold text-gray-800 text-center">
<h3 className="text-2xl md: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">
<div className="flex gap-4 text-sm md:justify-end justify-center md:p-0 pt-3 items-end text-blue-500">
<a
href="/softDownload"
className="no-underline hover:text-blue-700 active:no-underline focus:no-underline"
@@ -215,7 +215,7 @@ export default function IpLinePage() {
</div>
</div>
<ul className="flex justify-between gap-4 text-xs text-gray-600 px-2">
<ul className="flex flex-col md:flex-row md:justify-between gap-2 md: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" />
线路表和账户必须为同一产品才能使用
@@ -237,7 +237,7 @@ export default function IpLinePage() {
variant={p.id === productId ? "default" : "outline"}
onClick={() => handleSelectProduct(p.id)}
className={cn(
"px-6 py-1.5 rounded-full",
"px-4 md:px-6 py-1.5 rounded-full",
p.id === productId
? "text-white bg-linear-to-r from-cyan-400 to-blue-500 shadow-md shadow-blue-200 hover:opacity-90"
: "border-gray-200 bg-white text-gray-600 hover:border-blue-400 hover:text-blue-500",
@@ -248,8 +248,8 @@ export default function IpLinePage() {
))}
</div>
<div className="flex items-center justify-center gap-4 w-full">
<div className="relative w-1/2">
<div className="flex flex-col md:flex-row md:items-center md:justify-center gap-3 md:gap-4 w-full">
<div className="relative w-full md:w-1/2">
<div className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400">
<svg
className="w-4 h-4"
@@ -276,35 +276,37 @@ export default function IpLinePage() {
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
onClick={handleSearch}
className="px-8 rounded-full bg-linear-to-r from-cyan-400 to-blue-500 text-white shadow-md shadow-blue-200 hover:opacity-90"
>
搜索当前线路表
</Button>
<Button
variant="outline"
onClick={handleExport}
className="gap-1.5 px-4 rounded border-blue-400 text-blue-500 bg-white hover:bg-blue-50 hover:text-blue-500"
>
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
<div className="flex gap-3 w-full md:w-auto">
<Button
onClick={handleSearch}
className="flex-1 md:flex-none px-8 rounded-full bg-linear-to-r from-cyan-400 to-blue-500 text-white shadow-md shadow-blue-200 hover:opacity-90"
>
<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>
搜索当前线路表
</Button>
<Button
variant="outline"
onClick={handleExport}
className="flex-none gap-1.5 px-4 rounded border-blue-400 text-blue-500 bg-white hover:bg-blue-50 hover:text-blue-500"
>
<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>
<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="bg-orange-50 border border-orange-100 rounded-lg p-2 md:p-3 flex flex-col md:flex-row md:justify-center md:items-center gap-1 md:gap-8 text-sm font-medium">
<div className="flex items-center gap-2 text-orange-500">
<span>L2TP密钥:</span>
<span className="text-orange-600">
@@ -319,7 +321,7 @@ export default function IpLinePage() {
</div>
</div>
<div className="flex items-center gap-6 text-xs text-gray-600 px-1">
<div className="flex flex-col md:flex-row md:items-center gap-2 md:gap-6 text-xs text-gray-600 px-1">
{searching ? (
<div className="flex items-center gap-2">
<span className="w-1.5 h-1.5 bg-emerald-500 rounded-full" />

View File

@@ -47,7 +47,7 @@ function ModeTabs({
onModeChange: (mode: "single" | "batch") => void
}) {
return (
<div className="flex bg-gray-50 rounded-xl p-1 w-48">
<div className="flex bg-gray-50 rounded-xl p-1 w-48 max-md:w-full">
<button
className={cn(
"flex-1 py-2 text-sm font-medium rounded-lg transition-all cursor-pointer",
@@ -89,11 +89,11 @@ function ConnectCountSelector({
disabled?: boolean
}) {
return (
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
{label}:
</Label>
<div className="flex items-center gap-4 flex-1">
<div className="flex items-center gap-4 w-full md:w-auto md:flex-1">
<Button
variant="outline"
size="icon"
@@ -156,21 +156,19 @@ function ConnectCountSelector({
// ============ 优惠券选择器 ============
function CouponSelector() {
return (
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
选择优惠券:
</Label>
<Select>
<SelectTrigger className="h-10 rounded-xl border-gray-200 flex-1 max-w-xs">
<SelectTrigger className="h-10 rounded-xl border-gray-200 w-full md:w-auto md:flex-1 md:max-w-xs">
<SelectValue placeholder="请选择优惠券" />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">暂无可用优惠券</SelectItem>
</SelectContent>
</Select>
<span className="text-xs text-gray-400 whitespace-nowrap">
淘宝每次下单获得优惠券
</span>
<span className="text-xs text-gray-400">淘宝每次下单获得优惠券</span>
</div>
)
}
@@ -178,8 +176,8 @@ function CouponSelector() {
// ============ 批量注册说明 ============
function BatchInfo() {
return (
<div className="flex items-start gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28 pt-1">
<div className="flex flex-col gap-2 md:flex-row md:items-start md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28 md:pt-1">
说明:
</Label>
<div className="flex-1 text-sm text-gray-600 leading-relaxed p-2 bg-blue-50/70 rounded-xl border border-blue-100">
@@ -343,8 +341,8 @@ export default function BuyPage() {
const showTabs = !isTest && !isFirstOrderDayCard
return (
<Wrap className="bg-white min-h-screen flex items-start justify-center pt-16">
<div className="w-full max-w-2xl px-8">
<Wrap className="bg-white min-h-screen flex items-start justify-center pt-24">
<div className="w-full max-w-2xl px-0 md:px-8">
{/* 标题 */}
<div className="text-center mb-6 space-y-2">
<h4 className="text-2xl font-bold">
@@ -371,38 +369,38 @@ export default function BuyPage() {
// ====== 单个注册 ======
<>
{/* IP产品账号 */}
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
IP产品账号:
</Label>
<Input
value={account}
onChange={e => setAccount(e.target.value)}
placeholder="4-10位字母或数字"
className="h-10 rounded-xl border-gray-200 flex-1"
className="h-10 rounded-xl border-gray-200 w-full md:w-auto md:flex-1"
/>
<span className="text-xs text-gray-400 whitespace-nowrap">
<span className="text-xs text-gray-400">
4至10位字母或数字或组合
</span>
</div>
{/* IP产品密码 */}
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
IP产品密码:
</Label>
<Input
value={pwd}
onChange={e => setPwd(e.target.value)}
placeholder="1-10位字母或数字"
className="h-10 rounded-xl border-gray-200 flex-1"
className="h-10 rounded-xl border-gray-200 w-full md:w-auto md:flex-1"
/>
<span className="text-xs text-gray-400 whitespace-nowrap">
<span className="text-xs text-gray-400">
1至10位字母或数字或组合
</span>
</div>
{/* 连接设备数:测试卡不显示;天卡首单(0.1元)显示但禁用 */}
{!isTest && (
<ConnectCountSelector
value={displayConnectCount}
@@ -420,24 +418,24 @@ export default function BuyPage() {
<BatchInfo />
{/* IP账号前缀 */}
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
IP账号前缀:
</Label>
<Input
value={batchAccount}
onChange={e => setBatchAccount(e.target.value)}
placeholder="3-8位字母或数字"
className="h-10 rounded-xl border-gray-200 flex-1"
className="h-10 rounded-xl border-gray-200 w-full md:w-auto md:flex-1"
/>
<span className="text-xs text-gray-400 whitespace-nowrap">
<span className="text-xs text-gray-400">
3至8位字母或数字或组合
</span>
</div>
{/* 开始号 */}
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
开始号:
</Label>
<Input
@@ -446,7 +444,7 @@ export default function BuyPage() {
onChange={e =>
setBatchStart(Number.parseInt(e.target.value, 10) || 1)
}
className="h-10 rounded-xl border-gray-200 flex-1 max-w-32"
className="h-10 rounded-xl border-gray-200 w-full md:w-auto md:flex-1 md:max-w-32"
/>
<span className="text-xs text-gray-400">
本批次账号的起始账号
@@ -454,8 +452,8 @@ export default function BuyPage() {
</div>
{/* 注册个数 */}
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
注册个数:
</Label>
<Input
@@ -466,14 +464,14 @@ export default function BuyPage() {
Math.min(500, Number.parseInt(e.target.value, 10) || 1),
)
}
className="h-10 rounded-xl border-gray-200 flex-1 max-w-32"
className="h-10 rounded-xl border-gray-200 w-full md:w-auto md:flex-1 md:max-w-32"
/>
</div>
{/* 账号预览 */}
{batchPreview && (
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
即将生成的账号:
</Label>
<span className="text-sm text-gray-700 font-medium">
@@ -483,17 +481,17 @@ export default function BuyPage() {
)}
{/* IP产品密码 */}
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
IP产品密码:
</Label>
<Input
value={batchPwd}
onChange={e => setBatchPwd(e.target.value)}
placeholder="1-10位字母或数字"
className="h-10 rounded-xl border-gray-200 flex-1"
className="h-10 rounded-xl border-gray-200 w-full md:w-auto md:flex-1"
/>
<span className="text-xs text-gray-400 whitespace-nowrap">
<span className="text-xs text-gray-400">
1至10位字母或数字或组合
</span>
</div>
@@ -512,11 +510,11 @@ export default function BuyPage() {
{!isTest && <CouponSelector />}
{/* 余额 */}
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-2 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
余额:
</Label>
<div className="flex items-center gap-2 flex-1">
<div className="flex items-center gap-2 w-full md:w-auto md:flex-1">
<span className="text-sm text-gray-600">
当前账户余额
<span className="font-bold text-orange-500 ml-1">¥10.00</span>
@@ -532,14 +530,14 @@ export default function BuyPage() {
</div>
{/* 支付方式 */}
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex flex-col gap-3 md:flex-row md:items-center md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
支付方式:
</Label>
<RadioGroup
value={payType}
onValueChange={setPayType}
className="flex gap-8"
className="flex flex-wrap gap-x-8 gap-y-3 md:gap-8"
>
<div className="flex items-center gap-2">
<RadioGroupItem value="100" id="alipay" />
@@ -576,16 +574,16 @@ export default function BuyPage() {
{/* 总金额 / 应付款 */}
<div className="space-y-2">
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex items-center justify-between md:justify-start md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
总金额:
</Label>
<span className="font-medium text-gray-800">
¥{total.toFixed(2)}
</span>
</div>
<div className="flex items-center gap-6">
<Label className="text-sm font-medium text-gray-700 whitespace-nowrap w-28">
<div className="flex items-center justify-between md:justify-start md:gap-6">
<Label className="text-sm font-medium text-gray-700 md:whitespace-nowrap md:w-28">
应付款:
</Label>
<span className="text-xl font-bold text-orange-500">

View File

@@ -23,13 +23,13 @@ export default function SoftDownloadPage() {
)
return (
<Wrap className="flex flex-col gap-6 mt-20 bg-white">
<div className="grid grid-cols-3 mt-3">
<Wrap className="flex flex-col gap-6 mt-24 bg-white">
<div className="grid md:grid-cols-3 grid-cols-1 text-center justify-center mt-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">
<div className="flex gap-4 text-sm md:justify-end justify-center md:p-0 pt-3 items-end text-blue-500">
<a
href="/ipline"
className="no-underline hover:text-blue-700 active:no-underline focus:no-underline"
@@ -60,7 +60,7 @@ export default function SoftDownloadPage() {
</li>
</ul>
<div className="grid grid-cols-3 gap-8">
<div className="grid md:grid-cols-3 grid-cols-1 gap-8">
<div className="flex flex-col gap-3">
<h4 className="text-center font-bold text-gray-700 text-base mb-1">
极狐IP
@@ -131,7 +131,7 @@ export default function SoftDownloadPage() {
</div>
</div>
<div className="grid grid-cols-3 gap-8">
<div className="grid md:grid-cols-3 grid-cols-1 gap-8">
<div className="flex flex-col gap-3">
<h4 className="text-center font-bold text-gray-700 text-base mb-1">
麒麟IP
@@ -183,7 +183,7 @@ export default function SoftDownloadPage() {
<h4 className="text-center font-bold text-gray-700 text-base">
代理商专用客户端
</h4>
<div className="grid grid-cols-3 gap-4">
<div className="grid md:grid-cols-3 grid-cols-1 gap-4">
<DownloadBtn
href="https://mp4.juip.com/jh-dl.zip"
icon={<Monitor className="w-4 h-4" />}