From 60fe00b28c99ba056e7d2ebdb29c8a788f874f77 Mon Sep 17 00:00:00 2001 From: zhaoxing <2558691827@qq.com> Date: Sun, 25 Oct 2026 16:45:38 +0800 Subject: [PATCH] =?UTF-8?q?fix:=E6=89=93=E6=89=8B=E7=AB=AF/=E6=88=91?= =?UTF-8?q?=E7=9A=84=E8=AE=A2=E5=8D=95/=E9=AA=8C=E6=94=B6=EF=BC=9A?= =?UTF-8?q?=E9=AA=8C=E6=94=B6=E8=A7=86=E9=A2=91=E5=92=8C=E9=AA=8C=E6=94=B6?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E8=87=B3=E5=B0=91=E4=B8=8A=E4=BC=A0=E4=B8=80?= =?UTF-8?q?=E4=B8=AA=E5=B0=B1=E5=A5=BD=E4=BA=86=EF=BC=8C=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E6=8C=89=E7=85=A7=E4=B8=8A=E4=BC=A0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E8=B5=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/admin-notification-service.ts | 3 ++ .../worker-acceptance-service.ts | 52 +++++++++++-------- .../src/pages/worker/WorkerOrderModals.tsx | 15 +++--- .../src/pages/worker/WorkerOrdersPage.tsx | 19 +++++-- 4 files changed, 55 insertions(+), 34 deletions(-) diff --git a/apps/backend/src/services/admin/admin-notification-service.ts b/apps/backend/src/services/admin/admin-notification-service.ts index 46ef0706..9c7aa6d8 100644 --- a/apps/backend/src/services/admin/admin-notification-service.ts +++ b/apps/backend/src/services/admin/admin-notification-service.ts @@ -77,6 +77,8 @@ export async function createWorkerAcceptanceAdminNotification(input: { productName: string workerName: string imageCount: number + /** 是否提交了验收视频:视频与图片至少一个,通知里要体现,避免显示成“0 张图片”。 */ + hasVideo?: boolean /** 拼单份额:每份提交各生成一条待办(按 shareId 去重) */ shareId?: number | null }) { @@ -97,6 +99,7 @@ export async function createWorkerAcceptanceAdminNotification(input: { `打手:${input.workerName || '-'}`, ...(shareId > 0 ? [`份额:#${shareId}`] : []), `验收图片:${input.imageCount} 张`, + ...(input.hasVideo ? ['验收视频:已上传'] : []), ].join(';'), actionPath: `${ADMIN_WORKER_PLATFORM_PATH}?tab=orders`, now: new Date().toISOString(), diff --git a/apps/backend/src/services/worker-platform/worker-acceptance-service.ts b/apps/backend/src/services/worker-platform/worker-acceptance-service.ts index 07b5cb1e..77b6b191 100644 --- a/apps/backend/src/services/worker-platform/worker-acceptance-service.ts +++ b/apps/backend/src/services/worker-platform/worker-acceptance-service.ts @@ -1,3 +1,4 @@ +import { hasAcceptanceEvidence } from '../../domain/work-order-acceptance-evidence.js' import { WORK_ORDER_STATUS } from '../../domain/work-order-status.js' import { isFriendGiftAcceptanceMode, @@ -155,8 +156,10 @@ export async function submitWorkerOrderAcceptance( workOrder.draft_acceptance_json, ) const video = resolveAcceptanceVideo(payload, workOrder.draft_acceptance_json) - if (imageUrls.length === 0 && !canAutoAcceptWithoutEvidence) { - throw createHttpError('请上传验收图片', { + // 验收图片与验收视频至少上传一个;有视频时视同有图片,后续口径完全一致。 + const hasEvidence = hasAcceptanceEvidence({ imageUrls, video }) + if (!hasEvidence && !canAutoAcceptWithoutEvidence) { + throw createHttpError('请上传验收图片或验收视频', { statusCode: 400, errorCode: 'work_order_acceptance_image_required', }) @@ -170,9 +173,8 @@ export async function submitWorkerOrderAcceptance( ...(canAutoAcceptWithoutEvidence ? { reviewMode: 'vip_auto', - evidenceStatus: imageUrls.length > 0 ? 'complete' : 'pending', - evidenceDueAt: - imageUrls.length > 0 ? null : addHours(now, VIP_AUTO_ACCEPT_EVIDENCE_WINDOW_HOURS), + evidenceStatus: hasEvidence ? 'complete' : 'pending', + evidenceDueAt: hasEvidence ? null : addHours(now, VIP_AUTO_ACCEPT_EVIDENCE_WINDOW_HOURS), } : {}), } @@ -252,6 +254,7 @@ export async function submitWorkerOrderAcceptance( productName: workOrder.product_name, workerName: worker.display_name || worker.username, imageCount: imageUrls.length, + hasVideo: Boolean(video), }) publishWorkOrderRealtimeChange({ workOrderId: Number(workOrder.id), @@ -303,6 +306,7 @@ export async function remindWorkerOrderAcceptance( productName: workOrder.product_name, workerName: worker.display_name || worker.username, imageCount: normalizeStringArray(safeParseJson(workOrder.acceptance_json).imageUrls).length, + hasVideo: Boolean(safeParseJson(workOrder.acceptance_json).video), }) notification = await getWorkerAcceptanceReminder(Number(workOrder.id)) } @@ -396,7 +400,7 @@ export async function supplementWorkerAcceptedOrderEvidence( const payloadVideo = resolveAcceptanceVideoFromPayload(payload) const video = payloadVideo || readAcceptanceVideoLenient(previous) if (!mergedEvidence.changed && !payloadVideo) { - throw createHttpError('请上传新的验收图片', { + throw createHttpError('请上传新的验收图片或新的验收视频', { statusCode: 400, errorCode: 'work_order_evidence_supplement_image_required', }) @@ -485,8 +489,10 @@ async function submitWorkerSharingAcceptance( sharingShare.draft_acceptance_json, ) const video = resolveAcceptanceVideo(payload, sharingShare.draft_acceptance_json) - if (imageUrls.length === 0 && !canAutoAcceptWithoutEvidence) { - throw createHttpError('请上传验收图片', { + // 验收图片与验收视频至少上传一个;有视频时视同有图片,后续口径完全一致。 + const hasEvidence = hasAcceptanceEvidence({ imageUrls, video }) + if (!hasEvidence && !canAutoAcceptWithoutEvidence) { + throw createHttpError('请上传验收图片或验收视频', { statusCode: 400, errorCode: 'work_order_acceptance_image_required', }) @@ -500,9 +506,8 @@ async function submitWorkerSharingAcceptance( ...(canAutoAcceptWithoutEvidence ? { reviewMode: 'vip_auto', - evidenceStatus: imageUrls.length > 0 ? 'complete' : 'pending', - evidenceDueAt: - imageUrls.length > 0 ? null : addHours(now, VIP_AUTO_ACCEPT_EVIDENCE_WINDOW_HOURS), + evidenceStatus: hasEvidence ? 'complete' : 'pending', + evidenceDueAt: hasEvidence ? null : addHours(now, VIP_AUTO_ACCEPT_EVIDENCE_WINDOW_HOURS), } : {}), } @@ -562,6 +567,7 @@ async function submitWorkerSharingAcceptance( productName: workOrder.product_name, workerName: worker.display_name || worker.username, imageCount: acceptance.imageUrls.length, + hasVideo: Boolean(acceptance.video), shareId: Number(sharingShare.id), }) await publishWorkOrderParticipantChange(Number(workOrder.id), { @@ -593,7 +599,7 @@ async function supplementWorkerAcceptedSharingEvidence( const payloadVideo = resolveAcceptanceVideoFromPayload(payload) const video = payloadVideo || readAcceptanceVideoLenient(previous) if (!mergedEvidence.changed && !payloadVideo) { - throw createHttpError('请上传新的验收图片', { + throw createHttpError('请上传新的验收图片或新的验收视频', { statusCode: 400, errorCode: 'work_order_sharing_evidence_supplement_image_required', }) @@ -846,15 +852,16 @@ export async function saveWorkerOrderAcceptanceDraft( const worker = await getRequiredWorker(session.workerId) if (workOrder.status === WORK_ORDER_STATUS.PENDING_ACCEPTANCE) { - if (imageUrls.length === 0) { - throw createHttpError('请至少保留一张验收图片', { + const previous = safeParseJson(workOrder.acceptance_json) + // 已提交后只允许替换视频,不允许删除证据。 + const video = resolveAcceptanceVideo(payload, previous) + // 验收图片与验收视频至少保留一个。 + if (!hasAcceptanceEvidence({ imageUrls, video })) { + throw createHttpError('请至少保留一张验收图片或一段验收视频', { statusCode: 400, errorCode: 'work_order_acceptance_image_required', }) } - const previous = safeParseJson(workOrder.acceptance_json) - // 已提交后只允许替换视频,不允许删除证据。 - const video = resolveAcceptanceVideo(payload, previous) const acceptance = { note: String(payload.note ?? previous.note ?? '').trim(), files, @@ -944,15 +951,16 @@ async function saveWorkerSharingAcceptanceDraft( const { files, imageUrls } = resolveAcceptanceImages(payload, {}) if (sharingShare.status === 'submitted') { - if (imageUrls.length === 0) { - throw createHttpError('请至少保留一张验收图片', { + const previous = safeParseJson(sharingShare.acceptance_json) + // 已提交后只允许替换视频,不允许删除证据。 + const video = resolveAcceptanceVideo(payload, previous) + // 验收图片与验收视频至少保留一个。 + if (!hasAcceptanceEvidence({ imageUrls, video })) { + throw createHttpError('请至少保留一张验收图片或一段验收视频', { statusCode: 400, errorCode: 'work_order_acceptance_image_required', }) } - const previous = safeParseJson(sharingShare.acceptance_json) - // 已提交后只允许替换视频,不允许删除证据。 - const video = resolveAcceptanceVideo(payload, previous) const acceptance = { note: String(payload.note ?? previous.note ?? '').trim(), files, diff --git a/apps/frontend/src/pages/worker/WorkerOrderModals.tsx b/apps/frontend/src/pages/worker/WorkerOrderModals.tsx index ed7397c0..503c595e 100644 --- a/apps/frontend/src/pages/worker/WorkerOrderModals.tsx +++ b/apps/frontend/src/pages/worker/WorkerOrderModals.tsx @@ -267,17 +267,18 @@ export function WorkerOrderModals({ diff --git a/apps/frontend/src/pages/worker/WorkerOrdersPage.tsx b/apps/frontend/src/pages/worker/WorkerOrdersPage.tsx index ef46d14d..f3aac03a 100644 --- a/apps/frontend/src/pages/worker/WorkerOrdersPage.tsx +++ b/apps/frontend/src/pages/worker/WorkerOrdersPage.tsx @@ -518,8 +518,10 @@ export default function WorkerOrdersPage() { } const canAutoAccept = canAutoAcceptOrder(editingOrder, workerCanAutoAcceptWithoutEvidence) - if (acceptanceFiles.length === 0 && !canAutoAccept) { - message.error('请上传验收图片') + // 验收图片与验收视频至少上传一个;有视频时视同有图片,后续口径完全一致。 + const hasEvidence = acceptanceFiles.length > 0 || Boolean(acceptanceVideo) + if (!hasEvidence && !canAutoAccept) { + message.error('请上传验收图片或验收视频') return } try { @@ -529,7 +531,14 @@ export default function WorkerOrdersPage() { video: acceptanceVideo, ...(editingShareId ? { shareId: editingShareId } : {}), }) - message.success(canAutoAccept ? '已自动验收,可在 24 小时内补充图片' : '已提交验收') + // 已有图片或视频时证据即算齐备,不再提示补图。 + message.success( + canAutoAccept + ? hasEvidence + ? '已自动验收' + : '已自动验收,可在 24 小时内补充图片或视频' + : '已提交验收', + ) setEditingOrder(null) setAcceptanceFiles([]) setAcceptanceVideo(null) @@ -543,8 +552,8 @@ export default function WorkerOrdersPage() { async function saveAcceptanceImages(values: AcceptanceFormValues) { if (!editingOrder) return - if (hasSubmittedAcceptance(editingOrder) && acceptanceFiles.length === 0) { - message.error('请至少保留一张验收图片') + if (hasSubmittedAcceptance(editingOrder) && acceptanceFiles.length === 0 && !acceptanceVideo) { + message.error('请至少保留一张验收图片或一段验收视频') return } try {