From ecac7b63db0279ed9b344dc592585f6f190c61fe Mon Sep 17 00:00:00 2001 From: yml2213 Date: Mon, 17 Aug 2026 15:49:47 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=8E=A5=E5=8D=95=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E7=AD=9B=E9=80=89=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/repositories/worker-platform/types.ts | 8 +++ .../worker-platform/work-order-repo.ts | 67 +++++++++++++++++++ .../services/worker-platform/admin-service.ts | 9 +++ .../pages/admin/panels/WorkOrdersPanel.tsx | 55 ++++++++++++++- .../src/services/admin/worker-platform.ts | 7 +- apps/frontend/src/styles/admin.css | 28 ++++++++ apps/frontend/src/types/worker-platform.ts | 8 +++ 7 files changed, 180 insertions(+), 2 deletions(-) diff --git a/apps/backend/src/repositories/worker-platform/types.ts b/apps/backend/src/repositories/worker-platform/types.ts index ff1b1106..0ebf69b5 100644 --- a/apps/backend/src/repositories/worker-platform/types.ts +++ b/apps/backend/src/repositories/worker-platform/types.ts @@ -253,6 +253,14 @@ export type ListInput = { sort?: 'id_desc' | 'worker_claimed_at_desc' } +export type WorkOrderStatistics = { + totalCount: number + userPaymentAmount: number + rewardAmount: number + refundAmount: number + statusCounts: Record +} + export type ProductRuleListInput = { enabled?: boolean | null keyword?: string 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 e1d07648..b0c86891 100644 --- a/apps/backend/src/repositories/worker-platform/work-order-repo.ts +++ b/apps/backend/src/repositories/worker-platform/work-order-repo.ts @@ -15,6 +15,7 @@ import type { WorkerDepositUnfreezeRow, WorkOrderRow, WorkOrderShareRow, + WorkOrderStatistics, WorkProductRuleRow, } from './types.js' import type { PoolClient } from 'pg' @@ -56,6 +57,14 @@ type WorkerOrderOverviewRow = { vip_evidence_pending_settlement_amount: number } +type WorkOrderStatisticsRow = { + status: string + total_count: number + user_payment_amount: number | string + reward_amount: number | string + refund_amount: number | string +} + export async function listWorkOrderShares( workOrderId: number | string, ): Promise { @@ -830,6 +839,64 @@ export async function listWorkOrders({ return { items: itemsResult.rows, total: Number(totalResult.rows[0]?.total || 0) } } +/** 按筛选条件汇总工单,统计范围不受当前分页影响。 */ +export async function getWorkOrderStatistics(input: ListInput = {}): Promise { + const { whereClause, params } = buildWorkOrderWhere(input) + const vipEvidencePendingWhere = ` + wo.status = 'accepted' + AND wo.acceptance_json->>'reviewMode' = 'vip_auto' + AND wo.acceptance_json->>'evidenceStatus' = 'pending' + AND NULLIF(wo.acceptance_json->>'evidenceDueAt', '')::timestamptz > NOW() + ` + const statusExpression = input.vipEvidencePending + ? `CASE WHEN ${vipEvidencePendingWhere} THEN 'vip_evidence_pending' ELSE wo.status END` + : 'wo.status' + const paymentExpression = ` + CASE + WHEN COALESCE(wo.material_json->'source'->>'orderAmountFen', '') ~ '^-?[0-9]+$' + THEN (wo.material_json->'source'->>'orderAmountFen')::bigint + ELSE COALESCE(o.total_amount, 0) + END + ` + const result = await query( + ` + SELECT + ${statusExpression} AS status, + COUNT(*)::int AS total_count, + COALESCE(SUM(${paymentExpression}), 0)::bigint AS user_payment_amount, + COALESCE(SUM(wo.reward_amount), 0)::bigint AS reward_amount, + COALESCE(SUM( + CASE + WHEN o.pay_status = 'refunded' OR o.order_status = 'refunded' + THEN ${paymentExpression} + ELSE 0 + END + ), 0)::bigint AS refund_amount + FROM work_orders wo + LEFT JOIN orders o ON o.id = wo.order_id + ${whereClause} + GROUP BY ${statusExpression} + `, + params, + ) + const statistics: WorkOrderStatistics = { + totalCount: 0, + userPaymentAmount: 0, + rewardAmount: 0, + refundAmount: 0, + statusCounts: {}, + } + for (const row of result.rows) { + const count = Number(row.total_count || 0) + statistics.totalCount += count + statistics.userPaymentAmount += Number(row.user_payment_amount || 0) + statistics.rewardAmount += Number(row.reward_amount || 0) + statistics.refundAmount += Number(row.refund_amount || 0) + statistics.statusCounts[row.status] = count + } + return statistics +} + /** 汇总打手本人各订单状态的可结算金额,拼单按个人份额计算。 */ export async function getWorkerOrderOverview(workerId: number | string): Promise< Array<{ diff --git a/apps/backend/src/services/worker-platform/admin-service.ts b/apps/backend/src/services/worker-platform/admin-service.ts index d5762a32..ee19124b 100644 --- a/apps/backend/src/services/worker-platform/admin-service.ts +++ b/apps/backend/src/services/worker-platform/admin-service.ts @@ -23,6 +23,7 @@ import { getWorkerLevelById, getWorkerLevelByKey, getWorkerUserById, + getWorkOrderStatistics, getWorkOrderByOrderItemId, getWorkProductRuleByKey, listAllWorkCategories, @@ -761,6 +762,13 @@ export async function listAdminWorkOrders(query: JsonObject = {}) { workerSharingId: workerId, vipEvidencePending, }) + const statistics = await getWorkOrderStatistics({ + statuses, + workOrderId, + keyword: String(query.keyword || '').trim(), + workerSharingId: workerId, + vipEvidencePending, + }) const shares = await listWorkOrderSharesByOrderIds(items.map((item) => Number(item.id))) const sharesByOrderId = new Map() for (const share of shares) { @@ -789,6 +797,7 @@ export async function listAdminWorkOrders(query: JsonObject = {}) { } }), pagination: { page, pageSize, total }, + statistics, } } diff --git a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx index 087bf354..3945d294 100644 --- a/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/WorkOrdersPanel.tsx @@ -64,7 +64,13 @@ import { updateAdminWorkOrder, updateAdminWorkOrderSharing, } from '@/services/admin' -import type { CollectField, UploadedFile, WorkOrder, WorkOrderShare } from '@/types/worker-platform' +import type { + CollectField, + UploadedFile, + WorkOrder, + WorkOrderShare, + WorkOrderStatistics, +} from '@/types/worker-platform' import { ADMIN_DEFAULT_PAGE_SIZE, buildAdminTablePagination } from '@/utils/admin-pagination' import { hasAdminRole } from '@/utils/admin-auth' import { formatAdminDateTime } from '@/utils/admin-time' @@ -959,6 +965,7 @@ export default function WorkOrdersPanel() { + ({ + status, + count: Number(statistics.statusCounts?.[status] || 0), + })) + const otherStatusItems = Object.entries(statistics.statusCounts || {}) + .filter(([status]) => !WORK_ORDER_STATISTICS_STATUS_ORDER.includes(status)) + .map(([status, count]) => ({ status, count: Number(count || 0) })) + + return ( + +
+ 本次筛选统计共 {statistics.totalCount} 笔,预计总发单价{' '} + {formatMoney(statistics.rewardAmount)}(不含销售提成),总接单价{' '} + {formatMoney(statistics.userPaymentAmount)},总退款{' '} + {formatMoney(statistics.refundAmount)},预计利润{' '} + {formatMoney(profit)}。 +
+
+ 其中: + {[...statusItems, ...otherStatusItems].map(({ status, count }, index) => ( + + {index > 0 ? ',' : ''} + {formatStatus(status)} + {count}笔 + + ))} +
+
+ ) +} + function shouldSkipPublishConfirm() { try { return localStorage.getItem(SKIP_PUBLISH_CONFIRM_KEY) === '1' diff --git a/apps/frontend/src/services/admin/worker-platform.ts b/apps/frontend/src/services/admin/worker-platform.ts index dc4bde57..ef6fc0fb 100644 --- a/apps/frontend/src/services/admin/worker-platform.ts +++ b/apps/frontend/src/services/admin/worker-platform.ts @@ -6,6 +6,7 @@ import type { WorkOrder, WorkOrderEvent, WorkOrderShare, + WorkOrderStatistics, WorkProductRule, WorkerFinanceConfig, WorkerFinanceRequest, @@ -237,7 +238,11 @@ export function reviewAdminWorkerFinanceRequest( } export function fetchAdminWorkOrders(params?: Record) { - return apiGet>('/api/v1/admin/worker-platform/orders', params) + return apiGet< + WorkerListResponse & { + statistics: WorkOrderStatistics + } + >('/api/v1/admin/worker-platform/orders', params) } export function fetchAdminWorkOrderEvents(workOrderId: number) { diff --git a/apps/frontend/src/styles/admin.css b/apps/frontend/src/styles/admin.css index 2e9dc3c4..6c3c085f 100644 --- a/apps/frontend/src/styles/admin.css +++ b/apps/frontend/src/styles/admin.css @@ -888,6 +888,34 @@ min-width: 0; } +.worker-orders-statistics-card { + border-left: 3px solid #ff6a00; +} + +.worker-orders-statistics-card .ant-card-body { + padding: 10px 16px; +} + +.worker-orders-statistics-summary, +.worker-orders-statistics-statuses { + color: rgba(0, 0, 0, 0.65); + font-size: 13px; + line-height: 22px; +} + +.worker-orders-statistics-summary strong { + color: #262626; + font-weight: 600; +} + +.worker-orders-statistics-summary strong.is-negative { + color: #cf1322; +} + +.worker-orders-statistics-statuses { + overflow-wrap: anywhere; +} + .platform-section-gap { margin-top: 14px; } diff --git a/apps/frontend/src/types/worker-platform.ts b/apps/frontend/src/types/worker-platform.ts index a43e6c5c..a90397e2 100644 --- a/apps/frontend/src/types/worker-platform.ts +++ b/apps/frontend/src/types/worker-platform.ts @@ -325,6 +325,14 @@ export type WorkOrder = { updatedAt: string } +export type WorkOrderStatistics = { + totalCount: number + userPaymentAmount: number + rewardAmount: number + refundAmount: number + statusCounts: Record +} + /** 工单流转事件(订单详情中的操作日志) */ export type WorkOrderEvent = { id: number