diff --git a/apps/backend/src/services/realtime/realtime-event-service.ts b/apps/backend/src/services/realtime/realtime-event-service.ts index 3d4778f3..af678e54 100644 --- a/apps/backend/src/services/realtime/realtime-event-service.ts +++ b/apps/backend/src/services/realtime/realtime-event-service.ts @@ -311,6 +311,8 @@ async function publishWorkOrderSnapshots(input: { ? { ...mapWorkOrderForWorker(workOrder, { depositFreeAmount: 0 }), requiredDepositAmount: Number(workOrder.required_deposit_amount || 0), + // 广播无法按打手等级过滤可见延迟,携带入厅时间由客户端按各自延迟推迟展示。 + hallQueuedAt: workOrder.hall_queued_at || null, sharingProgress: { joinedQuantity, pendingSubmissionCount }, } : { workOrderId }, diff --git a/apps/frontend/src/layouts/WorkerLayout.tsx b/apps/frontend/src/layouts/WorkerLayout.tsx index 931e39f2..21bd2d30 100644 --- a/apps/frontend/src/layouts/WorkerLayout.tsx +++ b/apps/frontend/src/layouts/WorkerLayout.tsx @@ -228,6 +228,12 @@ function patchWorkerMyOrderCaches( } } +/** 新订单广播最多推迟入缓存的时长;超过则不占定时器,交给列表刷新自然带出。 */ +const HALL_BROADCAST_MAX_SCHEDULE_MS = 60_000 + +/** 等待可见延迟到期的挂起插入;期间收到下架事件需取消,避免把已下架订单插回缓存。 */ +const pendingHallUpsertTimers = new Map>() + function patchWorkerHallCaches( queryClient: ReturnType, event: RealtimeEvent, @@ -237,6 +243,35 @@ function patchWorkerHallCaches( void queryClient.invalidateQueries({ queryKey: ['worker-hall-orders'] }) return } + const workOrderId = Number(event.entityId || 0) + const pendingTimer = pendingHallUpsertTimers.get(workOrderId) + if (pendingTimer) { + clearTimeout(pendingTimer) + pendingHallUpsertTimers.delete(workOrderId) + } + if (event.operation === 'remove') { + applyWorkerHallCachePatch(queryClient, event) + return + } + // 服务端广播无法按打手等级过滤可见延迟;未到解锁时间的订单推迟入缓存,与列表接口和抢单校验对齐。 + const delayMs = resolveHallVisibilityDelayMs(queryClient, event.data, event.occurredAt) + if (delayMs > 0) { + if (delayMs <= HALL_BROADCAST_MAX_SCHEDULE_MS) { + const timer = setTimeout(() => { + pendingHallUpsertTimers.delete(workOrderId) + applyWorkerHallCachePatch(queryClient, event) + }, delayMs) + pendingHallUpsertTimers.set(workOrderId, timer) + } + return + } + applyWorkerHallCachePatch(queryClient, event) +} + +function applyWorkerHallCachePatch( + queryClient: ReturnType, + event: RealtimeEvent, +) { const workerSnapshot = applyWorkerDepositRule(queryClient, event.data) const item = workerSnapshot as { status?: unknown @@ -267,6 +302,41 @@ function patchWorkerHallCaches( } } +/** 订单入厅时间 + 当前打手等级的可见延迟尚未到期时,返回需要推迟的毫秒数。 */ +function resolveHallVisibilityDelayMs( + queryClient: ReturnType, + data: unknown, + occurredAt: string | undefined, +): number { + if (!isRecord(data)) return 0 + const queuedAtMs = Date.parse(String(data.hallQueuedAt || '')) + if (!Number.isFinite(queuedAtMs)) return 0 + const visibleDelaySeconds = readWorkerHallVisibleDelaySeconds(queryClient) + if (visibleDelaySeconds <= 0) return 0 + const unlockAtMs = queuedAtMs + visibleDelaySeconds * 1000 + // 入厅时间是服务器时钟,用事件的服务器时间戳校正浏览器钟差:快钟提前展示会重现“看得见抢不了”。 + const occurredAtMs = occurredAt ? Date.parse(occurredAt) : Number.NaN + const serverNowMs = Number.isFinite(occurredAtMs) ? occurredAtMs : Date.now() + const clockSkewMs = Date.now() - serverNowMs + return unlockAtMs - (Date.now() - clockSkewMs) +} + +/** 大厅列表响应自带当前打手的 visibleDelaySeconds,从已缓存分页读取,无需额外接口。 */ +function readWorkerHallVisibleDelaySeconds(queryClient: ReturnType): number { + for (const [, cached] of queryClient.getQueriesData({ queryKey: ['worker-hall-orders'] })) { + const pages = (cached as { pages?: unknown } | undefined)?.pages + if (!Array.isArray(pages)) continue + for (const page of pages) { + const delaySeconds = Number( + (page as { data?: { visibleDelaySeconds?: unknown } } | null)?.data?.visibleDelaySeconds || + 0, + ) + if (delaySeconds > 0) return delaySeconds + } + } + return 0 +} + function applyWorkerDepositRule(queryClient: ReturnType, data: unknown) { if (!isRecord(data)) return data const profile = queryClient.getQueryData(['worker-profile']) diff --git a/apps/frontend/src/types/worker-platform.ts b/apps/frontend/src/types/worker-platform.ts index ba47f528..c6123147 100644 --- a/apps/frontend/src/types/worker-platform.ts +++ b/apps/frontend/src/types/worker-platform.ts @@ -501,6 +501,8 @@ export type WorkOrder = { status: WorkOrderStatus rawStatus?: WorkOrderStatus hallAvailable?: boolean + /** 订单进入抢单大厅的时间;实时广播携带,客户端按打手等级可见延迟推迟展示。 */ + hallQueuedAt?: string | null /** 验收模式:standard 标准验收 / friend_gift 好友赠送 */ acceptanceMode?: 'standard' | 'friend_gift' | string /** 好友赠送模式进度;仅 acceptanceMode = friend_gift 时返回 */