439 lines
14 KiB
TypeScript
439 lines
14 KiB
TypeScript
import { useState } from 'react'
|
|
import { Card, Descriptions, Drawer, Select, Space, Table, Tabs, Tag, Typography } from 'antd'
|
|
import type { TableColumnsType } from 'antd'
|
|
import { useQuery } from '@tanstack/react-query'
|
|
|
|
import {
|
|
fetchAdminWorkOrders,
|
|
fetchAdminWorkerDetail,
|
|
fetchAdminWorkerDepositUnfreezes,
|
|
fetchAdminWorkerWalletLedgers,
|
|
} from '@/services/admin'
|
|
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
|
import { formatAdminDateTime } from '@/utils/admin-time'
|
|
import type {
|
|
WorkOrder,
|
|
WorkerDepositUnfreeze,
|
|
WorkerUser,
|
|
WorkerWalletLedger,
|
|
} from '@/types/worker-platform'
|
|
import { formatMoney, formatStatus, formatWorkerStatus, resolveStatusColor } from './shared'
|
|
|
|
/** 与打手端 worker-profile-view-utils 的流水文案保持一致;未知类型原样展示。 */
|
|
const LEDGER_TYPE_LABELS: Record<string, string> = {
|
|
manual_credit: '人工充值',
|
|
withdraw_freeze: '提现冻结',
|
|
withdraw_paid: '提现打款',
|
|
withdraw_release: '提现退回',
|
|
deposit_freeze: '冻结押金',
|
|
deposit_release: '释放押金',
|
|
deposit_pending_unfreeze: '待解冻金额',
|
|
reward_pending_unfreeze: '报酬待解冻',
|
|
deposit_unfreeze: '押金已解冻',
|
|
reward_unfreeze: '报酬已解冻',
|
|
deposit_deduction: '扣除押金',
|
|
reward_settlement: '结算报酬',
|
|
sharing_reward: '拼单报酬',
|
|
after_sales_deposit_deduction: '售后扣减押金',
|
|
after_sales_balance_deduction: '售后扣减余额',
|
|
after_sales_debt_offset: '抵扣售后欠款',
|
|
after_sales_refund: '售后追缴退还',
|
|
}
|
|
|
|
function formatLedgerType(ledgerType: string) {
|
|
return LEDGER_TYPE_LABELS[ledgerType] || ledgerType || '-'
|
|
}
|
|
|
|
function resolveLedgerTagColor(ledgerType: string) {
|
|
if (
|
|
[
|
|
'deposit_freeze',
|
|
'deposit_pending_unfreeze',
|
|
'reward_pending_unfreeze',
|
|
'after_sales_debt_offset',
|
|
].includes(ledgerType)
|
|
)
|
|
return 'gold'
|
|
if (
|
|
['deposit_release', 'deposit_unfreeze', 'reward_unfreeze', 'after_sales_refund'].includes(
|
|
ledgerType,
|
|
)
|
|
)
|
|
return 'green'
|
|
if (
|
|
[
|
|
'deposit_deduction',
|
|
'after_sales_deposit_deduction',
|
|
'after_sales_balance_deduction',
|
|
].includes(ledgerType)
|
|
)
|
|
return 'red'
|
|
if (['reward_settlement', 'sharing_reward'].includes(ledgerType)) return 'cyan'
|
|
if (ledgerType === 'manual_credit') return 'blue'
|
|
if (ledgerType === 'withdraw_freeze') return 'orange'
|
|
if (ledgerType === 'withdraw_paid') return 'purple'
|
|
if (ledgerType === 'withdraw_release') return 'green'
|
|
return 'default'
|
|
}
|
|
|
|
function formatUnfreezeStatus(status: string) {
|
|
if (status === 'pending') return { label: '待解冻', color: 'orange' }
|
|
if (status === 'released') return { label: '已到账', color: 'green' }
|
|
if (status === 'deducted') return { label: '已扣减', color: 'red' }
|
|
return { label: status || '-', color: 'default' }
|
|
}
|
|
|
|
export default function WorkerDetailDrawer({
|
|
worker,
|
|
onClose,
|
|
}: {
|
|
worker: WorkerUser | null
|
|
onClose: () => void
|
|
}) {
|
|
const workerId = worker?.workerId || 0
|
|
|
|
return (
|
|
<Drawer
|
|
className="worker-detail-drawer"
|
|
width="min(1120px, calc(100vw - 24px))"
|
|
open={Boolean(worker)}
|
|
destroyOnHidden
|
|
onClose={onClose}
|
|
title={
|
|
worker ? (
|
|
<Space size={8} wrap>
|
|
<span>{worker.displayName || worker.username}</span>
|
|
<Tag color={worker.workerType === 'internal' ? 'blue' : 'default'}>
|
|
{worker.workerType === 'internal' ? '内部' : '外部'}
|
|
</Tag>
|
|
<Tag color={worker.status === 'active' ? 'green' : 'orange'}>
|
|
{formatWorkerStatus(worker.status)}
|
|
</Tag>
|
|
</Space>
|
|
) : null
|
|
}
|
|
>
|
|
<Tabs
|
|
items={[
|
|
{ key: 'overview', label: '概览', children: <OverviewSection workerId={workerId} /> },
|
|
{
|
|
key: 'unfreezes',
|
|
label: '待解冻',
|
|
children: <UnfreezesSection workerId={workerId} />,
|
|
},
|
|
{
|
|
key: 'ledgers',
|
|
label: '资金流水',
|
|
children: <LedgersSection workerId={workerId} />,
|
|
},
|
|
{ key: 'orders', label: '他的订单', children: <OrdersSection workerId={workerId} /> },
|
|
]}
|
|
/>
|
|
</Drawer>
|
|
)
|
|
}
|
|
|
|
function OverviewSection({ workerId }: { workerId: number }) {
|
|
const detailQuery = useQuery({
|
|
queryKey: ['admin-worker-detail', workerId],
|
|
queryFn: () => fetchAdminWorkerDetail(workerId),
|
|
enabled: workerId > 0,
|
|
})
|
|
const detail = detailQuery.data?.data
|
|
if (!workerId) return null
|
|
if (!detail) {
|
|
return <Typography.Text type="secondary">加载中…</Typography.Text>
|
|
}
|
|
const { worker, summary, orderOverview } = detail
|
|
return (
|
|
<div className="worker-order-detail-stack">
|
|
<Card size="small" title="基本信息">
|
|
<Descriptions bordered size="small" column={{ xs: 1, md: 2 }}>
|
|
<Descriptions.Item label="昵称">{worker.displayName || '-'}</Descriptions.Item>
|
|
<Descriptions.Item label="账号">{worker.username}</Descriptions.Item>
|
|
<Descriptions.Item label="手机号">{worker.phone || '-'}</Descriptions.Item>
|
|
<Descriptions.Item label="等级">{worker.level?.name || '-'}</Descriptions.Item>
|
|
<Descriptions.Item label="邀请人">
|
|
{worker.inviter ? `${worker.inviter.displayName || worker.inviter.username}` : '-'}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="注册时间">
|
|
{formatAdminDateTime(worker.createdAt)}
|
|
</Descriptions.Item>
|
|
</Descriptions>
|
|
</Card>
|
|
<Card size="small" title="钱包">
|
|
<Descriptions bordered size="small" column={{ xs: 1, md: 2 }}>
|
|
<Descriptions.Item label="可用余额">
|
|
{formatMoney(worker.wallet.availableAmount)}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="冻结押金">
|
|
{formatMoney(worker.wallet.frozenDepositAmount)}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="待解冻金额">
|
|
{formatMoney(worker.wallet.pendingUnfreezeAmount)}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="累计充值">
|
|
{formatMoney(worker.wallet.totalCreditedAmount)}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item label="累计结算">
|
|
{formatMoney(worker.wallet.totalSettledAmount)}
|
|
</Descriptions.Item>
|
|
</Descriptions>
|
|
</Card>
|
|
<Card size="small" title="接单统计">
|
|
<Descriptions bordered size="small" column={{ xs: 1, md: 3 }}>
|
|
<Descriptions.Item label="进行中">{summary.activeOrderCount}</Descriptions.Item>
|
|
<Descriptions.Item label="已完成">{summary.acceptedOrderCount}</Descriptions.Item>
|
|
<Descriptions.Item label="超时">
|
|
{summary.timeoutOrderCount > 0 ? <Tag color="red">{summary.timeoutOrderCount}</Tag> : 0}
|
|
</Descriptions.Item>
|
|
</Descriptions>
|
|
</Card>
|
|
<Card size="small" title="订单概览(按状态)">
|
|
{orderOverview.length === 0 ? (
|
|
<Typography.Text type="secondary">暂无订单</Typography.Text>
|
|
) : (
|
|
<Space size={[12, 8]} wrap>
|
|
{orderOverview.map((item) => (
|
|
<Tag key={item.status} color={resolveStatusColor(item.status)}>
|
|
{formatStatus(item.status)} {item.orderCount} 单 /{' '}
|
|
{formatMoney(item.settlementAmount)}
|
|
</Tag>
|
|
))}
|
|
</Space>
|
|
)}
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function UnfreezesSection({ workerId }: { workerId: number }) {
|
|
const [page, setPage] = useState(1)
|
|
const unfreezesQuery = useQuery({
|
|
queryKey: ['admin-worker-detail-unfreezes', workerId, page],
|
|
queryFn: () =>
|
|
fetchAdminWorkerDepositUnfreezes(workerId, { page, pageSize: ADMIN_DEFAULT_PAGE_SIZE }),
|
|
enabled: workerId > 0,
|
|
})
|
|
const data = unfreezesQuery.data?.data
|
|
const columns: TableColumnsType<WorkerDepositUnfreeze> = [
|
|
{
|
|
title: '资金类型',
|
|
width: 110,
|
|
render: (_, row) => {
|
|
const labels: Record<string, string> = {
|
|
deposit: '押金',
|
|
reward: '报酬',
|
|
legacy_combined: '历史合并',
|
|
}
|
|
return <Tag>{labels[row.fundType] || row.fundType}</Tag>
|
|
},
|
|
},
|
|
{
|
|
title: '金额',
|
|
width: 110,
|
|
render: (_, row) => formatMoney(row.amount),
|
|
},
|
|
{
|
|
title: '状态',
|
|
width: 100,
|
|
render: (_, row) => {
|
|
const status = formatUnfreezeStatus(row.status)
|
|
return <Tag color={status.color}>{status.label}</Tag>
|
|
},
|
|
},
|
|
{
|
|
title: '到期时间',
|
|
width: 170,
|
|
render: (_, row) => formatAdminDateTime(row.unfreezeAt),
|
|
},
|
|
{
|
|
title: '到账时间',
|
|
width: 170,
|
|
render: (_, row) => (row.releasedAt ? formatAdminDateTime(row.releasedAt) : '-'),
|
|
},
|
|
{ title: '关联工单', dataIndex: 'workOrderId', width: 100 },
|
|
{
|
|
title: '创建时间',
|
|
width: 170,
|
|
render: (_, row) => formatAdminDateTime(row.createdAt),
|
|
},
|
|
]
|
|
return (
|
|
<Table<WorkerDepositUnfreeze>
|
|
rowKey="id"
|
|
size="small"
|
|
columns={columns}
|
|
dataSource={data?.items || []}
|
|
loading={unfreezesQuery.isFetching}
|
|
scroll={{ x: 930 }}
|
|
pagination={buildAdminTablePagination({
|
|
page,
|
|
pageSize: ADMIN_DEFAULT_PAGE_SIZE,
|
|
total: data?.pagination.total || 0,
|
|
onChange: setPage,
|
|
})}
|
|
/>
|
|
)
|
|
}
|
|
|
|
function LedgersSection({ workerId }: { workerId: number }) {
|
|
const [page, setPage] = useState(1)
|
|
const [ledgerType, setLedgerType] = useState('')
|
|
const ledgersQuery = useQuery({
|
|
queryKey: ['admin-worker-detail-ledgers', workerId, page, ledgerType],
|
|
queryFn: () =>
|
|
fetchAdminWorkerWalletLedgers(workerId, {
|
|
page,
|
|
pageSize: ADMIN_DEFAULT_PAGE_SIZE,
|
|
ledgerType: ledgerType || undefined,
|
|
}),
|
|
enabled: workerId > 0,
|
|
})
|
|
const data = ledgersQuery.data?.data
|
|
const columns: TableColumnsType<WorkerWalletLedger> = [
|
|
{
|
|
title: '类型',
|
|
width: 130,
|
|
render: (_, row) => (
|
|
<Tag color={resolveLedgerTagColor(row.ledgerType)}>{formatLedgerType(row.ledgerType)}</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '金额',
|
|
width: 110,
|
|
render: (_, row) => (
|
|
<Typography.Text type={row.amount >= 0 ? 'success' : 'danger'}>
|
|
{row.amount >= 0 ? '+' : '-'}
|
|
{formatMoney(Math.abs(row.amount))}
|
|
</Typography.Text>
|
|
),
|
|
},
|
|
{
|
|
title: '变动后余额',
|
|
width: 110,
|
|
render: (_, row) => formatMoney(row.balanceAfter),
|
|
},
|
|
{
|
|
title: '冻结金额',
|
|
width: 110,
|
|
render: (_, row) => formatMoney(row.frozenAfter),
|
|
},
|
|
{
|
|
title: '关联订单',
|
|
width: 150,
|
|
render: (_, row) => {
|
|
const orderNo = String(row.relatedPlatformOrderId || row.relatedWorkOrderId || '-').trim()
|
|
return (
|
|
<Typography.Text ellipsis={{ tooltip: orderNo }} copyable={orderNo !== '-'}>
|
|
{orderNo}
|
|
</Typography.Text>
|
|
)
|
|
},
|
|
},
|
|
{
|
|
title: '说明',
|
|
ellipsis: true,
|
|
render: (_, row) => row.note || '-',
|
|
},
|
|
{
|
|
title: '时间',
|
|
width: 170,
|
|
render: (_, row) => formatAdminDateTime(row.createdAt),
|
|
},
|
|
]
|
|
return (
|
|
<div className="worker-order-detail-stack">
|
|
<Select
|
|
allowClear
|
|
value={ledgerType || undefined}
|
|
style={{ width: 180 }}
|
|
placeholder="全部类型"
|
|
options={Object.entries(LEDGER_TYPE_LABELS).map(([value, label]) => ({ value, label }))}
|
|
onChange={(next) => {
|
|
setLedgerType(next || '')
|
|
setPage(1)
|
|
}}
|
|
/>
|
|
<Table<WorkerWalletLedger>
|
|
rowKey="ledgerId"
|
|
size="small"
|
|
columns={columns}
|
|
dataSource={data?.items || []}
|
|
loading={ledgersQuery.isFetching}
|
|
scroll={{ x: 1020 }}
|
|
pagination={buildAdminTablePagination({
|
|
page,
|
|
pageSize: ADMIN_DEFAULT_PAGE_SIZE,
|
|
total: data?.pagination.total || 0,
|
|
onChange: setPage,
|
|
})}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function OrdersSection({ workerId }: { workerId: number }) {
|
|
const [page, setPage] = useState(1)
|
|
const ordersQuery = useQuery({
|
|
queryKey: ['admin-worker-detail-orders', workerId, page],
|
|
queryFn: () => fetchAdminWorkOrders({ workerId, page, pageSize: ADMIN_DEFAULT_PAGE_SIZE }),
|
|
enabled: workerId > 0,
|
|
})
|
|
const data = ordersQuery.data?.data
|
|
const columns: TableColumnsType<WorkOrder> = [
|
|
{
|
|
title: '平台单号',
|
|
width: 160,
|
|
ellipsis: true,
|
|
render: (_, row) => {
|
|
const orderNo = String(row.platformOrderId || row.workOrderNo || row.workOrderId).trim()
|
|
return <Typography.Text ellipsis={{ tooltip: orderNo }}>{orderNo}</Typography.Text>
|
|
},
|
|
},
|
|
{
|
|
title: '商品',
|
|
ellipsis: true,
|
|
render: (_, row) => row.productName || '-',
|
|
},
|
|
{
|
|
title: '状态',
|
|
width: 110,
|
|
render: (_, row) => (
|
|
<Tag color={resolveStatusColor(row.status)}>{formatStatus(row.status)}</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '方式',
|
|
width: 90,
|
|
render: (_, row) => (row.sharing?.enabled ? `拼单` : '整单'),
|
|
},
|
|
{
|
|
title: '报酬',
|
|
width: 110,
|
|
render: (_, row) => formatMoney(row.rewardAmount),
|
|
},
|
|
{
|
|
title: '创建时间',
|
|
width: 170,
|
|
render: (_, row) => formatAdminDateTime(row.createdAt),
|
|
},
|
|
]
|
|
return (
|
|
<Table<WorkOrder>
|
|
rowKey="workOrderId"
|
|
size="small"
|
|
columns={columns}
|
|
dataSource={data?.items || []}
|
|
loading={ordersQuery.isFetching}
|
|
scroll={{ x: 900 }}
|
|
pagination={buildAdminTablePagination({
|
|
page,
|
|
pageSize: ADMIN_DEFAULT_PAGE_SIZE,
|
|
total: data?.pagination.total || 0,
|
|
onChange: setPage,
|
|
})}
|
|
/>
|
|
)
|
|
}
|