"use client"; import { useEffect, useState } from "react"; import { adjustQuota, changeUserStatus, fetchCsrf, grantMembership, getUserDetail, getUserQuota, listUsers, revokeMembership, type AdminUser, type AdminUserDetail, } from "@/lib/admin"; import { StatusCard } from "@/components/ui"; type UserRow = AdminUser; export function UsersPanel() { const [rows, setRows] = useState([]); const [total, setTotal] = useState(0); const [pages, setPages] = useState(1); const [page, setPage] = useState(1); const [email, setEmail] = useState(""); const [phone, setPhone] = useState(""); const [status, setStatus] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [selected, setSelected] = useState(null); const [notice, setNotice] = useState(""); async function load(pageNum = page) { setLoading(true); setError(""); try { const res = await listUsers({ email, phone, status, page: pageNum, limit: 20 }); setRows(res.items); setTotal(res.total); setPages(res.pages); setPage(res.page); } catch (e) { setError(e instanceof Error ? e.message : "加载失败"); } finally { setLoading(false); } } useEffect(() => { void load(1); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); async function openDetail(id: string) { setError(""); try { const detail = await getUserDetail(id); setSelected(detail); } catch (e) { setError(e instanceof Error ? e.message : "加载详情失败"); } } return (
setEmail(e.target.value)} placeholder="按邮箱筛选" value={email} /> setPhone(e.target.value)} placeholder="按手机号筛选" value={phone} />
{error ?
{error}
: null}
{loading ? ( ) : rows.length === 0 ? ( ) : ( rows.map((row) => ( )) )}
用户 有效 Plan 角色 状态 注册时间
加载中…
无匹配用户

{row.email}

{row.phone ?? "无手机号"}

{row.effective_plan.toUpperCase()} {row.role} {row.status === "active" ? "正常" : "已禁用"} {new Date(row.created_at).toLocaleDateString()}
第 {page} / {pages} 页
{selected ? void openDetail(selected.user.id)} setNotice={setNotice} onClose={() => setSelected(null)} /> : null} {notice ?
{notice}
: null}
); } function UserDetailPanel({ detail, onRefresh, setNotice, onClose }: { detail: AdminUserDetail; onRefresh: () => void; setNotice: (msg: string) => void; onClose: () => void }) { const [csrf, setCsrf] = useState(""); const [reason, setReason] = useState(""); const [vipDays, setVipDays] = useState("30"); const [amount, setAmount] = useState("100"); const [busy, setBusy] = useState(false); const [message, setMessage] = useState(""); useEffect(() => { void fetchCsrf().then(setCsrf); }, []); async function run(fn: () => Promise, okMsg: string) { if (!reason.trim()) { setMessage("请填写原因"); return; } setBusy(true); setMessage(""); try { await fn(); setNotice(okMsg); onRefresh(); } catch (e) { setMessage(e instanceof Error ? e.message : "操作失败"); } finally { setBusy(false); } } function toggleStatus() { const target: "active" | "disabled" = detail.user.status === "active" ? "disabled" : "active"; return run(() => changeUserStatus(detail.user.id, target, reason, csrf), target === "disabled" ? "已禁用并撤销会话" : "已恢复"); } function doGrant() { const days = Number(vipDays); const expiresAt = days > 0 ? new Date(Date.now() + days * 86400000).toISOString() : null; return run(() => grantMembership(detail.user.id, "vip", expiresAt, reason, csrf), "已授予 VIP"); } function doRevoke() { return run(() => revokeMembership(detail.user.id, reason, csrf), "已撤销 VIP"); } function doAdjust() { const amt = Number(amount); if (!Number.isFinite(amt)) { setMessage("额度需为数字"); return; } return run(() => adjustQuota(detail.user.id, amt, reason, `admin-${Date.now()}-${Math.random()}`, csrf), "额度已调整"); } return (

{detail.user.email}

有效 Plan:{detail.effective_plan.toUpperCase()} · 状态:{detail.user.status === "active" ? "正常" : "已禁用"}

额度周期

{new Date(detail.quota.period_start).toLocaleDateString()} → {new Date(detail.quota.period_end).toLocaleDateString()}

上限
{detail.quota.limit.toLocaleString()}
已用
{detail.quota.used.toLocaleString()}
冻结
{detail.quota.reserved.toLocaleString()}
调整
{detail.quota.adjustment.toLocaleString()}
可用
{detail.quota.available.toLocaleString()}

当前会员

{detail.effective_grant ? (

{detail.effective_grant.plan.toUpperCase()}

至 {detail.effective_grant.expires_at ? new Date(detail.effective_grant.expires_at).toLocaleDateString() : "长期"}

) : (

无有效会员

)} {detail.membership_history.length ? (
    {detail.membership_history.slice(0, 4).map((g) => (
  • {g.plan.toUpperCase()} · {new Date(g.starts_at).toLocaleDateString()} → {g.expires_at ? new Date(g.expires_at).toLocaleDateString() : "长期"}{g.revoked_at ? " · 已撤销" : ""}
  • ))}
) : null}

额度调整记录

{detail.adjustment_records.length ? (
    {detail.adjustment_records.map((r) => (
  • {r.amount > 0 ? "+" : ""}{r.amount.toLocaleString()} · {r.reason ?? "—"}
  • ))}
) : (

暂无调整记录

)}