管理后台新增打手详情抽屉聚合钱包流水待解冻与订单数据
This commit is contained in:
@@ -46,3 +46,39 @@ export async function listDueDepositUnfreezes({
|
||||
)
|
||||
return result.rows
|
||||
}
|
||||
|
||||
/** 管理端按打手列出全部解冻记录(含未到期与已释放),观察待解冻到账进度。 */
|
||||
export async function listWorkerDepositUnfreezes({
|
||||
workerId,
|
||||
status = '',
|
||||
page = 1,
|
||||
pageSize = 20,
|
||||
}: {
|
||||
workerId: number
|
||||
status?: string
|
||||
page?: number
|
||||
pageSize?: number
|
||||
}): Promise<{ items: WorkerDepositUnfreezeRow[]; total: number }> {
|
||||
const params: unknown[] = [Number(workerId)]
|
||||
const normalizedStatus = String(status || '').trim()
|
||||
let statusClause = ''
|
||||
if (normalizedStatus) {
|
||||
params.push(normalizedStatus)
|
||||
statusClause = ` AND status = $${params.length}`
|
||||
}
|
||||
const totalResult = await query<{ total: number }>(
|
||||
`SELECT COUNT(*)::int AS total FROM worker_deposit_unfreezes WHERE worker_id = $1${statusClause}`,
|
||||
params,
|
||||
)
|
||||
params.push(pageSize, (page - 1) * pageSize)
|
||||
const result = await query<WorkerDepositUnfreezeRow>(
|
||||
`
|
||||
SELECT * FROM worker_deposit_unfreezes
|
||||
WHERE worker_id = $1${statusClause}
|
||||
ORDER BY unfreeze_at DESC, id DESC
|
||||
LIMIT $${params.length - 1} OFFSET $${params.length}
|
||||
`,
|
||||
params,
|
||||
)
|
||||
return { items: result.rows, total: Number(totalResult.rows[0]?.total || 0) }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@ import { Router } from 'express'
|
||||
|
||||
import {
|
||||
creditAdminWorkerWallet,
|
||||
getAdminWorkerDetail,
|
||||
listAdminWorkerDepositUnfreezes,
|
||||
listAdminWorkerUsers,
|
||||
listAdminWorkerWalletLedgers,
|
||||
listAdminWorkerWithdrawalAccounts,
|
||||
resetAdminWorkerPassword,
|
||||
reviewAdminWorkerUser,
|
||||
@@ -22,6 +25,42 @@ router.get(
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/worker-platform/workers/:workerId/detail',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
createJsonHandler((req) => getAdminWorkerDetail(String(req.params.workerId || '')), {
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取打手详情失败',
|
||||
scope: '[admin/worker-platform/workers/:workerId/detail]',
|
||||
}),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/worker-platform/workers/:workerId/wallet-ledgers',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
createJsonHandler(
|
||||
(req) => listAdminWorkerWalletLedgers(String(req.params.workerId || ''), req.query),
|
||||
{
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取打手资金流水失败',
|
||||
scope: '[admin/worker-platform/workers/:workerId/wallet-ledgers]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.get(
|
||||
'/worker-platform/workers/:workerId/deposit-unfreezes',
|
||||
requireAdminRoles(['admin', 'operator', 'support']),
|
||||
createJsonHandler(
|
||||
(req) => listAdminWorkerDepositUnfreezes(String(req.params.workerId || ''), req.query),
|
||||
{
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取打手待解冻记录失败',
|
||||
scope: '[admin/worker-platform/workers/:workerId/deposit-unfreezes]',
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
router.post(
|
||||
'/worker-platform/workers/:workerId/review',
|
||||
requireAdminRoles(['admin', 'operator']),
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import type { WorkerDepositUnfreezeRow } from '../../repositories/worker-platform/index.js'
|
||||
import {
|
||||
countWorkerAcceptedOrders,
|
||||
countWorkerActiveOrders,
|
||||
countWorkerTimeoutEvents,
|
||||
getWorkerOrderOverview,
|
||||
listWorkerDepositUnfreezes,
|
||||
listWorkerWalletLedgers,
|
||||
} from '../../repositories/worker-platform/index.js'
|
||||
import type { JsonObject } from '../../types/json.js'
|
||||
import { normalizePage, normalizePageSize } from '../admin/admin-query-utils.js'
|
||||
import { mapWalletLedger, mapWorkerUser } from './mappers.js'
|
||||
import { getRequiredWorker } from './worker-session-context-service.js'
|
||||
|
||||
/** 管理端打手详情:聚合账号钱包、接单统计与订单概览,全部只读。 */
|
||||
export async function getAdminWorkerDetail(workerId: number | string) {
|
||||
const worker = await getRequiredWorker(workerId)
|
||||
const [activeOrderCount, acceptedOrderCount, timeoutOrderCount, orderOverview] =
|
||||
await Promise.all([
|
||||
countWorkerActiveOrders(worker.id),
|
||||
countWorkerAcceptedOrders(worker.id),
|
||||
countWorkerTimeoutEvents(worker.id),
|
||||
getWorkerOrderOverview(worker.id),
|
||||
])
|
||||
return {
|
||||
worker: mapWorkerUser(worker),
|
||||
summary: { activeOrderCount, acceptedOrderCount, timeoutOrderCount },
|
||||
orderOverview,
|
||||
}
|
||||
}
|
||||
|
||||
export async function listAdminWorkerWalletLedgers(
|
||||
workerId: number | string,
|
||||
payload: JsonObject = {},
|
||||
) {
|
||||
const worker = await getRequiredWorker(workerId)
|
||||
const page = normalizePage(payload.page)
|
||||
const pageSize = normalizePageSize(payload.pageSize)
|
||||
const ledgerType = String(payload.ledgerType || payload.ledger_type || '').trim()
|
||||
const { items, total } = await listWorkerWalletLedgers({
|
||||
page,
|
||||
pageSize,
|
||||
workerId: Number(worker.id),
|
||||
ledgerType,
|
||||
})
|
||||
return { items: items.map(mapWalletLedger), pagination: { page, pageSize, total } }
|
||||
}
|
||||
|
||||
export async function listAdminWorkerDepositUnfreezes(
|
||||
workerId: number | string,
|
||||
payload: JsonObject = {},
|
||||
) {
|
||||
const worker = await getRequiredWorker(workerId)
|
||||
const page = normalizePage(payload.page)
|
||||
const pageSize = normalizePageSize(payload.pageSize)
|
||||
const status = String(payload.status || '').trim()
|
||||
const { items, total } = await listWorkerDepositUnfreezes({
|
||||
workerId: Number(worker.id),
|
||||
status,
|
||||
page,
|
||||
pageSize,
|
||||
})
|
||||
return {
|
||||
items: items.map(mapDepositUnfreeze),
|
||||
pagination: { page, pageSize, total },
|
||||
}
|
||||
}
|
||||
|
||||
function mapDepositUnfreeze(row: WorkerDepositUnfreezeRow) {
|
||||
return {
|
||||
id: Number(row.id),
|
||||
workerId: Number(row.worker_id),
|
||||
workOrderId: Number(row.work_order_id),
|
||||
amount: Number(row.amount || 0),
|
||||
status: row.status,
|
||||
unfreezeAt: row.unfreeze_at,
|
||||
releasedAt: row.released_at || null,
|
||||
createdAt: row.created_at,
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ export * from './admin-worker-leaderboard-service.js'
|
||||
export * from './admin-kuaishou-match-service.js'
|
||||
export * from './admin-worker-config-service.js'
|
||||
export * from './admin-worker-user-service.js'
|
||||
export * from './admin-worker-detail-service.js'
|
||||
export * from './admin-work-order-query-service.js'
|
||||
export * from './admin-worker-feedback-service.js'
|
||||
export * from './admin-worker-cancel-service.js'
|
||||
|
||||
@@ -0,0 +1,398 @@
|
||||
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_paid: '提现打款',
|
||||
deposit_freeze: '冻结押金',
|
||||
deposit_release: '释放押金',
|
||||
deposit_pending_unfreeze: '待解冻金额',
|
||||
deposit_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', 'after_sales_debt_offset'].includes(ledgerType)
|
||||
)
|
||||
return 'gold'
|
||||
if (['deposit_release', 'deposit_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_paid') return 'purple'
|
||||
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={860}
|
||||
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) => 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}
|
||||
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) => row.relatedPlatformOrderId || row.relatedWorkOrderId || '-',
|
||||
},
|
||||
{
|
||||
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}
|
||||
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) => row.platformOrderId || row.workOrderNo || row.workOrderId,
|
||||
},
|
||||
{
|
||||
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}
|
||||
pagination={buildAdminTablePagination({
|
||||
page,
|
||||
pageSize: ADMIN_DEFAULT_PAGE_SIZE,
|
||||
total: data?.pagination.total || 0,
|
||||
onChange: setPage,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
Typography,
|
||||
} from 'antd'
|
||||
import type { TableColumnsType } from 'antd'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useSearchParams } from 'react-router'
|
||||
|
||||
import {
|
||||
@@ -34,6 +34,7 @@ import type {
|
||||
WorkerUser,
|
||||
} from '@/types/worker-platform'
|
||||
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
|
||||
import WorkerDetailDrawer from './WorkerDetailDrawer'
|
||||
import { formatMoney, formatWorkerStatus } from './shared'
|
||||
|
||||
type WithdrawalAccountFormValues = {
|
||||
@@ -64,6 +65,8 @@ export default function WorkersPanel() {
|
||||
const [withdrawalAccountWorker, setWithdrawalAccountWorker] = useState<WorkerUser | null>(null)
|
||||
const [resetPasswordWorker, setResetPasswordWorker] = useState<WorkerUser | null>(null)
|
||||
const [freezeWorker, setFreezeWorker] = useState<WorkerUser | null>(null)
|
||||
const [detailWorker, setDetailWorker] = useState<WorkerUser | null>(null)
|
||||
const autoOpenedDetailRef = useRef(0)
|
||||
const [withdrawalAccounts, setWithdrawalAccounts] = useState<AdminWorkerWithdrawalAccount[]>([])
|
||||
const [loadingWithdrawalAccounts, setLoadingWithdrawalAccounts] = useState(false)
|
||||
const [savingWithdrawalAccount, setSavingWithdrawalAccount] = useState(false)
|
||||
@@ -117,6 +120,17 @@ export default function WorkersPanel() {
|
||||
})
|
||||
const workersPagination = workersQuery.data?.data.pagination
|
||||
|
||||
useEffect(() => {
|
||||
// 从工单流转记录等入口带 ?workerId= 定位到打手后,自动打开一次详情,关闭后不再重复弹。
|
||||
if (targetWorkerId <= 0 || autoOpenedDetailRef.current === targetWorkerId) return
|
||||
const match = workersQuery.data?.data.items.find(
|
||||
(item) => Number(item.workerId) === targetWorkerId,
|
||||
)
|
||||
if (!match) return
|
||||
autoOpenedDetailRef.current = targetWorkerId
|
||||
setDetailWorker(match)
|
||||
}, [targetWorkerId, workersQuery.data])
|
||||
|
||||
async function refreshWorkers() {
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({
|
||||
@@ -383,6 +397,9 @@ export default function WorkersPanel() {
|
||||
width: 340,
|
||||
render: (_, row) => (
|
||||
<Space wrap>
|
||||
<Button type="primary" ghost onClick={() => setDetailWorker(row)}>
|
||||
详情
|
||||
</Button>
|
||||
{row.status !== 'active' ? (
|
||||
<Button onClick={() => review(row, 'active')}>启用</Button>
|
||||
) : null}
|
||||
@@ -686,6 +703,8 @@ export default function WorkersPanel() {
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<WorkerDetailDrawer worker={detailWorker} onClose={() => setDetailWorker(null)} />
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { apiDelete, apiGet, apiPost, apiPut } from '@/lib/http'
|
||||
import type {
|
||||
UploadedFile,
|
||||
AdminWorkerDetail,
|
||||
AdminWorkerWithdrawalAccount,
|
||||
WorkCategory,
|
||||
WorkOrder,
|
||||
@@ -11,6 +12,7 @@ import type {
|
||||
WorkProductRuleMapping,
|
||||
KuaishouMatchSource,
|
||||
WorkProductMatchLog,
|
||||
WorkerDepositUnfreeze,
|
||||
WorkerFinanceConfig,
|
||||
WorkerHallConfig,
|
||||
WorkerFinanceRequest,
|
||||
@@ -24,6 +26,7 @@ import type {
|
||||
WorkerLevel,
|
||||
WorkerListResponse,
|
||||
WorkerUser,
|
||||
WorkerWalletLedger,
|
||||
} from '@/types/worker-platform'
|
||||
|
||||
type WorkOrderVoucherConsumeResult = {
|
||||
@@ -171,6 +174,30 @@ export function fetchAdminWorkerUsers(params?: Record<string, unknown>) {
|
||||
return apiGet<WorkerListResponse<WorkerUser>>('/api/v1/admin/worker-platform/workers', params)
|
||||
}
|
||||
|
||||
export function fetchAdminWorkerDetail(workerId: number | string) {
|
||||
return apiGet<AdminWorkerDetail>(`/api/v1/admin/worker-platform/workers/${workerId}/detail`)
|
||||
}
|
||||
|
||||
export function fetchAdminWorkerWalletLedgers(
|
||||
workerId: number | string,
|
||||
params?: Record<string, unknown>,
|
||||
) {
|
||||
return apiGet<WorkerListResponse<WorkerWalletLedger>>(
|
||||
`/api/v1/admin/worker-platform/workers/${workerId}/wallet-ledgers`,
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
export function fetchAdminWorkerDepositUnfreezes(
|
||||
workerId: number | string,
|
||||
params?: Record<string, unknown>,
|
||||
) {
|
||||
return apiGet<WorkerListResponse<WorkerDepositUnfreeze>>(
|
||||
`/api/v1/admin/worker-platform/workers/${workerId}/deposit-unfreezes`,
|
||||
params,
|
||||
)
|
||||
}
|
||||
|
||||
export function fetchAdminWorkProductRules(params?: Record<string, unknown>) {
|
||||
return apiGet<{
|
||||
items: WorkProductRule[]
|
||||
|
||||
@@ -86,6 +86,35 @@ export type WorkerWalletLedger = {
|
||||
createdAt: string
|
||||
}
|
||||
|
||||
/** 押金/报酬待解冻记录;status: pending 待到期 / released 已到账 / deducted 已扣减。 */
|
||||
export type WorkerDepositUnfreeze = {
|
||||
id: number
|
||||
workerId: number
|
||||
workOrderId: number
|
||||
amount: number
|
||||
status: string
|
||||
unfreezeAt: string | null
|
||||
releasedAt: string | null
|
||||
createdAt: string | null
|
||||
}
|
||||
|
||||
/** 管理端打手详情聚合数据。 */
|
||||
export type AdminWorkerDetail = {
|
||||
worker: WorkerUser
|
||||
summary: {
|
||||
activeOrderCount: number
|
||||
acceptedOrderCount: number
|
||||
timeoutOrderCount: number
|
||||
}
|
||||
orderOverview: Array<{
|
||||
status: string
|
||||
orderCount: number
|
||||
settlementAmount: number
|
||||
vipEvidencePendingCount: number
|
||||
vipEvidencePendingSettlementAmount: number
|
||||
}>
|
||||
}
|
||||
|
||||
export type WorkerFinanceRequest = {
|
||||
requestId: number
|
||||
requestType: string
|
||||
@@ -465,11 +494,7 @@ export type WorkOrderShare = {
|
||||
}
|
||||
|
||||
export type WorkOrderGiftPhase =
|
||||
| 'material_required'
|
||||
| 'material_submitted'
|
||||
| 'friend_countdown'
|
||||
| 'gift_ready'
|
||||
| string
|
||||
'material_required' | 'material_submitted' | 'friend_countdown' | 'gift_ready' | string
|
||||
|
||||
/** 好友赠送验收模式的打手本人资料 */
|
||||
export type BoosterMaterial = {
|
||||
|
||||
Reference in New Issue
Block a user