feat(admin): 新增后台提现审核与打款确认
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
import { useState } from 'react'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { App, Button, Image, Input, Modal, Space, Tag, Typography } from 'antd'
|
||||
import { CheckOutlined, CloseOutlined, WalletOutlined } from '@ant-design/icons'
|
||||
|
||||
import {
|
||||
confirmAdminWithdrawalPayment,
|
||||
fetchAdminWithdrawal,
|
||||
reviewAdminWithdrawal,
|
||||
} from '@/features/admin/api'
|
||||
import { DetailDrawer, type DetailSection } from '@/features/admin/components/DetailDrawer'
|
||||
import { StatusTag } from '@/features/admin/components/StatusTag'
|
||||
import { readStatusMeta, withdrawalStatusMap } from '@/features/admin/labels'
|
||||
import type { AdminWithdrawal } from '@/features/admin/types'
|
||||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||
import { emptyText, formatDateTime, formatMoney, formatText } from '@/shared/utils/format'
|
||||
|
||||
type WithdrawalAction = 'approve' | 'reject' | 'pay'
|
||||
|
||||
const actionCopy: Record<WithdrawalAction, { title: string; okText: string; hint: string }> = {
|
||||
approve: {
|
||||
title: '通过提现审核',
|
||||
okText: '通过审核',
|
||||
hint: '通过后进入待打款状态,请确认收款账户与金额无误。',
|
||||
},
|
||||
reject: {
|
||||
title: '驳回提现申请',
|
||||
okText: '确认驳回',
|
||||
hint: '驳回后资金会退回用户可用余额,请填写驳回原因。',
|
||||
},
|
||||
pay: {
|
||||
title: '确认已打款',
|
||||
okText: '确认打款',
|
||||
hint: '确认后提现单完结;建议填写打款备注或凭证地址,便于对账。',
|
||||
},
|
||||
}
|
||||
|
||||
function buildSections(item: AdminWithdrawal): DetailSection[] {
|
||||
const sections: DetailSection[] = [
|
||||
{
|
||||
key: 'overview',
|
||||
title: '申请概览',
|
||||
fields: [
|
||||
{
|
||||
key: 'withdraw_no',
|
||||
label: '提现单号',
|
||||
value: <span className="admin-mono">{item.withdraw_no}</span>,
|
||||
},
|
||||
{
|
||||
key: 'status',
|
||||
label: '提现状态',
|
||||
value: <StatusTag meta={readStatusMeta(withdrawalStatusMap, item.status)} />,
|
||||
},
|
||||
{ key: 'user', label: '用户', value: `${item.user_nickname || '未设置昵称'} · ${item.user_phone}` },
|
||||
{ key: 'user_id', label: '用户 ID', value: `UID ${item.user_id}` },
|
||||
{
|
||||
key: 'site',
|
||||
label: '来源分站',
|
||||
value: item.distribution_site_name
|
||||
? `${item.distribution_site_name}${item.distribution_site_code ? ` · ${item.distribution_site_code}` : ''}`
|
||||
: '主站',
|
||||
},
|
||||
{ key: 'created', label: '申请时间', value: formatDateTime(item.created_at) },
|
||||
{ key: 'updated', label: '更新时间', value: formatDateTime(item.updated_at) },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'amount',
|
||||
title: '提现金额',
|
||||
fields: [
|
||||
{ key: 'amount', label: '申请金额', value: formatMoney(item.amount_cent) },
|
||||
{ key: 'fee', label: '手续费', value: formatMoney(item.fee_cent) },
|
||||
{ key: 'actual', label: '实际到账', value: formatMoney(item.actual_amount_cent) },
|
||||
{
|
||||
key: 'account_id',
|
||||
label: '收款账户 ID',
|
||||
value: item.payment_account_id ? `#${item.payment_account_id}` : emptyText,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'account',
|
||||
title: '收款账户',
|
||||
fields: [
|
||||
{ key: 'type', label: '账户类型', value: formatText(item.account_type) },
|
||||
{ key: 'name', label: '收款人', value: formatText(item.account_name) },
|
||||
{
|
||||
key: 'no',
|
||||
label: '收款账号',
|
||||
value: <span className="admin-mono">{formatText(item.account_no)}</span>,
|
||||
span: 2,
|
||||
},
|
||||
{ key: 'bank', label: '开户银行', value: formatText(item.bank_name) },
|
||||
{ key: 'branch', label: '开户支行', value: formatText(item.bank_branch) },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'audit',
|
||||
title: '审核与打款',
|
||||
fields: [
|
||||
{ key: 'reviewed_by', label: '审核人', value: formatText(item.reviewed_by_name) },
|
||||
{ key: 'reviewed_at', label: '审核时间', value: formatDateTime(item.reviewed_at) },
|
||||
{ key: 'review_remark', label: '审核备注', value: formatText(item.review_remark), span: 2 },
|
||||
{ key: 'paid_by', label: '打款人', value: formatText(item.paid_by_name) },
|
||||
{ key: 'paid_at', label: '打款时间', value: formatDateTime(item.paid_at) },
|
||||
{ key: 'payment_remark', label: '打款备注', value: formatText(item.payment_remark), span: 2 },
|
||||
{
|
||||
key: 'proof',
|
||||
label: '打款凭证',
|
||||
span: 2,
|
||||
value: item.payment_proof_url ? (
|
||||
<a href={item.payment_proof_url} target="_blank" rel="noreferrer">
|
||||
查看打款凭证
|
||||
</a>
|
||||
) : (
|
||||
emptyText
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
if (item.certificate_urls?.length > 0) {
|
||||
sections.push({
|
||||
key: 'certificates',
|
||||
title: '收款凭证',
|
||||
fields: [
|
||||
{
|
||||
key: 'images',
|
||||
label: '凭证图片',
|
||||
span: 2,
|
||||
value: (
|
||||
<Image.PreviewGroup>
|
||||
<Space size={8} wrap>
|
||||
{item.certificate_urls.map((url, index) => (
|
||||
<Image
|
||||
key={url}
|
||||
src={url}
|
||||
alt={`收款凭证 ${index + 1}`}
|
||||
width={120}
|
||||
height={90}
|
||||
style={{ objectFit: 'cover', borderRadius: 6 }}
|
||||
/>
|
||||
))}
|
||||
</Space>
|
||||
</Image.PreviewGroup>
|
||||
),
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
return sections
|
||||
}
|
||||
|
||||
interface AdminWithdrawalDetailDrawerProps {
|
||||
withdrawalId: number | null
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
onChanged?: () => void
|
||||
}
|
||||
|
||||
export function AdminWithdrawalDetailDrawer({
|
||||
withdrawalId,
|
||||
open,
|
||||
onClose,
|
||||
onChanged,
|
||||
}: AdminWithdrawalDetailDrawerProps) {
|
||||
const { message } = App.useApp()
|
||||
const permissions = useAdminSessionStore(state => state.admin?.permissions)
|
||||
const can = (code: string) => Boolean(permissions?.includes('*') || permissions?.includes(code))
|
||||
const [action, setAction] = useState<WithdrawalAction | null>(null)
|
||||
const [remark, setRemark] = useState('')
|
||||
const [proofURL, setProofURL] = useState('')
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: ['admin-withdrawal', withdrawalId],
|
||||
queryFn: () => fetchAdminWithdrawal(withdrawalId as number),
|
||||
enabled: open && Boolean(withdrawalId),
|
||||
})
|
||||
const item = query.data
|
||||
|
||||
const openAction = (next: WithdrawalAction) => {
|
||||
setRemark('')
|
||||
setProofURL('')
|
||||
setAction(next)
|
||||
}
|
||||
|
||||
const closeAction = () => {
|
||||
if (submitting) return
|
||||
setAction(null)
|
||||
}
|
||||
|
||||
const submit = async () => {
|
||||
if (!item || !action) return
|
||||
const text = remark.trim()
|
||||
if (action === 'reject' && !text) {
|
||||
message.warning('请填写驳回原因')
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
try {
|
||||
if (action === 'approve') {
|
||||
await reviewAdminWithdrawal(item.id, true, text)
|
||||
message.success('提现已通过审核')
|
||||
} else if (action === 'reject') {
|
||||
await reviewAdminWithdrawal(item.id, false, text)
|
||||
message.success('提现已驳回')
|
||||
} else {
|
||||
await confirmAdminWithdrawalPayment(item.id, {
|
||||
remark: text || undefined,
|
||||
payment_proof_url: proofURL.trim() || undefined,
|
||||
})
|
||||
message.success('已确认打款')
|
||||
}
|
||||
setAction(null)
|
||||
await query.refetch()
|
||||
onChanged?.()
|
||||
} catch (error) {
|
||||
const text =
|
||||
error && typeof error === 'object' && 'response' in error
|
||||
? ((error as { response?: { data?: { message?: string } } }).response?.data?.message ??
|
||||
'操作失败')
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: '操作失败'
|
||||
message.error(text)
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
const copy = action ? actionCopy[action] : null
|
||||
const canReview = item?.status === 'pending' && can('withdrawal:review')
|
||||
const canPay = item?.status === 'processing' && can('withdrawal:pay')
|
||||
|
||||
return (
|
||||
<>
|
||||
<DetailDrawer
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
loading={query.isPending && Boolean(withdrawalId)}
|
||||
error={query.isError ? '提现详情没有加载出来,请重试或重新登录后台。' : null}
|
||||
onRetry={() => void query.refetch()}
|
||||
title={item ? item.withdraw_no : '提现详情'}
|
||||
subtitle={
|
||||
item ? (
|
||||
<span>
|
||||
{item.user_nickname || item.user_phone} · {formatMoney(item.actual_amount_cent)} 到账
|
||||
</span>
|
||||
) : null
|
||||
}
|
||||
tags={
|
||||
item ? (
|
||||
<Space size={6} wrap>
|
||||
<StatusTag meta={readStatusMeta(withdrawalStatusMap, item.status)} />
|
||||
{item.distribution_site_name ? <Tag>{item.distribution_site_name}</Tag> : null}
|
||||
</Space>
|
||||
) : null
|
||||
}
|
||||
sections={item ? buildSections(item) : []}
|
||||
footer={
|
||||
item ? (
|
||||
<Space wrap>
|
||||
{canReview ? (
|
||||
<>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<CheckOutlined />}
|
||||
onClick={() => openAction('approve')}
|
||||
>
|
||||
通过审核
|
||||
</Button>
|
||||
<Button danger icon={<CloseOutlined />} onClick={() => openAction('reject')}>
|
||||
驳回
|
||||
</Button>
|
||||
</>
|
||||
) : null}
|
||||
{canPay ? (
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<WalletOutlined />}
|
||||
onClick={() => openAction('pay')}
|
||||
>
|
||||
确认已打款
|
||||
</Button>
|
||||
) : null}
|
||||
<Button onClick={onClose}>关闭</Button>
|
||||
</Space>
|
||||
) : (
|
||||
<Button onClick={onClose}>关闭</Button>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
open={action !== null}
|
||||
title={copy?.title}
|
||||
okText={copy?.okText}
|
||||
cancelText="取消"
|
||||
confirmLoading={submitting}
|
||||
okButtonProps={{ danger: action === 'reject' }}
|
||||
onOk={() => void submit()}
|
||||
onCancel={closeAction}
|
||||
>
|
||||
<Typography.Paragraph type="secondary">{copy?.hint}</Typography.Paragraph>
|
||||
{item ? (
|
||||
<Typography.Paragraph>
|
||||
提现单 {item.withdraw_no},申请 {formatMoney(item.amount_cent)},到账{' '}
|
||||
{formatMoney(item.actual_amount_cent)},收款人 {item.account_name}(
|
||||
{item.account_no})。
|
||||
</Typography.Paragraph>
|
||||
) : null}
|
||||
<Input.TextArea
|
||||
rows={3}
|
||||
value={remark}
|
||||
maxLength={200}
|
||||
showCount
|
||||
placeholder={action === 'reject' ? '驳回原因(必填)' : '备注(可选)'}
|
||||
onChange={event => setRemark(event.target.value)}
|
||||
/>
|
||||
{action === 'pay' ? (
|
||||
<Input
|
||||
style={{ marginTop: 12 }}
|
||||
value={proofURL}
|
||||
placeholder="打款凭证地址(可选)"
|
||||
onChange={event => setProofURL(event.target.value)}
|
||||
/>
|
||||
) : null}
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
||||
import { Alert, Button, Select, Space, Tag, Typography, type TableColumnsType } from 'antd'
|
||||
import { EyeOutlined, ReloadOutlined } from '@ant-design/icons'
|
||||
|
||||
import { fetchAdminWithdrawals } from '@/features/admin/api'
|
||||
import { AdminWithdrawalDetailDrawer } from '@/features/admin/AdminWithdrawalDetailDrawer'
|
||||
import { AdminPageHeader } from '@/features/admin/components/AdminPageHeader'
|
||||
import { DataTable } from '@/features/admin/components/DataTable'
|
||||
import { FilterBar, FilterField } from '@/features/admin/components/FilterBar'
|
||||
import { StatusTag } from '@/features/admin/components/StatusTag'
|
||||
import { readStatusMeta, withdrawalStatusMap, withdrawalStatusOptions } from '@/features/admin/labels'
|
||||
import type { AdminWithdrawal, AdminWithdrawalQuery } from '@/features/admin/types'
|
||||
import { useAdminSessionStore } from '@/stores/adminSession'
|
||||
import { formatDateTime, formatMoney, formatText } from '@/shared/utils/format'
|
||||
|
||||
const emptyFilters: AdminWithdrawalQuery = {
|
||||
status: '',
|
||||
}
|
||||
|
||||
function buildColumns(
|
||||
onOpen: (row: AdminWithdrawal) => void,
|
||||
canView: boolean
|
||||
): TableColumnsType<AdminWithdrawal> {
|
||||
return [
|
||||
{
|
||||
title: '提现单',
|
||||
dataIndex: 'withdraw_no',
|
||||
width: 230,
|
||||
fixed: 'left',
|
||||
render: (_, row) => (
|
||||
<div className="admin-stack">
|
||||
{canView ? (
|
||||
<button type="button" className="admin-order-link" onClick={() => onOpen(row)}>
|
||||
{row.withdraw_no}
|
||||
</button>
|
||||
) : (
|
||||
<span className="admin-mono">{row.withdraw_no}</span>
|
||||
)}
|
||||
<strong>{row.user_nickname || row.user_phone || `UID ${row.user_id}`}</strong>
|
||||
<span>
|
||||
{formatText(row.user_phone)}
|
||||
{row.distribution_site_name ? ` · ${row.distribution_site_name}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '提现金额',
|
||||
key: 'amount',
|
||||
width: 150,
|
||||
align: 'right',
|
||||
render: (_, row) => (
|
||||
<div className="admin-amount">
|
||||
<strong>{formatMoney(row.amount_cent)}</strong>
|
||||
<span>手续费 {formatMoney(row.fee_cent)}</span>
|
||||
<span className="admin-amount__accent">到账 {formatMoney(row.actual_amount_cent)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '收款账户',
|
||||
key: 'account',
|
||||
width: 220,
|
||||
render: (_, row) => (
|
||||
<div className="admin-stack">
|
||||
<span>
|
||||
{formatText(row.account_type)} · {formatText(row.account_name)}
|
||||
</span>
|
||||
<span className="admin-mono">{formatText(row.account_no)}</span>
|
||||
<span>
|
||||
{formatText(row.bank_name)}
|
||||
{row.bank_branch ? ` · ${row.bank_branch}` : ''}
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
width: 110,
|
||||
render: value => <StatusTag meta={readStatusMeta(withdrawalStatusMap, value)} />,
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
dataIndex: 'created_at',
|
||||
width: 170,
|
||||
render: value => formatDateTime(value),
|
||||
},
|
||||
{
|
||||
title: '审核 / 打款',
|
||||
key: 'audit',
|
||||
width: 190,
|
||||
render: (_, row) => (
|
||||
<div className="admin-stack">
|
||||
<span>审核 {row.reviewed_by_name || '—'}</span>
|
||||
<span>打款 {row.paid_by_name || '—'}</span>
|
||||
<span>{formatDateTime(row.paid_at || row.reviewed_at)}</span>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 96,
|
||||
fixed: 'right',
|
||||
render: (_, row) =>
|
||||
canView ? (
|
||||
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => onOpen(row)}>
|
||||
处理
|
||||
</Button>
|
||||
) : (
|
||||
<Typography.Text type="secondary">无权限</Typography.Text>
|
||||
),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function AdminWithdrawalsPage() {
|
||||
const [draft, setDraft] = useState<AdminWithdrawalQuery>(emptyFilters)
|
||||
const [applied, setApplied] = useState<AdminWithdrawalQuery>(emptyFilters)
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(20)
|
||||
const [detailId, setDetailId] = useState<number | null>(null)
|
||||
const [detailOpen, setDetailOpen] = useState(false)
|
||||
const permissions = useAdminSessionStore(state => state.admin?.permissions)
|
||||
const canView = Boolean(
|
||||
permissions?.includes('*') ||
|
||||
permissions?.includes('withdrawal:list') ||
|
||||
permissions?.includes('withdrawal:detail')
|
||||
)
|
||||
|
||||
const { data, isPending, isFetching, isError, error, refetch } = useQuery({
|
||||
queryKey: ['admin-withdrawals', applied, page, pageSize],
|
||||
queryFn: () => fetchAdminWithdrawals({ ...applied, page, page_size: pageSize }),
|
||||
placeholderData: keepPreviousData,
|
||||
})
|
||||
|
||||
const columns = useMemo(() => buildColumns(row => {
|
||||
setDetailId(row.id)
|
||||
setDetailOpen(true)
|
||||
}, canView), [canView])
|
||||
|
||||
const patchFilter = (patch: Partial<AdminWithdrawalQuery>) => {
|
||||
setDraft(current => ({ ...current, ...patch }))
|
||||
}
|
||||
|
||||
const applyFilters = () => {
|
||||
setApplied({ ...draft })
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
const resetFilters = () => {
|
||||
setDraft(emptyFilters)
|
||||
setApplied(emptyFilters)
|
||||
setPage(1)
|
||||
}
|
||||
|
||||
const hasActiveFilters = Boolean(draft.status)
|
||||
const errorText = error instanceof Error ? error.message : '请稍后重试或重新登录后台。'
|
||||
|
||||
return (
|
||||
<div className="admin-page">
|
||||
<AdminPageHeader
|
||||
eyebrow="JIEYOU OPS · 财务"
|
||||
title="提现审核"
|
||||
description="核对收款账户与金额,审核通过后进入待打款,再确认实际打款结果。"
|
||||
meta={
|
||||
<Space size={8} wrap>
|
||||
<Tag color="geekblue">共 {data?.total ?? 0} 笔</Tag>
|
||||
{isFetching && !isPending ? <Tag color="processing">正在更新</Tag> : null}
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Button icon={<ReloadOutlined />} onClick={() => void refetch()} loading={isFetching}>
|
||||
刷新数据
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
<FilterBar
|
||||
onSearch={applyFilters}
|
||||
onReset={hasActiveFilters ? resetFilters : undefined}
|
||||
loading={isFetching}
|
||||
>
|
||||
<FilterField label="提现状态" width={180}>
|
||||
<Select
|
||||
allowClear
|
||||
value={draft.status || undefined}
|
||||
placeholder="全部状态"
|
||||
options={withdrawalStatusOptions}
|
||||
onChange={value => patchFilter({ status: value ?? '' })}
|
||||
/>
|
||||
</FilterField>
|
||||
</FilterBar>
|
||||
|
||||
{isError ? (
|
||||
<Alert
|
||||
type="error"
|
||||
showIcon
|
||||
className="admin-table-alert"
|
||||
message="提现列表没有加载出来"
|
||||
description={errorText}
|
||||
action={
|
||||
<Button size="small" onClick={() => void refetch()}>
|
||||
重试
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<DataTable<AdminWithdrawal>
|
||||
title={<span>提现申请</span>}
|
||||
extra={
|
||||
<Typography.Text type="secondary">
|
||||
{data ? `第 ${data.page} 页 · 当前 ${data.items.length} 条` : '加载中'}
|
||||
</Typography.Text>
|
||||
}
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={data?.items ?? []}
|
||||
loading={isFetching}
|
||||
total={data?.total ?? 0}
|
||||
page={data?.page ?? page}
|
||||
pageSize={data?.page_size ?? pageSize}
|
||||
onPageChange={(nextPage, nextPageSize) => {
|
||||
setPage(nextPageSize !== pageSize ? 1 : nextPage)
|
||||
setPageSize(nextPageSize)
|
||||
}}
|
||||
onRowClick={canView ? row => {
|
||||
setDetailId(row.id)
|
||||
setDetailOpen(true)
|
||||
} : undefined}
|
||||
emptyDescription="没有匹配的提现申请"
|
||||
/>
|
||||
|
||||
{canView ? (
|
||||
<AdminWithdrawalDetailDrawer
|
||||
withdrawalId={detailId}
|
||||
open={detailOpen}
|
||||
onClose={() => setDetailOpen(false)}
|
||||
onChanged={() => {
|
||||
void refetch()
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -13,6 +13,9 @@ import type {
|
||||
AdminUserItem,
|
||||
AdminUserPage,
|
||||
AdminUserQuery,
|
||||
AdminWithdrawal,
|
||||
AdminWithdrawalPage,
|
||||
AdminWithdrawalQuery,
|
||||
DashboardData,
|
||||
} from '@/features/admin/types'
|
||||
|
||||
@@ -232,3 +235,46 @@ export async function approveAdminRefund(id: number | string) {
|
||||
export async function rejectAdminRefund(id: number | string, action: 'restore' | 'close') {
|
||||
await adminClient.post(`/admin/orders/${id}/refund/reject`, { action })
|
||||
}
|
||||
|
||||
export async function fetchAdminWithdrawals(query: AdminWithdrawalQuery) {
|
||||
const { data } = await adminClient.get<ApiResponse<Partial<AdminWithdrawalPage>>>(
|
||||
'/admin/withdrawals',
|
||||
{ params: compactParams(query) }
|
||||
)
|
||||
return {
|
||||
items: data.data.items ?? [],
|
||||
total: data.data.total ?? 0,
|
||||
page: data.data.page ?? query.page ?? 1,
|
||||
page_size: data.data.page_size ?? query.page_size ?? 20,
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchAdminWithdrawal(id: number | string) {
|
||||
const { data } = await adminClient.get<ApiResponse<AdminWithdrawal>>(
|
||||
`/admin/withdrawals/${id}`
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function reviewAdminWithdrawal(
|
||||
id: number | string,
|
||||
approved: boolean,
|
||||
remark: string
|
||||
) {
|
||||
const { data } = await adminClient.post<ApiResponse<AdminWithdrawal>>(
|
||||
`/admin/withdrawals/${id}/review`,
|
||||
{ approved, remark }
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function confirmAdminWithdrawalPayment(
|
||||
id: number | string,
|
||||
payload: { payment_proof_url?: string; remark?: string }
|
||||
) {
|
||||
const { data } = await adminClient.post<ApiResponse<AdminWithdrawal>>(
|
||||
`/admin/withdrawals/${id}/confirm-payment`,
|
||||
payload
|
||||
)
|
||||
return data.data
|
||||
}
|
||||
|
||||
@@ -179,3 +179,13 @@ export const riskStatusMap: Record<string, StatusMeta> = {
|
||||
export const userStatusOptions = toOptions(userStatusMap)
|
||||
export const realnameStatusOptions = toOptions(realnameStatusMap)
|
||||
export const riskStatusOptions = toOptions(riskStatusMap)
|
||||
|
||||
export const withdrawalStatusMap: Record<string, StatusMeta> = {
|
||||
pending: { label: '待审核', tone: 'gold' },
|
||||
processing: { label: '待打款', tone: 'blue' },
|
||||
completed: { label: '已完成', tone: 'success' },
|
||||
rejected: { label: '已驳回', tone: 'red' },
|
||||
cancelled: { label: '已取消', tone: 'default' },
|
||||
}
|
||||
|
||||
export const withdrawalStatusOptions = toOptions(withdrawalStatusMap)
|
||||
|
||||
@@ -304,3 +304,50 @@ export interface AdminUserPage {
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export interface AdminWithdrawalQuery {
|
||||
page?: number
|
||||
page_size?: number
|
||||
status?: string
|
||||
user_id?: number
|
||||
}
|
||||
|
||||
export interface AdminWithdrawal {
|
||||
id: number
|
||||
withdraw_no: string
|
||||
user_id: number
|
||||
distribution_site_id: number
|
||||
distribution_site_name: string
|
||||
distribution_site_code: string
|
||||
user_nickname: string
|
||||
user_phone: string
|
||||
amount_cent: number
|
||||
fee_cent: number
|
||||
actual_amount_cent: number
|
||||
payment_account_id?: number | null
|
||||
account_type: string
|
||||
account_name: string
|
||||
account_no: string
|
||||
bank_name: string
|
||||
bank_branch: string
|
||||
certificate_urls: string[]
|
||||
status: string
|
||||
reviewed_by?: number | null
|
||||
reviewed_by_name: string
|
||||
reviewed_at?: string | null
|
||||
review_remark: string
|
||||
paid_by?: number | null
|
||||
paid_by_name: string
|
||||
paid_at?: string | null
|
||||
payment_proof_url: string
|
||||
payment_remark: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface AdminWithdrawalPage {
|
||||
items: AdminWithdrawal[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user