补充个人中心下的页面

This commit is contained in:
Eamon
2026-08-31 14:49:28 +08:00
parent b1f99b51e2
commit 3072b97bcf
59 changed files with 4122 additions and 970 deletions

View File

@@ -1,37 +1,18 @@
import type { ColumnDef } from "@tanstack/react-table"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
import { Toaster, toast } from "sonner"
import {
fetchLineData,
type LineItem,
PHP_API_BASE_URL,
searchLineData,
} from "@/api/linedata"
import { PHP_API_BASE_URL } from "@/api/http"
import { fetchLineData, searchLineData } from "@/api/linedata"
import DataTable from "@/components/data-table"
import { Button } from "@/components/ui/button"
import Wrap from "@/components/wrap"
import { cn } from "@/lib/utils"
import type { LineItem } from "@/lib/models/line"
import { PRODUCTS } from "@/lib/models/line"
import { cn, downloadCsv } from "@/lib/utils"
const REFRESH_INTERVAL = 5 * 60 * 1000
const PAGE_SIZE_OPTIONS = [100, 200, 500]
type ProductOption = {
id: number
name: string
l2tp: string
sstp: string
}
const PRODUCTS: ProductOption[] = [
{ id: 29, name: "极狐IP", l2tp: "8899", sstp: "4430" },
{ id: 3, name: "极光IP", l2tp: "1234", sstp: "4430" },
// { id: 18, name: "蘑菇IP", l2tp: "8899", sstp: "4430" },
{ id: 27, name: "麒麟IP", l2tp: "123", sstp: "4432" },
{ id: 26, name: "猎豹IP", l2tp: "1234", sstp: "4430" },
{ id: 28, name: "水滴独享IP", l2tp: "123", sstp: "4432" },
{ id: 22, name: "火狐静态IP", l2tp: "888888", sstp: "5908" },
]
function toText(value: string | number): string {
return value === null || value === undefined ? "" : String(value)
}
@@ -120,7 +101,6 @@ export default function IpLinePage() {
setPageIndex(0)
const seq = ++requestSeq.current
const result = await searchLineData(productId ?? 0, info)
console.log("搜索的result", result)
if (seq !== requestSeq.current) return
if (result.success) {
@@ -154,29 +134,15 @@ export default function IpLinePage() {
"带宽",
"服务器状态",
]
const lines = [
header,
...rows.map(r => [
toText(r.name),
toText(r.city),
toText(r.supply),
toText(r.nasname),
toText(r.daikuan),
toText(r.online),
]),
]
const csv = lines
.map(line => line.map(v => `"${v.replaceAll('"', '""')}"`).join(","))
.join("\r\n")
const blob = new Blob([`\uFEFF${csv}`], {
type: "text/csv;charset=utf-8",
})
const url = URL.createObjectURL(blob)
const a = document.createElement("a")
a.href = url
a.download = `${currentName || "IP线路表"}.csv`
a.click()
URL.revokeObjectURL(url)
const data = rows.map(r => [
toText(r.name),
toText(r.city),
toText(r.supply),
toText(r.nasname),
toText(r.daikuan),
toText(r.online),
])
downloadCsv(`${currentName || "IP线路表"}.csv`, header, data)
toast.success("导出成功")
}

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from "react"
import { useLocation, useNavigate } from "react-router-dom"
import { toast } from "sonner"
import { type CreateOrderRequest, createOrder } from "@/api/order"
import { createOrder } from "@/api/order"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
@@ -14,6 +14,7 @@ import {
SelectValue,
} from "@/components/ui/select"
import Wrap from "@/components/wrap"
import type { CreateOrderRequest } from "@/lib/models/order"
import type { Product } from "@/lib/models/product"
import { cn } from "@/lib/utils"
import alipay from "../_assets/alipay.svg"

View File

@@ -80,7 +80,7 @@ function OperatorSelect({
return (
<div className="flex flex-wrap gap-2">
{operators.map((op) => (
{operators.map(op => (
<button
key={op}
className={cn(
@@ -129,7 +129,7 @@ function RegionGrid({
H: "H",
I: "I",
}
regions.forEach((r) => {
regions.forEach(r => {
const key = letterMap[r.id] || "其他"
if (!groupedRegions[key]) groupedRegions[key] = []
groupedRegions[key].push(r)
@@ -143,7 +143,7 @@ function RegionGrid({
{letter}
</span>
<div className="flex flex-wrap gap-2 flex-1">
{items.map((region) => (
{items.map(region => (
<button
key={region.id}
className={cn(
@@ -155,7 +155,9 @@ function RegionGrid({
onClick={() => onSelect(region.name)}
>
{region.name}
<span className="ml-1 text-xs text-gray-400">余量:{region.stock}</span>
<span className="ml-1 text-xs text-gray-400">
余量:{region.stock}
</span>
</button>
))}
</div>
@@ -177,7 +179,7 @@ function BandwidthSelect({
return (
<div className="flex flex-wrap gap-2">
{options.map((bw) => (
{options.map(bw => (
<button
key={bw}
className={cn(
@@ -211,7 +213,7 @@ function DurationSelect({
return (
<div className="flex flex-wrap gap-2">
{options.map((opt) => (
{options.map(opt => (
<button
key={opt.value}
className={cn(
@@ -242,10 +244,10 @@ function ProjectSelect({
return (
<select
value={value}
onChange={(e) => onChange(e.target.value)}
onChange={e => onChange(e.target.value)}
className="h-10 px-3 rounded-xl border border-gray-200 bg-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 w-full max-w-xs"
>
{projects.map((p) => (
{projects.map(p => (
<option key={p} value={p}>
{p}
</option>
@@ -281,7 +283,12 @@ export default function BuyStaticPage() {
// 根据类型计算价格
const getPrice = () => {
const bwMap: Record<string, number> = { "2M": 10, "4M": 20, "10M": 40, "20M": 80 }
const bwMap: Record<string, number> = {
"2M": 10,
"4M": 20,
"10M": 40,
"20M": 80,
}
const durationMap: Record<string, number> = { test: 0.1, day: 1, month: 10 }
const basePrice = bwMap[bandwidth] || 10
const durationPrice = durationMap[duration] || 0.1
@@ -315,7 +322,9 @@ export default function BuyStaticPage() {
</div>
) : (
<div className="mb-4">
<p className="text-sm text-gray-600">专线独享:个人独享整个线路,真实独享,永远不重复。</p>
<p className="text-sm text-gray-600">
专线独享:个人独享整个线路,真实独享,永远不重复。
</p>
<p className="mt-1 text-sm text-red-500">线路切换单日限制为20次</p>
<p className="mt-1 text-sm text-red-500">
如果没有自己想用的地区线路或线路余量不足,请联系客服上架
@@ -356,7 +365,7 @@ export default function BuyStaticPage() {
placeholder="请输入地区"
className="h-10 rounded-xl border-gray-200 flex-1"
value={regionSearch}
onChange={(e) => setRegionSearch(e.target.value)}
onChange={e => setRegionSearch(e.target.value)}
/>
<Button className="h-10 rounded-xl px-6 bg-blue-500 hover:bg-blue-600 text-white">
搜索
@@ -438,7 +447,9 @@ export default function BuyStaticPage() {
<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">¥{getPrice()}</span>
<span className="text-xl font-bold text-orange-500">
¥{getPrice()}
</span>
</div>
{/* 余额 */}
@@ -520,4 +531,4 @@ export default function BuyStaticPage() {
</div>
</Wrap>
)
}
}

View File

@@ -51,7 +51,6 @@ export default function ProductPage() {
const results = await Promise.allSettled(
TAB_SOURCES.map(source => source.fetch()),
)
console.log(results, "resultsresultsresults")
if (cancelled) return

View File

@@ -44,8 +44,8 @@ export default function PurchaseInfo() {
tabIndex={0}
className="w-full overflow-hidden rounded-lg border border-gray-200 shadow-sm cursor-pointer hover:opacity-90 transition-opacity"
onClick={() => setIsImageOpen(true)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
onKeyDown={e => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault()
setIsImageOpen(true)
}
@@ -75,7 +75,7 @@ export default function PurchaseInfo() {
<button
type="button"
className="absolute top-4 right-4 text-white bg-black/50 rounded-full w-10 h-10 text-2xl hover:bg-black/70 transition-colors pointer-events-auto"
onClick={(e) => {
onClick={e => {
e.stopPropagation()
setIsImageOpen(false)
}}

View File

@@ -59,7 +59,10 @@ export default function ShuidiPools({ onBuy }: ShuidiPoolsProps) {
<div className="text-sm text-gray-500 space-y-1.5 mt-5">
{pool.features.map((feature, index) => (
<div key={index} className="flex items-center justify-center gap-2 text-amber-500">
<div
key={index}
className="flex items-center justify-center gap-2 text-amber-500"
>
{feature}
</div>
))}