From a902f5be291cc17930617c46e2b424c0af61027f Mon Sep 17 00:00:00 2001 From: Eamon <17516219072@163.com> Date: Tue, 15 Sep 2026 17:39:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=85=85=E5=80=BC=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E5=92=8C=E7=9F=AD=E6=95=88=E5=A5=97=E9=A4=90=E9=A1=B5?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/admin/funds/page.tsx | 36 ++-- src/admin/httpRecharge/page.tsx | 70 +++++--- src/admin/resources/short-api-link.tsx | 236 ++++++++++++++++++++++++- src/admin/resources/short.tsx | 2 + src/api/funds.ts | 19 +- src/api/http.ts | 4 +- src/api/httpRecharge.ts | 22 ++- src/api/order.ts | 2 +- src/lib/models/funds.ts | 1 + src/lib/models/httpRecharge.ts | 22 ++- src/lib/models/package.ts | 19 +- 11 files changed, 374 insertions(+), 59 deletions(-) diff --git a/src/admin/funds/page.tsx b/src/admin/funds/page.tsx index 6af8cf2..fa7cab4 100644 --- a/src/admin/funds/page.tsx +++ b/src/admin/funds/page.tsx @@ -1,5 +1,5 @@ import type { ColumnDef } from "@tanstack/react-table" -import { useCallback, useEffect, useState } from "react" +import { useCallback, useEffect, useRef, useState } from "react" import { toast } from "sonner" import { fetchScoreList } from "@/api/funds" import DataTable from "@/components/data-table" @@ -58,18 +58,31 @@ export default function FundsPage() { const [status, setStatus] = useState<"load" | "done" | "fail">("load") const [page, setPage] = useState(1) const [hasMore, setHasMore] = useState(false) + // 总数恰为 50 整数倍时最后一页也是 50 条,翻到空页后锁死最大页码 + const [maxPage, setMaxPage] = useState(null) + const seqRef = useRef(0) const load = useCallback(async (targetPage: number) => { + const seq = ++seqRef.current setStatus("load") const res = await fetchScoreList(targetPage) - if (res.success) { - setData(res.data) - setHasMore(res.data.length >= SCORE_PAGE_SIZE) - setStatus("done") - } else { + console.log(res, 'datadatadatadatadata'); + + if (seq !== seqRef.current) return + if (!res.success) { setStatus("fail") toast.error(res.message) + return } + if (res.data.length === 0 && targetPage > 1) { + setMaxPage(targetPage - 1) + setPage(targetPage - 1) + load(targetPage - 1) + return + } + setData(res.data) + setHasMore(res.data.length >= SCORE_PAGE_SIZE) + setStatus("done") }, []) useEffect(() => { @@ -82,10 +95,13 @@ export default function FundsPage() { load(nextPage) } - // 接口不返回总条数:还有下一页时多加 1 条保证可翻页,否则按累计条数展示 - const total = hasMore - ? page * SCORE_PAGE_SIZE + 1 - : (page - 1) * SCORE_PAGE_SIZE + data.length + const canNext = maxPage !== null ? page < maxPage : hasMore + const total = + maxPage !== null + ? maxPage * SCORE_PAGE_SIZE + : canNext + ? page * SCORE_PAGE_SIZE + 1 + : (page - 1) * SCORE_PAGE_SIZE + data.length return ( diff --git a/src/admin/httpRecharge/page.tsx b/src/admin/httpRecharge/page.tsx index 0942d57..924d4ee 100644 --- a/src/admin/httpRecharge/page.tsx +++ b/src/admin/httpRecharge/page.tsx @@ -1,34 +1,56 @@ +import type { ColumnDef } from "@tanstack/react-table" +import { useCallback, useEffect, useMemo, useState } from "react" +import { fetchHttpRechargeList } from "@/api/httpRecharge" import DataTable from "@/components/data-table" import Page from "@/components/page" -import type { HttpRechargeItem } from "@/lib/models/httpRecharge" +import { + formatPayStatus, + type HttpRechargeItem, +} from "@/lib/models/httpRecharge" export default function HttpRechargePage() { + const [data, setData] = useState([]) + const [status, setStatus] = useState<"load" | "done" | "fail">("load") + + const load = useCallback(async () => { + setStatus("load") + const res = await fetchHttpRechargeList() + console.log(res, 'fewewf'); + + if (!res.success) { + setStatus("fail") + return + } + setData(res.data) + setStatus("done") + }, []) + + useEffect(() => { + load() + }, [load]) + + const columns = useMemo[]>( + () => [ + { accessorKey: "create_time", header: "交易时间" }, + { accessorKey: "ju_money", header: "充值H币" }, + { accessorKey: "pay_money", header: "支付金额" }, + { accessorKey: "pay_type", header: "充值渠道" }, + { + accessorKey: "pay_status", + header: "支付状态", + cell: ({ row }) => formatPayStatus(row.original.pay_status), + }, + { accessorKey: "order_no", header: "订单编号" }, + { accessorKey: "ali_wx_no", header: "支付宝/微信订单号" }, + ], + [], + ) + return (
-

HTTP 充值记录

- - data={[]} - status="done" - columns={[ - { - accessorKey: "create_time", - header: "交易时间", - }, - { - accessorKey: "ju_money", - header: "充值H币", - }, - { - accessorKey: "pay_type", - header: "充值渠道", - }, - { - accessorKey: "pay_money", - header: "充值金额", - }, - ]} - /> +

HTTP 充值记录

+
) diff --git a/src/admin/resources/short-api-link.tsx b/src/admin/resources/short-api-link.tsx index 2428abd..9970a37 100644 --- a/src/admin/resources/short-api-link.tsx +++ b/src/admin/resources/short-api-link.tsx @@ -13,6 +13,14 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" import { type ApiLinkConfig, type DxPackage, @@ -69,6 +77,151 @@ const SEPARATOR_OPTIONS = [ { value: "6", label: "自定义:" }, ] +const API_PARAM_DOCS: { + name: string + type: string + required: string + desc: string +}[] = [ + { + name: "pt", + type: "int", + required: "是", + desc: "套餐类型,0或12-储值套餐,21-短效包天,22-短效包量,23-短效无限量,29-终身免费VIP套餐,31-长效套餐", + }, + { name: "num", type: "int", required: "是", desc: "提取IP数量" }, + { + name: "gt", + type: "int", + required: "否", + desc: "提取时长 0-默认 1-1分钟以上 2-2分钟以上 3-3分钟以上 4-4分钟以上", + }, + { + name: "regions", + type: "string", + required: "否", + desc: "多个用英文逗号换行,不传表示随机", + }, + { name: "isp", type: "int", required: "是", desc: "0:不限 4:联通 2:电信" }, + { name: "port", type: "int", required: "是", desc: "IP协议 1:HTTP 2:SOCKS5" }, + { + name: "time", + type: "int", + required: "否", + desc: "有效时长 1-0.1至1分钟 5-1至5分钟 25-5至25分钟 180-25至180分钟 360-3至6小时", + }, + { + name: "type", + type: "int", + required: "否", + desc: "数据格式:1:TXT 2:JSON 3:HTML", + }, + { name: "pack", type: "int", required: "否", desc: "用户套餐ID" }, + { + name: "ts", + type: "int", + required: "否", + desc: "是否显示IP过期时间: 1显示 0不显示", + }, + { + name: "lb", + type: "int", + required: "否", + desc: "换行符(1:\\r\\n 2:/br 3:\\r 4:\\n 5:\\t 6:自定义)", + }, + { name: "clb", type: "string", required: "否", desc: "自定义换行符" }, + { + name: "sp", + type: "int", + required: "否", + desc: "分隔符(1:竖线| 2:斜线/ 3:冒号 6:自定义)", + }, + { name: "csp", type: "string", required: "否", desc: "自定义分隔符" }, + { + name: "distinct", + type: "int", + required: "否", + desc: "去重选择(2:严格去重。0:不去重)", + }, + { + name: "aw", + type: "int", + required: "否", + desc: "是否将调用者自动添加到IP白名单(0:不自动添加。1:自动添加)", + }, + { + name: "at", + type: "int", + required: "否", + desc: "鉴权模式 1-白名单,2-白名单+账密严格鉴权,3-仅账密鉴权(无限量不可用)", + }, +] + +const RESPONSE_FIELD_DOCS: { name: string; type: string; desc: string }[] = [ + { name: "success", type: "bool", desc: "是否成功" }, + { name: "code", type: "int", desc: "0表示成功,其他参见《错误代码》表" }, + { name: "ip", type: "string", desc: "IP" }, + { name: "port", type: "int", desc: "端口号" }, +] + +const ERROR_CODE_DOCS: { code: string; msg: string; desc: string }[] = [ + { + code: "111", + msg: "请在1秒后再次请求", + desc: "提取链接请求太过频繁,超出限制", + }, + { + code: "113", + msg: "请将192.168.0.0设置为白名单!", + desc: "白名单未添加/白名单掉了", + }, + { code: "114", msg: "余额不足", desc: "账户金额消耗完毕" }, + { + code: "115", + msg: "请更换地区等条件重新生成api链接地址", + desc: "没有资源或没有符合条件的数据", + }, + { + code: "116", + msg: "您的套餐今日已到达上限!", + desc: "套餐内IP数量消耗完毕", + }, + { + code: "117", + msg: "您的套餐pack传参有误!请检测您现在的ip是否在套餐所在账户!", + desc: "检测本地白名单是不是在账户下", + }, + { + code: "118", + msg: "您的账户异常,请联系客服!", + desc: "账户处于被禁用状态", + }, + { code: "121", msg: "您的该套餐已经过期了!", desc: "套餐过期" }, + { + code: "401", + msg: "IP:1.1.1.1:2000 Authentication failed!", + desc: "白名单错误/使用的IP已经过期", + }, + { + code: "403", + msg: "Sorry!The site has been banned!", + desc: "客户目标网站异常,联系客服处理", + }, +] + +const RESPONSE_SAMPLE = `{ + "code": 0, + "msg": "提示信息", + "success": true, + "data": [ + { + "ip": "49.68.68.197", // 隧道ip(代理ip) + "port": 33220, // 代理端口 + "expireTime": "2019-05-24 08:58:31" + } + ] +}` + type FieldRowProps = { label: string children: React.ReactNode @@ -179,8 +332,10 @@ export default function ShortApiLink({ packages }: { packages: DxPackage[] }) { push("t", token.token) } push("at", config.at) - push("sp", config.sp) - push("csp", config.csp) + if (config.at !== 1) { + push("sp", config.sp) + push("csp", config.csp) + } if (regions.length > 0) push("regions", regions.join(",")) return `${API_LINK_BASE}?${params.join("&")}` } @@ -431,7 +586,7 @@ export default function ShortApiLink({ packages }: { packages: DxPackage[] }) { )} - {config.at !== 1 && !isUnlimited && ( + {!isUnlimited && ( )} + +
+

请求参数注释

+ + + + 名称 + 类型 + 必选 + 说明 + + + + {API_PARAM_DOCS.map(param => ( + + {param.name} + {param.type} + {param.required} + + {param.desc} + + + ))} + +
+
+ +
+

返回结果示例

+
+          {RESPONSE_SAMPLE}
+        
+ + + + 名称 + 类型 + 说明 + + + + {RESPONSE_FIELD_DOCS.map(field => ( + + {field.name} + {field.type} + + {field.desc} + + + ))} + +
+
+ +
+

错误代码

+ + + + 错误编码 + 错误提示 + 说明 + + + + {ERROR_CODE_DOCS.map(err => ( + + code:{err.code} + {err.msg} + {err.desc} + + ))} + +
+
) } diff --git a/src/admin/resources/short.tsx b/src/admin/resources/short.tsx index 847b647..f21e91d 100644 --- a/src/admin/resources/short.tsx +++ b/src/admin/resources/short.tsx @@ -33,6 +33,7 @@ export default function ShortPage() { const loadPackages = useCallback(async () => { setStatus("load") const res = await fetchDxPackages() + console.log(res, 'resresresres'); if (!res.success) { setStatus("fail") return @@ -44,6 +45,7 @@ export default function ShortPage() { const loadOrders = useCallback(async () => { setOrderStatus("load") const res = await fetchDxOrders() + if (!res.success) { setOrderStatus("fail") return diff --git a/src/api/funds.ts b/src/api/funds.ts index 1138c59..dfc26a2 100644 --- a/src/api/funds.ts +++ b/src/api/funds.ts @@ -1,14 +1,21 @@ import type { ApiResponse } from "@/lib/api" import type { ScoreItem } from "@/lib/models/funds" -import { phpPost } from "./http" +import { phpFetch } from "./http" export async function fetchScoreList( page: number, ): Promise> { - const res = await phpPost("/user/UserScore/getList", { - cookie: document.cookie, - page, - }) + const query = page > 1 ? `?page=${page}` : "" + const res = await phpFetch( + "POST", + `/user/UserScore/getList${query}`, + { cookie: document.cookie }, + { credentials: "include" }, + ) if (!res.success) return res - return { success: true, data: Array.isArray(res.data) ? res.data : [] } + if (Array.isArray(res.data)) return { success: true, data: res.data } + return { + success: true, + data: Array.isArray(res.data?.data) ? res.data.data : [], + } } diff --git a/src/api/http.ts b/src/api/http.ts index e65e971..87032ab 100644 --- a/src/api/http.ts +++ b/src/api/http.ts @@ -61,7 +61,9 @@ export async function phpFetch( message: "请求失败", } } - const data = (await response.json()) as T + const text = await response.text() + // 老 PHP 接口异常时可能返回空响应体,直接 json() 会抛错 + const data = text ? (JSON.parse(text) as T) : ({} as T) return { success: true, data } } catch (e) { if (controller.signal.aborted) { diff --git a/src/api/httpRecharge.ts b/src/api/httpRecharge.ts index 60f0fc3..2ec7176 100644 --- a/src/api/httpRecharge.ts +++ b/src/api/httpRecharge.ts @@ -1,16 +1,20 @@ import type { ApiResponse } from "@/lib/api" import type { HttpRechargeItem } from "@/lib/models/httpRecharge" -import { callByUser } from "./base" +import { isArray, phpPost } from "./http" // ==================== HTTP 充值记录 ==================== -// TODO: 接口路径待后端确认(旧后台为 php-api /http/* 充值列表接口)。 -export async function fetchHttpRechargeList( - params: Record, -): Promise> { - return await callByUser( - "/api/course/v1/order/HttpRecharges", - params, - "GET", +export async function fetchHttpRechargeList(): Promise< + ApiResponse +> { + const res = await phpPost( + "/http/user/charge_list", + { cookie: document.cookie }, ) + if (!res.success) return res + if (isArray(res.data)) { + return { success: true, data: res.data } + } + const list = isArray(res.data?.data) ? res.data.data : [] + return { success: true, data: list } } diff --git a/src/api/order.ts b/src/api/order.ts index 36ee77a..41d6b4d 100644 --- a/src/api/order.ts +++ b/src/api/order.ts @@ -167,7 +167,7 @@ export async function createRechargeOrder(data: { } // 轮询充值支付状态:返回 true 表示支付成功 -// 新后端 /user/IsPay 响应体:{ Code: 0|1, Message: "" },Code 本身即支付状态 +// 响应体:{ Code: 0|1, Message: "" },Code 本身即支付状态 // 兼容旧格式 { Code: 10000, Data: 0|1 } export async function checkRechargeStatus( orderNo: string, diff --git a/src/lib/models/funds.ts b/src/lib/models/funds.ts index 0ce6b6d..ff3df0c 100644 --- a/src/lib/models/funds.ts +++ b/src/lib/models/funds.ts @@ -16,4 +16,5 @@ export type ScoreItem = { RestAmount2: string } +// 服务端固定每页条数(接口不返回总条数) export const SCORE_PAGE_SIZE = 50 diff --git a/src/lib/models/httpRecharge.ts b/src/lib/models/httpRecharge.ts index cae7058..c0c27f1 100644 --- a/src/lib/models/httpRecharge.ts +++ b/src/lib/models/httpRecharge.ts @@ -1,6 +1,22 @@ export type HttpRechargeItem = { - create_time: string - ju_money: number + id: string + user_id: string + ju_money: string | number + pay_money: string | number pay_type: string - pay_money: number + pay_status: string | number + channel?: string | null + order_no: string + ali_wx_no: string + create_time: string + update_time: string +} + +export const HTTP_RECHARGE_PAY_STATUS_LABELS: Record = { + 0: "未支付", + 1: "支付成功", +} + +export function formatPayStatus(value: string | number): string { + return HTTP_RECHARGE_PAY_STATUS_LABELS[Number(value)] ?? String(value) } diff --git a/src/lib/models/package.ts b/src/lib/models/package.ts index fd96ef1..b33f8d3 100644 --- a/src/lib/models/package.ts +++ b/src/lib/models/package.ts @@ -11,6 +11,11 @@ export type DxPackage = { expireTime: string isExpired?: boolean | number | string ipAmount?: number + userId?: number + poolType?: number + usedAmount?: number + transferable?: number + createTime?: number } // 短效购买记录 @@ -21,6 +26,16 @@ export type DxOrder = { durationType: string money: number | string create_time: string + id?: string + user_id?: string + order_status?: number | string + game_type?: string | null + ip_count?: number | string | null + order_id?: string + payCash?: string + serial?: string + status?: number | string + update_time?: string } // 长效套餐(已开通) @@ -214,11 +229,11 @@ export const IP_STATE_LABELS: Record = { // ==================== 字段格式化 ==================== -// 后端的 isExpired 可能返回 true/1/"1",统一转布尔 +// 后端的 isExpired 可能返回 true/1/"1",也可能直接返回"已过期"/"未过期"字符串 export function parseExpired( value: boolean | number | string | undefined, ): boolean { - return value === true || value === 1 || value === "1" + return value === true || value === 1 || value === "1" || value === "已过期" } export function formatExpired(