feat(withdraw-detail): 抽屉占屏一半 + 金币记录页码分页 + 风险提示去分数 (#15)

- 详情抽屉宽度 720→50%
- 金币记录改 antd 页码分页(10/页,显示「共N条」)
- 风险提示去掉不可解释的分数;历史异常拆成「提现拒绝/提现失败」两类

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: zzhyyyyy <2685922758@qq.com>
Reviewed-on: #15
Co-authored-by: zhuzihao <zhuzihao@wonderable.ai>
Co-committed-by: zhuzihao <zhuzihao@wonderable.ai>
This commit was merged in pull request #15.
This commit is contained in:
2026-06-21 23:41:33 +08:00
committed by marco
parent 12cc6f7aa6
commit bd0bacb836
2 changed files with 28 additions and 25 deletions
+22 -20
View File
@@ -2,7 +2,6 @@
import { useCallback, useEffect, useState } from 'react';
import {
Button,
DatePicker,
Descriptions,
Radio,
@@ -35,6 +34,8 @@ const SOURCE_COLOR: Record<string, string> = {
signin: 'green',
};
const PAGE_SIZE = 10; // 金币记录每页条数
interface Props {
userId: number;
user: WithdrawUserSnapshot | null;
@@ -48,7 +49,8 @@ export default function UserRewardPanel({ userId, user }: Props) {
const [statsLoading, setStatsLoading] = useState(false);
const [records, setRecords] = useState<UserCoinRecord[]>([]);
const [nextCursor, setNextCursor] = useState<number | null>(null);
const [page, setPage] = useState(1);
const [total, setTotal] = useState(0);
const [recordsLoading, setRecordsLoading] = useState(false);
// 时间窗口参数:注册至今=不传;自定义区间(选齐两端)=date_from/date_to。
@@ -76,17 +78,15 @@ export default function UserRewardPanel({ userId, user }: Props) {
}, [userId, paramsKey]);
const loadRecords = useCallback(
async (cursor: number | null) => {
async (p: number) => {
setRecordsLoading(true);
try {
const reqParams: Record<string, unknown> = { ...JSON.parse(paramsKey), limit: 20 };
if (cursor != null) reqParams.cursor = cursor;
const { data } = await api.get<CursorPage<UserCoinRecord>>(
`/admin/api/users/${userId}/coin-records`,
{ params: reqParams },
{ params: { ...JSON.parse(paramsKey), limit: PAGE_SIZE, cursor: (p - 1) * PAGE_SIZE } },
);
setRecords((prev) => (cursor == null ? data.items : [...prev, ...data.items]));
setNextCursor(data.next_cursor);
setRecords(data.items);
setTotal(data.total ?? 0);
} catch (e) {
message.error(errMsg(e));
} finally {
@@ -96,9 +96,11 @@ export default function UserRewardPanel({ userId, user }: Props) {
[userId, paramsKey],
);
// 用户或时间筛选变化:回到第 1 页重新拉取
useEffect(() => {
loadStats();
loadRecords(null);
setPage(1);
loadRecords(1);
}, [loadStats, loadRecords]);
const regDays = user?.created_at ? dayjs().diff(apiTime(user.created_at), 'day') : null;
@@ -203,18 +205,18 @@ export default function UserRewardPanel({ userId, user }: Props) {
columns={columns}
dataSource={records}
loading={recordsLoading || statsLoading}
pagination={false}
pagination={{
current: page,
pageSize: PAGE_SIZE,
total,
showSizeChanger: false,
onChange: (p) => {
setPage(p);
loadRecords(p);
},
showTotal: (t) => `${t}`,
}}
/>
<div style={{ marginTop: 12, textAlign: 'center' }}>
<Button
size="small"
onClick={() => loadRecords(nextCursor)}
disabled={!nextCursor}
loading={recordsLoading}
>
{nextCursor ? '加载更多' : '没有更多了'}
</Button>
</div>
</div>
);
}
+6 -5
View File
@@ -149,9 +149,10 @@ function riskTags(detail: WithdrawDetail | null) {
if (detail.user?.status && detail.user.status !== 'active') tags.push(`账号状态:${detail.user.status}`);
if (detail.user && dayjs().diff(utcDayjs(detail.user.created_at), 'hour') < 24) tags.push('新注册用户');
if ((detail.user?.withdraw_total || 0) >= 3) tags.push('多次提现用户');
if (detail.recent_withdraws.some((o) => o.status === 'rejected' || o.status === 'failed')) {
tags.push('历史异常单');
}
const rejectedN = detail.recent_withdraws.filter((o) => o.status === 'rejected').length;
const failedN = detail.recent_withdraws.filter((o) => o.status === 'failed').length;
if (rejectedN) tags.push(`提现拒绝${rejectedN}`);
if (failedN) tags.push(`提现失败${failedN}`);
return tags;
}
@@ -858,7 +859,7 @@ export default function WithdrawsPage() {
<Drawer
title="提现详情"
width={720}
width="50%"
open={drawerOpen}
onClose={() => setDrawerOpen(false)}
extra={
@@ -900,7 +901,7 @@ export default function WithdrawsPage() {
<UserRewardPanel userId={detail.order.user_id} user={detail.user} />
<div>
<h3> {detail.risk_score ? <Tag color="red">{detail.risk_score}</Tag> : null}</h3>
<h3></h3>
{risks.length ? (
<Space wrap>
{risks.map((risk) => (