feat: 提现优化 — 收款方式选择、收款码上传、取消按钮 (HIF-43)

- 用户端申请提现表单:新增收款方式选择器(支付宝/微信/银行卡/其他)
  收款账号输入框及收款码图片上传,调用 POST /v1/public/file/upload
- 用户端提现记录:展示收款方式、账号、收款码(可放大),新增已取消
  状态(灰色),待审核记录显示取消申请按钮调用 withdrawal_cancel
- 管理端提现列表:新增收款方式、账号、收款码列,支持按收款方式过滤,
  状态过滤增加已取消选项
- 类型更新:WithdrawalLog / AdminWithdrawalLog / CommissionWithdrawRequest
  增加 method / account / qr_code_url 字段;新增 WithdrawalCancelRequest
This commit is contained in:
2026-05-25 20:39:04 -07:00
parent cad75b92bc
commit 21e351a727
5 changed files with 467 additions and 68 deletions
+84 -5
View File
@@ -40,6 +40,13 @@ import { Display } from "@/components/display";
import { UserDetail } from "@/sections/user/user-detail";
import { formatDate } from "@/utils/common";
const WITHDRAWAL_METHOD_MAP: Record<number, string> = {
0: "其他",
1: "支付宝",
2: "微信",
3: "银行卡",
};
const rejectSchema = z.object({
reason: z
.string()
@@ -48,11 +55,40 @@ const rejectSchema = z.object({
.max(500, "拒绝原因不能超过 500 个字符"),
});
function QrCodeCell({ url }: { url?: string }) {
const [zoomOpen, setZoomOpen] = useState(false);
if (!url) return <span>--</span>;
return (
<>
<button
className="block overflow-hidden rounded border"
onClick={() => setZoomOpen(true)}
type="button"
>
<img
alt="收款码"
className="h-12 w-12 cursor-zoom-in object-cover"
src={url}
/>
</button>
{zoomOpen && (
<Dialog onOpenChange={setZoomOpen} open={zoomOpen}>
<DialogContent className="max-w-sm">
<img alt="收款码" className="w-full" src={url} />
</DialogContent>
</Dialog>
)}
</>
);
}
export default function WithdrawalPage() {
const { t } = useTranslation("user");
const ref = useRef<ProTableActions>(null);
const [rejectOpen, setRejectOpen] = useState(false);
const [currentRow, setCurrentRow] = useState<API.WithdrawalLog | null>(null);
const [currentRow, setCurrentRow] = useState<API.AdminWithdrawalLog | null>(
null
);
const form = useForm<z.infer<typeof rejectSchema>>({
resolver: zodResolver(rejectSchema),
defaultValues: {
@@ -93,6 +129,14 @@ export default function WithdrawalPage() {
{ value: "0", label: "审核中" },
{ value: "1", label: "已通过" },
{ value: "2", label: "已拒绝" },
{ value: "3", label: "已取消" },
];
const methodOptions = [
{ value: "1", label: "支付宝" },
{ value: "2", label: "微信" },
{ value: "3", label: "银行卡" },
{ value: "0", label: "其他" },
];
const statusMap: Record<number, { label: string; className: string }> = {
@@ -108,11 +152,18 @@ export default function WithdrawalPage() {
label: "已拒绝",
className: "bg-rose-100 text-rose-700 border-rose-200",
},
3: {
label: "已取消",
className: "bg-gray-100 text-gray-500 border-gray-200",
},
};
return (
<>
<ProTable<API.WithdrawalLog, { status?: string; user_id?: string }>
<ProTable<
API.AdminWithdrawalLog,
{ status?: string; user_id?: string; method?: string }
>
action={ref}
actions={{
render: (row) => {
@@ -170,10 +221,32 @@ export default function WithdrawalPage() {
),
},
{
accessorKey: "content",
header: "收款信息",
accessorKey: "method",
header: "收款方式",
cell: ({ row }) =>
WITHDRAWAL_METHOD_MAP[row.original.method] ?? "--",
},
{
accessorKey: "account",
header: "收款账号",
cell: ({ row }) => (
<div className="max-w-[360px] break-all text-sm">
<div className="max-w-[200px] break-all text-sm">
{row.original.account || "--"}
</div>
),
},
{
accessorKey: "qr_code_url",
header: "收款码",
cell: ({ row }) => (
<QrCodeCell url={row.original.qr_code_url} />
),
},
{
accessorKey: "content",
header: "备注",
cell: ({ row }) => (
<div className="max-w-[200px] break-all text-sm">
{row.original.content || "--"}
</div>
),
@@ -216,6 +289,11 @@ export default function WithdrawalPage() {
options: statusOptions,
placeholder: "状态",
},
{
key: "method",
options: methodOptions,
placeholder: "收款方式",
},
{
key: "user_id",
placeholder: "用户 ID",
@@ -226,6 +304,7 @@ export default function WithdrawalPage() {
page: pagination.page,
size: pagination.size,
status: filter.status ? Number(filter.status) : undefined,
method: filter.method ? Number(filter.method) : undefined,
user_id: filter.user_id ? Number(filter.user_id) : undefined,
});