import type { ColumnDef } from "@tanstack/react-table" import { useCallback, useEffect, useMemo, useState } from "react" import { useNavigate } from "react-router-dom" import { toast } from "sonner" import { fetchCxOrders, fetchCxPackages, fetchLangIpList, fetchLangIps, fetchSwitchList, } from "@/api/http" import DataTable from "@/components/data-table" import Page from "@/components/page" import { Button } from "@/components/ui/button" import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs" import type { CxOrder, CxPackage, LangIpItem, SwitchRecord, } from "@/lib/models/package" import { formatAbroad, formatExpired, formatIpState, formatLineType, formatOrderTypeLabel, formatShareType, formatSwitchReason, LONG_ORDER_TYPE_LABELS, parseExpired, } from "@/lib/models/package" import { downloadTextFile, estimateTotal, formatUnixTime } from "@/lib/utils" import LongRenewDialog, { type LongRenewSource } from "./long-renew-dialog" import LongSwitchDialog from "./long-switch-dialog" const PAGE_SIZE = 50 export default function LongPage() { const navigate = useNavigate() const [activeTab, setActiveTab] = useState("ips") // 已开通长效 const [ipList, setIpList] = useState([]) const [ipStatus, setIpStatus] = useState<"load" | "done" | "fail">("load") const [ipPage, setIpPage] = useState(1) const [ipTotal, setIpTotal] = useState(0) const [rowSelection, setRowSelection] = useState>({}) // 全部套餐 const [packages, setPackages] = useState([]) const [packageStatus, setPackageStatus] = useState<"load" | "done" | "fail">( "load", ) // 购买记录 const [orders, setOrders] = useState([]) const [orderStatus, setOrderStatus] = useState<"load" | "done" | "fail">( "load", ) // IP切换记录 const [switchList, setSwitchList] = useState([]) const [switchStatus, setSwitchStatus] = useState<"load" | "done" | "fail">( "load", ) const [switchPage, setSwitchPage] = useState(1) const [switchTotal, setSwitchTotal] = useState(0) // 弹窗 const [renewSource, setRenewSource] = useState(null) const [switchTarget, setSwitchTarget] = useState<{ ip: LangIpItem pop: 0 | 1 } | null>(null) const loadIps = useCallback(async (page: number) => { setIpStatus("load") const res = await fetchLangIpList({ page, limit: PAGE_SIZE, packId: "", isAbroad: "", isExpired: 0, state: -1, ip: "", groupId: "", gameId: "", cityCode: "", orderBy: 0, }) if (!res.success) { setIpStatus("fail") toast.error(res.message) return } setIpList(res.data.items) setIpTotal( estimateTotal( res.data.pager.totalCount, res.data.pager.currentPage || page, PAGE_SIZE, res.data.items.length, ), ) setIpStatus("done") }, []) const loadPackages = useCallback(async () => { setPackageStatus("load") const res = await fetchCxPackages() if (!res.success) { setPackageStatus("fail") return } setPackages(res.data) setPackageStatus("done") }, []) const loadOrders = useCallback(async () => { setOrderStatus("load") const res = await fetchCxOrders() if (!res.success) { setOrderStatus("fail") return } setOrders(res.data) setOrderStatus("done") }, []) const loadSwitch = useCallback(async (page: number) => { setSwitchStatus("load") const res = await fetchSwitchList({ page, limit: PAGE_SIZE, }) if (!res.success) { setSwitchStatus("fail") toast.error(res.message) return } setSwitchList(res.data.items) setSwitchTotal( estimateTotal( res.data.pager.totalCount, res.data.pager.currentPage || page, PAGE_SIZE, res.data.items.length, ), ) setSwitchStatus("done") }, []) useEffect(() => { loadIps(1) }, [loadIps]) const handleTabChange = (value: string) => { setActiveTab(value) if (value === "packages" && !packages.length) loadPackages() if (value === "orders" && !orders.length) loadOrders() if (value === "switch" && !switchList.length) loadSwitch(1) } const handlePageChange = (page: number) => { setIpPage(page) setRowSelection({}) loadIps(page) } const handleSwitchPageChange = (page: number) => { setSwitchPage(page) loadSwitch(page) } const handleExtract = async () => { const ids = Object.keys(rowSelection) if (ids.length === 0) { toast.error("请选择IP!") return } const res = await fetchLangIps(ids.join(",")) if (!res.success) { toast.error(res.message) return } if (res.data.length === 0) { toast.error("提取失败,请稍后重试") return } const content = res.data .map( ip => `${ip.ip}|${ip.httpPort}|${ip.socks5Port}|${ip.username}|${ip.password}`, ) .join("\n") downloadTextFile("ip.txt", content) toast.success("提取成功,已开始下载") } const ipColumns = useMemo[]>( () => [ { accessorKey: "isRelayed", header: "模式", cell: ({ row }) => (row.original.isRelayed ? "中继" : "直连"), }, { accessorKey: "ip", header: "节点入口IP" }, { accessorKey: "realIp", header: "隧道出口IP" }, { id: "ports", header: "HTTP/SK5端口", cell: ({ row }) => `${row.original.httpPort}/${row.original.socks5Port}`, }, { accessorKey: "area", header: "归属地" }, { id: "bandwidth", header: "带宽", cell: ({ row }) => `${row.original.bandwidth}M/${formatLineType(row.original.lineType)}`, }, { accessorKey: "shareType", header: "线路", cell: ({ row }) => formatShareType(row.original.shareType), }, { accessorKey: "state", header: "状态", cell: ({ row }) => formatIpState(row.original.state), }, { id: "account", header: "账号/密码", cell: ({ row }) => `${row.original.username}/${row.original.password}`, }, { accessorKey: "expireTime", header: "过期时间", cell: ({ row }) => formatUnixTime(row.original.expireTime), }, { accessorKey: "isExpired", header: "是否过期", cell: ({ row }) => parseExpired(row.original.isExpired) ? "已过期" : "未过期", }, { id: "actions", header: "操作", cell: ({ row }) => { const ip = row.original return (
{ip.isAbroad === 1 && ip.isRelayed === 1 && ( )}
) }, }, ], [], ) const packageColumns = useMemo[]>( () => [ { accessorKey: "name", header: "套餐名称", cell: ({ row }) => row.original.name || "-", }, { accessorKey: "isAbroad", header: "套餐类型", cell: ({ row }) => formatAbroad(row.original.isAbroad), }, { accessorKey: "shareType", header: "模式", cell: ({ row }) => formatShareType(row.original.shareType), }, { accessorKey: "ipAmount", header: "可用IP数量(个)" }, { accessorKey: "isExpired", header: "是否过期", cell: ({ row }) => formatExpired(row.original.isExpired), }, { accessorKey: "expireTime", header: "过期时间", cell: ({ row }) => formatUnixTime(row.original.expireTime), }, { id: "actions", header: "续费", cell: ({ row }) => ( ), }, ], [], ) const orderColumns = useMemo[]>( () => [ { accessorKey: "order_no", header: "订单号" }, { accessorKey: "order_type", header: "订单类型", cell: ({ row }) => formatOrderTypeLabel(LONG_ORDER_TYPE_LABELS, row.original.order_type), }, { accessorKey: "game_type", header: "游戏类型" }, { accessorKey: "ip_count", header: "IP数量" }, { accessorKey: "money", header: "实付金额" }, { accessorKey: "create_time", header: "支付时间" }, { id: "rebuy", header: "再次购买", cell: () => ( ), }, ], [navigate], ) const switchColumns = useMemo[]>( () => [ { accessorKey: "oldIp", header: "原IP" }, { accessorKey: "oldArea", header: "原属地" }, { accessorKey: "newIp", header: "切换后IP" }, { accessorKey: "newArea", header: "切换后属地" }, { accessorKey: "reason", header: "切换方式", cell: ({ row }) => formatSwitchReason(row.original.reason), }, { accessorKey: "createTime", header: "切换时间", cell: ({ row }) => formatUnixTime(row.original.createTime), }, ], [], ) return (

长效套餐

已开通长效 全部套餐 购买记录 IP切换记录
格式:IP|http端口|socks5端口|账号|密码
String(row.id)} pagination={{ page: ipPage, size: PAGE_SIZE, total: ipTotal, sizeOptions: [PAGE_SIZE], onPageChange: handlePageChange, }} />
setRenewSource(null)} onSuccess={() => { loadIps(ipPage) loadPackages() }} /> setSwitchTarget(null)} onSuccess={() => loadIps(ipPage)} />
) }