|
用户
|
toggleSort("points")} />
|
toggleSort("total_points")} />
|
等级
|
操作
|
{users.map((u) => (
|
{u.nickname || u.username}
@{u.username}
|
{fmt(u.points)}
|
{fmt(u.total_points)}
|
|
|
))}
{loading ?
加载中…
: null}
{!loading && error ?
{error}
: null}
{!loading && !error && loaded && users.length === 0 ? (
没有匹配的用户
) : null}
{!error && users.length > 0 ? (
void load({ page: p, q: appliedQ })} />
) : null}
{adjustTarget ? (
setAdjustTarget(null)}
onAdjusted={(balance, totalPoints, level) => {
patchUser(adjustTarget.id, { points: balance, total_points: totalPoints, level });
}}
/>
) : null}
{ledgerTarget ? (
setLedgerTarget(null)} />
) : null}
);
}
/** 调整弹窗:方向(加/减)+ 数量 + 理由;成功后回填余额/累计/等级 */
function AdjustModal({
user,
onClose,
onAdjusted,
}: {
user: AdminUser;
onClose: () => void;
onAdjusted: (balance: number, totalPoints: number, level: number) => void;
}) {
const [dir, setDir] = useState<"add" | "sub">("add");
const [amount, setAmount] = useState(10);
const [note, setNote] = useState("");
const [saving, setSaving] = useState(false);
const submit = async () => {
if (saving) return;
if (!Number.isInteger(amount) || amount < 1 || amount > MAX_DELTA) {
toast(`积分数量须为 1–${MAX_DELTA} 的整数`);
return;
}
const trimmed = note.trim();
if (!trimmed) {
toast("请填写调整理由");
return;
}
setSaving(true);
try {
const delta = dir === "add" ? amount : -amount;
const res = await apiAdminAdjustUserPoints(user.id, delta, trimmed);
onAdjusted(res.balance, res.total_points, res.level);
toast(
`已${dir === "add" ? "增加" : "扣减"} ${fmt(amount)} 积分:余额 ${fmt(res.balance)},当前 Lv.${res.level}`,
"ok"
);
onClose();
} catch (e) {
toast(e instanceof Error ? e.message : "积分调整失败");
} finally {
setSaving(false);
}
};
return (