import { ReloadOutlined } from '@ant-design/icons' import { useQuery, useQueryClient } from '@tanstack/react-query' import { App, Alert, Button, Card, Descriptions, Form, Input, InputNumber, Modal, Select, Space, Switch, Table, Tag, Typography, } from 'antd' import type { TableColumnsType } from 'antd' import { useEffect, useState } from 'react' import { useSearchParams } from 'react-router' import ImageUpload from '@/components/files/ImageUpload' import ImagePreviewList from '@/components/files/ImagePreviewList' import { fetchAdminWorkerFinanceConfig, fetchAdminWorkerFinanceRequests, reviewAdminWorkerFinanceRequest, saveAdminWorkerFinanceConfig, } from '@/services/admin' import type { UploadedFile, WorkerFinanceConfig, WorkerFinanceRequest, } from '@/types/worker-platform' import { AdminRangePicker } from '@/components/admin/AdminDatePicker' import dayjs, { ADMIN_DATE_TIME_FORMAT } from '@/lib/dayjs' import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination' import { formatAdminDateTime } from '@/utils/admin-time' import { asRecord, formatMoney } from './shared' type FinanceConfigFormValues = { depositUnfreezeDays?: number rewardUnfreezeEnabled?: boolean recharge?: { enabled?: boolean channelName?: string accountName?: string accountNo?: string qrCodeImageList?: UploadedFile[] instructions?: string } adminContact?: { wechatQrCodeImageList?: UploadedFile[] } withdraw?: { enabled?: boolean instructions?: string } } type FinanceReviewAction = 'approved' | 'rejected' | 'cancelled' export default function FinancePanel() { const { message } = App.useApp() const queryClient = useQueryClient() const [searchParams, setSearchParams] = useSearchParams() const [configForm] = Form.useForm() const [reviewForm] = Form.useForm<{ reviewedNote?: string }>() const [requestStatus, setRequestStatus] = useState('') const [targetRequestId, setTargetRequestId] = useState() const [requestType, setRequestType] = useState('') const [keywordInput, setKeywordInput] = useState('') const [keyword, setKeyword] = useState('') const [createdRange, setCreatedRange] = useState<[string, string] | null>(null) const [accountChannel, setAccountChannel] = useState('') const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(ADMIN_DEFAULT_PAGE_SIZE) const [savingConfig, setSavingConfig] = useState(false) const [configExpanded, setConfigExpanded] = useState(false) const [reviewing, setReviewing] = useState(false) const [reviewState, setReviewState] = useState<{ action: FinanceReviewAction request: WorkerFinanceRequest } | null>(null) const financeConfigQuery = useQuery({ queryKey: ['admin-worker-platform-finance-config'], queryFn: () => fetchAdminWorkerFinanceConfig(), }) useEffect(() => { const requestId = Number(searchParams.get('financeRequestId') || 0) if (!Number.isInteger(requestId) || requestId <= 0) return setTargetRequestId(requestId) setRequestStatus('') setRequestType('') setKeyword('') setKeywordInput('') setPage(1) setSearchParams( (current) => { current.delete('financeRequestId') return current }, { replace: true }, ) }, [searchParams, setSearchParams]) const financeRequestsQuery = useQuery({ queryKey: [ 'admin-worker-platform-finance-requests', requestStatus, requestType, targetRequestId, keyword, page, pageSize, createdRange ? createdRange[0] : '', createdRange ? createdRange[1] : '', accountChannel, ], queryFn: () => fetchAdminWorkerFinanceRequests({ status: requestStatus, requestType, requestId: targetRequestId, keyword, page, pageSize, accountChannel, ...(createdRange ? { createdFrom: createdRange[0], createdTo: createdRange[1] } : {}), }), }) const financeRequestsPagination = financeRequestsQuery.data?.data.pagination useEffect(() => { const config = financeConfigQuery.data?.data if (!config) { return } configForm.setFieldsValue(mapFinanceConfigToFormValues(config)) }, [financeConfigQuery.data, configForm]) async function refreshAll() { await Promise.all([ queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-finance-config'], }), queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-finance-requests'], }), queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-workers'], }), ]) } async function submitFinanceConfig(values: FinanceConfigFormValues) { setSavingConfig(true) try { await saveAdminWorkerFinanceConfig({ depositUnfreezeDays: Number(values.depositUnfreezeDays ?? 3), rewardUnfreezeEnabled: values.rewardUnfreezeEnabled !== false, recharge: { enabled: values.recharge?.enabled !== false, channelName: String(values.recharge?.channelName || '').trim(), accountName: String(values.recharge?.accountName || '').trim(), accountNo: String(values.recharge?.accountNo || '').trim(), qrCodeImage: values.recharge?.qrCodeImageList?.[0] || null, instructions: String(values.recharge?.instructions || '').trim(), }, adminContact: { wechatQrCodeImage: values.adminContact?.wechatQrCodeImageList?.[0] || null, }, withdraw: { enabled: values.withdraw?.enabled !== false, instructions: String(values.withdraw?.instructions || '').trim(), }, }) message.success('资金配置已保存') await refreshAll() } catch (error) { message.error(error instanceof Error ? error.message : '保存资金配置失败') } finally { setSavingConfig(false) } } function openReviewModal(request: WorkerFinanceRequest, action: FinanceReviewAction) { setReviewState({ request, action }) reviewForm.setFieldsValue({ reviewedNote: '' }) } async function submitFinanceReview(values: { reviewedNote?: string }) { if (!reviewState) return setReviewing(true) try { await reviewAdminWorkerFinanceRequest(reviewState.request.requestId, { status: reviewState.action, reviewedNote: String(values.reviewedNote || '').trim(), }) message.success(`${formatFinanceReviewAction(reviewState.action)}成功`) setReviewState(null) reviewForm.resetFields() await refreshAll() } catch (error) { message.error(error instanceof Error ? error.message : '处理资金申请失败') } finally { setReviewing(false) } } const requestColumns: TableColumnsType = [ { title: '打手', minWidth: 250, render: (_, row) => (
{row.worker?.displayName || row.worker?.username || '-'} {row.worker?.username || '-'} {row.worker ? ( {row.worker.workerType === 'internal' ? '内部打手' : '外部打手'} {row.worker.levelName || row.worker.levelKey || '未分级'} 提现 {row.worker.withdrawRequestCount} 次,累计 {formatMoney(row.worker.totalWithdrawAmount)} ) : null}
), }, { title: '类型', width: 120, render: (_, row) => ( {formatFinanceRequestType(row.requestType)} ), }, { title: '金额', width: 120, render: (_, row) => formatMoney(row.amount), }, { title: '收款信息', minWidth: 240, render: (_, row) => renderFinanceRequestAccount(row), }, { title: '凭证/收款码', minWidth: 140, render: (_, row) => ( ), }, { title: '备注', minWidth: 260, render: (_, row) => (
{row.note || '-'} {row.reviewedNote ? ( 审核备注:{row.reviewedNote} ) : null}
), }, { title: '状态', width: 120, render: (_, row) => ( {formatFinanceRequestStatus(row.status)} ), }, { title: '时间', width: 180, render: (_, row) => (
{formatAdminDateTime(row.createdAt)} {row.reviewedAt ? `审核:${formatAdminDateTime(row.reviewedAt)}${resolveFinanceReviewer(row) ? `(${resolveFinanceReviewer(row)})` : ''}` : '待审核'}
), }, { title: '操作', width: 240, render: (_, row) => row.status === 'pending' ? ( ) : ( 已处理 ), }, ] return (
{targetRequestId ? ( setTargetRequestId(undefined)}> 取消定位 } /> ) : null} } > {configExpanded && financeConfigQuery.error ? ( {financeConfigQuery.error instanceof Error ? financeConfigQuery.error.message : '读取资金配置失败'} ) : null} {configExpanded ? (
解冻期间如订单出现问题,可在"接单工单"或"售后问题单"中优先扣减本单待解冻金额。
) : null}
setKeywordInput(event.target.value)} onSearch={(value) => { setKeyword(String(value || '').trim()) setPage(1) }} /> { setRequestStatus(nextStatus) setPage(1) }} />