补充个人中心下的页面
This commit is contained in:
@@ -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("导出成功")
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user