增加打手列表筛选汇总

This commit is contained in:
yml2213
2026-09-06 12:55:58 +08:00
parent 4c6c53d818
commit fe2a28454d
6 changed files with 268 additions and 98 deletions
@@ -33,6 +33,7 @@ import type {
AdminWorkerWithdrawalAccount,
UploadedFile,
WorkerUser,
WorkerUserStatistics,
} from '@/types/worker-platform'
import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination'
import WorkerDetailDrawer from './WorkerDetailDrawer'
@@ -54,6 +55,62 @@ type PersonalDepositFreeAmountFormValues = {
depositFreeAmount?: number
}
const WORKER_STATISTICS_STATUS_ORDER = ['pending_review', 'active', 'rejected', 'disabled']
const WORKER_STATISTICS_TYPE_ORDER = ['internal', 'external']
function WorkerStatisticsBar({ statistics }: { statistics?: WorkerUserStatistics }) {
if (!statistics) return null
const statusItems = WORKER_STATISTICS_STATUS_ORDER.map((status) => ({
status,
count: Number(statistics.statusCounts?.[status] || 0),
}))
const otherStatusItems = Object.entries(statistics.statusCounts || {})
.filter(([status]) => !WORKER_STATISTICS_STATUS_ORDER.includes(status))
.map(([status, count]) => ({ status, count: Number(count || 0) }))
const typeItems = WORKER_STATISTICS_TYPE_ORDER.map((workerType) => ({
workerType,
count: Number(statistics.workerTypeCounts?.[workerType] || 0),
}))
const otherTypeItems = Object.entries(statistics.workerTypeCounts || {})
.filter(([workerType]) => !WORKER_STATISTICS_TYPE_ORDER.includes(workerType))
.map(([workerType, count]) => ({ workerType, count: Number(count || 0) }))
const workerTypeLabels: Record<string, string> = {
internal: '内部打手',
external: '外部打手',
}
return (
<Card size="small" bordered={false} className="worker-orders-statistics-card">
<div className="worker-orders-statistics-summary">
本次筛选统计共 <strong>{statistics.totalCount}</strong> 名,余额合计{' '}
<strong>{formatMoney(statistics.availableAmount)}</strong>,冻结押金合计{' '}
<strong>{formatMoney(statistics.frozenDepositAmount)}</strong>,待解冻合计{' '}
<strong>{formatMoney(statistics.pendingUnfreezeAmount)}</strong>,累计结算{' '}
<strong>{formatMoney(statistics.totalSettledAmount)}</strong>,累计充值{' '}
<strong>{formatMoney(statistics.totalCreditedAmount)}</strong>。
</div>
<div className="worker-orders-statistics-statuses">
其中:
{[...statusItems, ...otherStatusItems].map(({ status, count }, index) => (
<span key={`status-${status}`}>
{index > 0 ? ',' : ''}
{formatWorkerStatus(status)}
{count}名
</span>
))}
{[...typeItems, ...otherTypeItems].map(({ workerType, count }, index) => (
<span key={`type-${workerType}`}>
{index === 0 ? ',' : ''}
{workerTypeLabels[workerType] || workerType}
{count}名
</span>
))}
<span>,超时订单{statistics.timeoutOrderCount}单</span>
</div>
</Card>
)
}
export default function WorkersPanel() {
const { message, modal } = App.useApp()
const queryClient = useQueryClient()
@@ -123,6 +180,7 @@ export default function WorkersPanel() {
workerType,
levelId: levelId || undefined,
workerId: targetWorkerId || undefined,
includeStatistics: true,
page,
pageSize,
}),
@@ -491,97 +549,102 @@ export default function WorkersPanel() {
]
return (
<Card
title="打手"
extra={
<Space wrap className="filter-form">
<Input
allowClear
value={keywordInput}
placeholder="账号 / 昵称 / 手机号"
style={{ width: 220 }}
onChange={(event) => setKeywordInput(event.target.value)}
onPressEnter={searchWorkers}
/>
<Select
allowClear
value={status}
style={{ width: 150 }}
options={[
{ value: '', label: '全部状态' },
{ value: 'pending_review', label: '待处理' },
{ value: 'active', label: '已启用' },
{ value: 'rejected', label: '已冻结' },
{ value: 'disabled', label: '已停用' },
]}
onChange={(nextStatus) => {
setStatus(nextStatus || '')
setPage(1)
}}
/>
<Select
allowClear
value={workerType || undefined}
style={{ width: 140 }}
placeholder="全部类型"
options={[
{ value: 'external', label: '外部打手' },
{ value: 'internal', label: '内部打手' },
]}
onChange={(nextType) => {
setWorkerType(nextType || '')
setPage(1)
}}
/>
<Select
allowClear
value={levelId || undefined}
style={{ width: 140 }}
placeholder="全部等级"
loading={levelsQuery.isLoading}
options={(levelsQuery.data?.data.items || []).map((item) => ({
value: item.levelId,
label: item.name,
}))}
onChange={(nextLevelId) => {
setLevelId(nextLevelId || 0)
setPage(1)
}}
/>
<Button type="primary" icon={<SearchOutlined />} onClick={searchWorkers}>
查询
</Button>
<Button onClick={clearWorkerFilters}>清空筛选</Button>
<Button
icon={<ReloadOutlined />}
loading={workersQuery.isFetching}
onClick={() => workersQuery.refetch()}
>
刷新
</Button>
</Space>
}
bordered={false}
>
{targetWorkerId ? (
<Typography.Text type="secondary">已定位打手 #{targetWorkerId}</Typography.Text>
) : null}
<Table<WorkerUser>
rowKey="workerId"
loading={workersQuery.isLoading}
dataSource={workersQuery.data?.data.items || []}
columns={columns}
pagination={buildAdminTablePagination({
current: workersPagination?.page || page,
pageSize: workersPagination?.pageSize || pageSize,
total: workersPagination?.total || 0,
onChange: (nextPage, nextPageSize) => {
setPage(nextPage)
setPageSize(nextPageSize)
},
})}
scroll={{ x: 1000 }}
/>
<section className="platform-panel-stack">
<Card
title="打手"
extra={
<Space wrap className="filter-form">
<Input
allowClear
value={keywordInput}
placeholder="账号 / 昵称 / 手机号"
style={{ width: 220 }}
onChange={(event) => setKeywordInput(event.target.value)}
onPressEnter={searchWorkers}
/>
<Select
allowClear
value={status}
style={{ width: 150 }}
options={[
{ value: '', label: '全部状态' },
{ value: 'pending_review', label: '待处理' },
{ value: 'active', label: '已启用' },
{ value: 'rejected', label: '已冻结' },
{ value: 'disabled', label: '已停用' },
]}
onChange={(nextStatus) => {
setStatus(nextStatus || '')
setPage(1)
}}
/>
<Select
allowClear
value={workerType || undefined}
style={{ width: 140 }}
placeholder="全部类型"
options={[
{ value: 'external', label: '外部打手' },
{ value: 'internal', label: '内部打手' },
]}
onChange={(nextType) => {
setWorkerType(nextType || '')
setPage(1)
}}
/>
<Select
allowClear
value={levelId || undefined}
style={{ width: 140 }}
placeholder="全部等级"
loading={levelsQuery.isLoading}
options={(levelsQuery.data?.data.items || []).map((item) => ({
value: item.levelId,
label: item.name,
}))}
onChange={(nextLevelId) => {
setLevelId(nextLevelId || 0)
setPage(1)
}}
/>
<Button type="primary" icon={<SearchOutlined />} onClick={searchWorkers}>
查询
</Button>
<Button onClick={clearWorkerFilters}>清空筛选</Button>
<Button
icon={<ReloadOutlined />}
loading={workersQuery.isFetching}
onClick={() => workersQuery.refetch()}
>
刷新
</Button>
</Space>
}
bordered={false}
>
{targetWorkerId ? (
<Typography.Text type="secondary">已定位打手 #{targetWorkerId}</Typography.Text>
) : null}
</Card>
<WorkerStatisticsBar statistics={workersQuery.data?.data.statistics} />
<Card title="打手列表" bordered={false}>
<Table<WorkerUser>
rowKey="workerId"
loading={workersQuery.isLoading}
dataSource={workersQuery.data?.data.items || []}
columns={columns}
pagination={buildAdminTablePagination({
current: workersPagination?.page || page,
pageSize: workersPagination?.pageSize || pageSize,
total: workersPagination?.total || 0,
onChange: (nextPage, nextPageSize) => {
setPage(nextPage)
setPageSize(nextPageSize)
},
})}
scroll={{ x: 1000 }}
/>
</Card>
<Modal
title="冻结打手账号"
@@ -812,6 +875,6 @@ export default function WorkersPanel() {
</Modal>
<WorkerDetailDrawer worker={detailWorker} onClose={() => setDetailWorker(null)} />
</Card>
</section>
)
}
@@ -25,6 +25,7 @@ import type {
WorkerAnnouncementConfig,
WorkerLevel,
WorkerListResponse,
WorkerUserListResponse,
WorkerUser,
WorkerWalletLedger,
} from '@/types/worker-platform'
@@ -173,7 +174,7 @@ export function saveAdminWorkerLevel(payload: {
}
export function fetchAdminWorkerUsers(params?: Record<string, unknown>) {
return apiGet<WorkerListResponse<WorkerUser>>('/api/v1/admin/worker-platform/workers', params)
return apiGet<WorkerUserListResponse>('/api/v1/admin/worker-platform/workers', params)
}
export function fetchAdminWorkerDetail(workerId: number | string) {
@@ -50,6 +50,18 @@ export type WorkerUser = {
renameUnlockedAt: string | null
}
export type WorkerUserStatistics = {
totalCount: number
statusCounts: Record<string, number>
workerTypeCounts: Record<string, number>
availableAmount: number
frozenDepositAmount: number
pendingUnfreezeAmount: number
totalCreditedAmount: number
totalSettledAmount: number
timeoutOrderCount: number
}
export type WorkerSessionDevice = {
sessionId: string
deviceId: string
@@ -677,6 +689,10 @@ export type WorkerListResponse<T> = {
pagination: Pagination
}
export type WorkerUserListResponse = WorkerListResponse<WorkerUser> & {
statistics?: WorkerUserStatistics
}
export type WorkerOrderOverviewItem = {
status: string
orderCount: number