超时订单按打手统计,抢单大厅标注限时任务

This commit is contained in:
yml2213
2026-08-01 19:18:20 +08:00
parent 23ad60a1ef
commit 3650526863
7 changed files with 87 additions and 3 deletions
@@ -1425,6 +1425,7 @@ export async function settleOverdueWorkOrder(input: {
payloadJson: JSON.stringify({
policy: input.policy,
depositAmount: resolvedDepositAmount,
workerId: workerId > 0 ? workerId : null,
}),
now: input.now,
})
@@ -1449,6 +1450,36 @@ export async function countWorkerActiveOrders(workerId: number | string): Promis
return Number(result.rows[0]?.total || 0)
}
export async function countTimeoutEventsByWorkerIds(workerIds: number[]): Promise<Map<number, number>> {
const counts = new Map<number, number>()
const uniqueIds = [...new Set(workerIds.map((id) => Number(id)).filter((id) => id > 0))]
if (uniqueIds.length === 0) return counts
const result = await query<{ worker_id: string; total: number }>(
`
SELECT payload_json->>'workerId' AS worker_id, COUNT(*)::int AS total
FROM work_order_events
WHERE event_type LIKE 'timeout_%'
AND payload_json->>'workerId' IS NOT NULL
AND payload_json->>'workerId' != ''
AND payload_json->>'workerId' = ANY($1::text[])
GROUP BY payload_json->>'workerId'
`,
[uniqueIds.map(String)],
)
for (const row of result.rows) {
const workerId = Number(row.worker_id)
if (Number.isFinite(workerId) && workerId > 0) {
counts.set(workerId, Number(row.total || 0))
}
}
return counts
}
export async function countWorkerTimeoutEvents(workerId: number | string): Promise<number> {
const counts = await countTimeoutEventsByWorkerIds([Number(workerId)])
return counts.get(Number(workerId)) || 0
}
export async function createWorkOrderEvent(input: {
workOrderId: number
actorType: string
@@ -12,6 +12,7 @@ import {
countWorkCategoryUsages,
countWorkerLevelUsages,
countWorkOrderPendingSharingSubmissions,
countTimeoutEventsByWorkerIds,
cancelWorkerWorkOrder,
countWorkerCancellationsSince,
createWorkOrder,
@@ -299,8 +300,17 @@ export async function listAdminWorkerUsers(query: JsonObject = {}) {
status: String(query.status || '').trim(),
keyword: String(query.keyword || '').trim(),
})
const timeoutCounts = await countTimeoutEventsByWorkerIds(
items.map((item) => Number(item.id)),
)
return {
items: items.map(mapWorkerUser),
items: items.map((item) => {
const worker = mapWorkerUser(item)
return {
...worker,
timeoutOrderCount: timeoutCounts.get(Number(item.id)) || 0,
}
}),
pagination: { page, pageSize, total },
}
}
@@ -9,6 +9,7 @@ import {
addWorkerWalletCredit,
countWorkerAcceptedOrders,
countWorkerActiveOrders,
countWorkerTimeoutEvents,
countWorkCategoryUsages,
countWorkerLevelUsages,
countWorkOrderPendingSharingSubmissions,
@@ -347,9 +348,10 @@ export function requireActiveWorkerSession(session: WorkerSession | null | undef
export async function getWorkerProfile(session: WorkerSession) {
const worker = await getRequiredWorker(session.workerId)
const [financeSummary, acceptedOrderCount, financeConfig] = await Promise.all([
const [financeSummary, acceptedOrderCount, timeoutOrderCount, financeConfig] = await Promise.all([
getWorkerFinanceRequestSummary(session.workerId),
countWorkerAcceptedOrders(session.workerId),
countWorkerTimeoutEvents(session.workerId),
Promise.resolve(getWorkerFinanceConfig()),
])
const permissions = resolveWorkerPermissions(worker)
@@ -359,6 +361,7 @@ export async function getWorkerProfile(session: WorkerSession) {
permissions,
summary: {
acceptedOrderCount,
timeoutOrderCount,
pendingWithdrawAmount: financeSummary.pendingWithdrawAmount,
approvedWithdrawAmount: financeSummary.approvedWithdrawAmount,
pendingRechargeAmount: financeSummary.pendingRechargeAmount,
@@ -196,6 +196,18 @@ export default function WorkersPanel() {
width: 120,
render: (_, row) => formatMoney(row.wallet.frozenDepositAmount),
},
{
title: '超时订单',
width: 110,
render: (_, row) => {
const count = Number(row.timeoutOrderCount || 0)
return count > 0 ? (
<Tag color="red">{count} </Tag>
) : (
<Typography.Text type="secondary">0</Typography.Text>
)
},
},
{
title: '状态',
width: 130,
@@ -1,4 +1,4 @@
import { EyeOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'
import { ClockCircleOutlined, EyeOutlined, ReloadOutlined, SearchOutlined } from '@ant-design/icons'
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
Alert,
@@ -378,6 +378,14 @@ export default function WorkerHallPage() {
</div>
) : null}
{Number(order.timeoutMinutes || 0) > 0 ? (
<div className="worker-hall-card-sharing">
<Tag color="orange" icon={<ClockCircleOutlined />}>
{order.timeoutMinutes}
</Tag>
</div>
) : null}
<div className="worker-hall-card-amount-row">
<div className="worker-hall-card-amount">
{formatMoney(order.rewardAmount)}
@@ -511,6 +519,15 @@ export default function WorkerHallPage() {
<Descriptions.Item label="冻结押金">
{formatMoney(detailOrder.freezeDepositAmount)}
</Descriptions.Item>
<Descriptions.Item label="任务时限">
{Number(detailOrder.timeoutMinutes || 0) > 0 ? (
<Tag color="orange" icon={<ClockCircleOutlined />}>
{detailOrder.timeoutMinutes}
</Tag>
) : (
'不限时'
)}
</Descriptions.Item>
<Descriptions.Item label="发布时间">
{formatDateTime(resolvePublishedTime(detailOrder))}
</Descriptions.Item>
@@ -87,6 +87,7 @@ type MetricCardItem = {
prefix?: string
suffix?: string
precision?: number
danger?: boolean
}
export default function WorkerProfilePage() {
@@ -519,6 +520,7 @@ export default function WorkerProfilePage() {
prefix={item.prefix}
suffix={item.suffix}
precision={item.precision}
valueStyle={item.danger ? { color: '#cf1322' } : undefined}
/>
<Typography.Text type="secondary">{item.note}</Typography.Text>
</Card>
@@ -1086,6 +1088,13 @@ function buildMetricCards(worker: WorkerUser, summary?: WorkerProfileSummary): M
note: '已完成并验收通过的工单数量',
suffix: '单',
},
{
label: '超时订单',
value: Number(summary?.timeoutOrderCount || 0),
note: '因任务超时被系统判定失败的工单数量',
suffix: '单',
danger: Number(summary?.timeoutOrderCount || 0) > 0,
},
]
}
@@ -28,6 +28,7 @@ export type WorkerUser = {
totalCreditedAmount: number
totalSettledAmount: number
}
timeoutOrderCount?: number
createdAt: string
updatedAt: string
reviewedAt: string | null
@@ -35,6 +36,7 @@ export type WorkerUser = {
export type WorkerProfileSummary = {
acceptedOrderCount: number
timeoutOrderCount: number
pendingWithdrawAmount: number
approvedWithdrawAmount: number
pendingRechargeAmount: number