'use client'; import { useSearchParams } from 'next/navigation'; import { useState, useEffect, useCallback, Suspense } from 'react'; import OrderTable from '@/components/admin/OrderTable'; import OrderDetail from '@/components/admin/OrderDetail'; import PaginationBar from '@/components/PaginationBar'; import PayPageLayout from '@/components/PayPageLayout'; interface AdminOrder { id: string; userId: number; userName: string | null; userEmail: string | null; userNotes: string | null; amount: number; status: string; paymentType: string; createdAt: string; paidAt: string | null; completedAt: string | null; failedReason: string | null; expiresAt: string; srcHost: string | null; } interface AdminOrderDetail extends AdminOrder { rechargeCode: string; paymentTradeNo: string | null; refundAmount: number | null; refundReason: string | null; refundAt: string | null; forceRefund: boolean; failedAt: string | null; updatedAt: string; clientIp: string | null; srcHost: string | null; srcUrl: string | null; paymentSuccess?: boolean; rechargeSuccess?: boolean; rechargeStatus?: string; auditLogs: { id: string; action: string; detail: string | null; operator: string | null; createdAt: string }[]; } function AdminContent() { const searchParams = useSearchParams(); const token = searchParams.get('token'); const theme = searchParams.get('theme') === 'dark' ? 'dark' : 'light'; const uiMode = searchParams.get('ui_mode') || 'standalone'; const isDark = theme === 'dark'; const isEmbedded = uiMode === 'embedded'; const [orders, setOrders] = useState([]); const [total, setTotal] = useState(0); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(20); const [totalPages, setTotalPages] = useState(1); const [statusFilter, setStatusFilter] = useState(''); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [detailOrder, setDetailOrder] = useState(null); const fetchOrders = useCallback(async () => { if (!token) return; setLoading(true); try { const params = new URLSearchParams({ token, page: String(page), page_size: String(pageSize) }); if (statusFilter) params.set('status', statusFilter); const res = await fetch(`/api/admin/orders?${params}`); if (!res.ok) { if (res.status === 401) { setError('管理员凭证无效'); return; } throw new Error('请求失败'); } const data = await res.json(); setOrders(data.orders); setTotal(data.total); setTotalPages(data.total_pages); } catch (e) { setError('加载订单列表失败'); } finally { setLoading(false); } }, [token, page, pageSize, statusFilter]); useEffect(() => { fetchOrders(); }, [fetchOrders]); if (!token) { return (

缺少管理员凭证

请从 Sub2API 平台正确访问管理页面

); } const handleRetry = async (orderId: string) => { if (!confirm('确认重试充值?')) return; try { const res = await fetch(`/api/admin/orders/${orderId}/retry?token=${token}`, { method: 'POST', }); if (res.ok) { fetchOrders(); } else { const data = await res.json(); setError(data.error || '重试失败'); } } catch { setError('重试请求失败'); } }; const handleCancel = async (orderId: string) => { if (!confirm('确认取消该订单?')) return; try { const res = await fetch(`/api/admin/orders/${orderId}/cancel?token=${token}`, { method: 'POST', }); if (res.ok) { fetchOrders(); } else { const data = await res.json(); setError(data.error || '取消失败'); } } catch { setError('取消请求失败'); } }; const handleViewDetail = async (orderId: string) => { try { const res = await fetch(`/api/admin/orders/${orderId}?token=${token}`); if (res.ok) { const data = await res.json(); setDetailOrder(data); } } catch { setError('加载订单详情失败'); } }; const statuses = ['', 'PENDING', 'PAID', 'RECHARGING', 'COMPLETED', 'EXPIRED', 'CANCELLED', 'FAILED', 'REFUNDED']; const statusLabels: Record = { '': '全部', PENDING: '待支付', PAID: '已支付', RECHARGING: '充值中', COMPLETED: '已完成', EXPIRED: '已超时', CANCELLED: '已取消', FAILED: '充值失败', REFUNDED: '已退款', }; const navParams = new URLSearchParams(); if (token) navParams.set('token', token); if (isDark) navParams.set('theme', 'dark'); if (isEmbedded) navParams.set('ui_mode', 'embedded'); const btnBase = [ 'inline-flex items-center rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors', isDark ? 'border-slate-600 text-slate-200 hover:bg-slate-800' : 'border-slate-300 text-slate-700 hover:bg-slate-100', ].join(' '); return ( 数据概览 } > {error && (
{error}
)} {/* Filters */}
{statuses.map((s) => ( ))}
{/* Table */}
{loading ? (
加载中...
) : ( )}
setPage(p)} onPageSizeChange={(s) => { setPageSize(s); setPage(1); }} isDark={isDark} /> {/* Order Detail */} {detailOrder && setDetailOrder(null)} dark={isDark} />}
); } export default function AdminPage() { return (
加载中...
} >
); }