diff --git a/apps/backend/src/db/migrations/047_worker_order_list_performance.sql b/apps/backend/src/db/migrations/047_worker_order_list_performance.sql new file mode 100644 index 00000000..4ca96727 --- /dev/null +++ b/apps/backend/src/db/migrations/047_worker_order_list_performance.sql @@ -0,0 +1,3 @@ +-- 打手订单列表按打手关联拼单记录,避免全表扫描后逐行查找拼单份额。 +CREATE INDEX IF NOT EXISTS idx_work_order_shares_worker_order + ON work_order_shares(worker_id, work_order_id); diff --git a/apps/backend/src/repositories/worker-platform/work-order-repo.ts b/apps/backend/src/repositories/worker-platform/work-order-repo.ts index a4216c41..6c975d33 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-repo.ts @@ -139,23 +139,16 @@ function workOrderSearchExpression(alias = 'wo') { } /** 打手订单筛选使用个人份额状态,而不是拼单母工单的 open 状态。 */ -function workerOrderStatusExpression(workerIdPlaceholder: string) { +function workerOrderStatusExpression(workerShareAlias = 'worker_share') { return `CASE WHEN wo.status IN ('accepted', 'cancelled', 'problem') THEN wo.status - ELSE COALESCE(( - SELECT CASE wos.status - WHEN 'joined' THEN 'in_progress' - WHEN 'submitted' THEN 'pending_acceptance' - WHEN 'accepted' THEN 'accepted' - WHEN 'cancelled' THEN 'cancelled' - ELSE wo.status - END - FROM work_order_shares wos - WHERE wos.work_order_id = wo.id - AND wos.worker_id = ${workerIdPlaceholder} - ORDER BY wos.id DESC - LIMIT 1 - ), wo.status) + ELSE COALESCE(CASE ${workerShareAlias}.status + WHEN 'joined' THEN 'in_progress' + WHEN 'submitted' THEN 'pending_acceptance' + WHEN 'accepted' THEN 'accepted' + WHEN 'cancelled' THEN 'cancelled' + ELSE wo.status + END, wo.status) END` } @@ -1274,7 +1267,7 @@ export async function listWorkOrders({ sort = 'id_desc', }: ListInput = {}): Promise<{ items: WorkOrderRow[]; total: number }> { const effectiveStatuses = statuses.length > 0 ? statuses : status.trim() ? [status.trim()] : [] - const { whereClause, params } = buildWorkOrderWhere({ + const { joins, whereClause, params } = buildWorkOrderWhere({ statuses: effectiveStatuses, workOrderId, keyword, @@ -1291,6 +1284,7 @@ export async function listWorkOrders({ ` SELECT COUNT(*)::int AS total FROM work_orders wo + ${joins} ${whereClause} `, params, @@ -1321,6 +1315,7 @@ export async function listWorkOrders({ params.push(pageSize, offset) const itemsResult = await query( `${WORK_ORDER_SELECT} + ${joins} ${whereClause} ORDER BY ${effectiveOrderBy} LIMIT $${params.length - 1} OFFSET $${params.length}`, @@ -4368,6 +4363,19 @@ function buildWorkOrderWhere({ }: ListInput) { const filters: string[] = [] const params: unknown[] = [] + let joins = '' + let workerSharePlaceholder = '' + + // 拼单表在 (work_order_id, worker_id) 上唯一;提前按打手关联一次即可, + // 避免列表和 COUNT 查询对每一条工单重复执行相关子查询。 + if (workerSharingId) { + params.push(workerSharingId) + workerSharePlaceholder = `$${params.length}` + joins = ` + LEFT JOIN work_order_shares worker_share + ON worker_share.work_order_id = wo.id + AND worker_share.worker_id = ${workerSharePlaceholder}` + } const normalizedStatuses = [ ...new Set(statuses.map((item) => String(item || '').trim()).filter(Boolean)), ] @@ -4390,9 +4398,8 @@ function buildWorkOrderWhere({ params.push(normalizedStatuses) const statusesPlaceholder = `$${params.length}` if (workerSharingId) { - params.push(workerSharingId) filters.push( - `${workerOrderStatusExpression(`$${params.length}`)} = ANY(${statusesPlaceholder}::text[])`, + `${workerOrderStatusExpression()} = ANY(${statusesPlaceholder}::text[])`, ) } else { filters.push( @@ -4445,11 +4452,10 @@ function buildWorkOrderWhere({ filters.push(`wo.category_id = $${params.length}`) } if (workerSharingId) { - params.push(workerSharingId) filters.push( - `(wo.assigned_worker_id = $${params.length} + `(wo.assigned_worker_id = ${workerSharePlaceholder} OR ( - wo.last_assigned_worker_id = $${params.length} + wo.last_assigned_worker_id = ${workerSharePlaceholder} AND ( wo.status <> 'open' OR EXISTS ( @@ -4460,10 +4466,7 @@ function buildWorkOrderWhere({ ) ) ) - OR wo.id IN ( - SELECT work_order_id FROM work_order_shares - WHERE worker_id = $${params.length} AND status != 'cancelled' - ))`, + OR (worker_share.work_order_id IS NOT NULL AND worker_share.status != 'cancelled'))`, ) } if (visibleAfterIso) { @@ -4482,6 +4485,7 @@ function buildWorkOrderWhere({ )`) } return { + joins, whereClause: filters.length > 0 ? `WHERE ${filters.join(' AND ')}` : '', params, } diff --git a/apps/frontend/src/pages/worker/WorkerHallPage.tsx b/apps/frontend/src/pages/worker/WorkerHallPage.tsx index 90a7e316..2bf807eb 100644 --- a/apps/frontend/src/pages/worker/WorkerHallPage.tsx +++ b/apps/frontend/src/pages/worker/WorkerHallPage.tsx @@ -30,6 +30,7 @@ import { fetchWorkerHallOrders, fetchWorkerHallLeaderboard, fetchWorkerMyOrders, + fetchWorkerMyOrderOverview, fetchWorkerProfile, grabWorkerOrder, joinWorkerSharingOrder, @@ -93,25 +94,14 @@ export default function WorkerHallPage() { const hallSummaryQuery = useQuery({ queryKey: ['worker-hall-summary'], queryFn: async () => { - const [pendingAcceptanceResponse, problemResponse] = await Promise.all([ - fetchWorkerMyOrders({ - status: 'pending_acceptance', - page: 1, - pageSize: 1, - }), - fetchWorkerMyOrders({ - status: 'problem', - page: 1, - pageSize: 1, - }), - ]) - - const pendingAcceptanceCount = pendingAcceptanceResponse.data.pagination.total - const problemCount = problemResponse.data.pagination.total + const response = await fetchWorkerMyOrderOverview() + const summaryByStatus = new Map( + response.data.items.map((item) => [item.status, item.orderCount]), + ) return { - pendingAcceptanceCount, - problemCount, + pendingAcceptanceCount: Number(summaryByStatus.get('pending_acceptance') || 0), + problemCount: Number(summaryByStatus.get('problem') || 0), } satisfies HallSummary }, retry: false,