From 67bb5fec15ebca81bd4b36b0ca948d9cdece48f0 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 22 Aug 2026 11:25:18 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=89=93=E6=89=8B=E7=AB=AF?= =?UTF-8?q?=E5=94=AE=E5=90=8E=E9=97=AE=E9=A2=98=E5=8D=95=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=B1=95=E7=A4=BA=E9=97=AE=E9=A2=98=E7=B1=BB=E5=9E=8B=E5=A4=84?= =?UTF-8?q?=E7=90=86=E7=BB=93=E6=9E=9C=E4=B8=8E=E6=8F=90=E4=BA=A4=E8=AF=B4?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/worker/WorkerAfterSalesPage.tsx | 358 ++++++++++++++---- apps/frontend/src/styles/worker.css | 100 +++++ 2 files changed, 377 insertions(+), 81 deletions(-) diff --git a/apps/frontend/src/pages/worker/WorkerAfterSalesPage.tsx b/apps/frontend/src/pages/worker/WorkerAfterSalesPage.tsx index fe6640e2..5455ac82 100644 --- a/apps/frontend/src/pages/worker/WorkerAfterSalesPage.tsx +++ b/apps/frontend/src/pages/worker/WorkerAfterSalesPage.tsx @@ -4,7 +4,6 @@ import { App, Alert, Button, - Card, Empty, Form, Input, @@ -20,6 +19,7 @@ import ImagePreviewList from '@/components/files/ImagePreviewList' import ImageUpload from '@/components/files/ImageUpload' import { fetchWorkerAfterSalesCases, submitWorkerAfterSalesCaseResponse } from '@/services/worker' import type { UploadedFile, WorkerAfterSalesCase } from '@/types/worker-platform' +import { formatDateTime } from '@/utils/date-time' import { useIsMobile } from '@/utils/use-is-mobile' type AfterSalesResponseFormValues = { @@ -41,6 +41,13 @@ const STATUS_FILTERS: Array<{ value: StatusFilter; label: string }> = [ { value: 'closed', label: '已关闭' }, ] +/** 待办优先:待说明 > 处理中 > 追缴中 > 已关闭,同状态按发起时间倒序。 */ +const STATUS_WEIGHT: Record = { + awaiting_worker_response: 0, + under_review: 1, + recovery_pending: 2, +} + export function formatMoney(value: number | undefined) { return `¥${((Number(value || 0) || 0) / 100).toFixed(2)}` } @@ -59,11 +66,40 @@ export function resolveWorkerAfterSalesStatusColor(status: string) { return 'green' } +/** 与后台售后面板保持相同的问题类型口径。 */ +export function formatWorkerAfterSalesProblemType(value: string) { + if (value === 'fake_evidence') return '疑似P图 / 伪造验收' + if (value === 'result_not_met') return '实际结果未达标' + return '其他售后问题' +} + +function resolveWorkerAfterSalesProblemTypeColor(value: string) { + if (value === 'fake_evidence') return 'red' + if (value === 'result_not_met') return 'volcano' + return 'default' +} + +export function formatWorkerAfterSalesResolution(value: string) { + if (value === 'upheld') return '问题成立' + if (value === 'remedied') return '补救完成' + if (value === 'not_upheld') return '问题不成立' + return '' +} + +function resolveWorkerAfterSalesResolutionColor(value: string) { + if (value === 'upheld') return 'red' + if (value === 'remedied') return 'gold' + if (value === 'not_upheld') return 'green' + return 'default' +} + +function isAfterSalesClosed(status: string) { + return !['awaiting_worker_response', 'under_review', 'recovery_pending'].includes(status) +} + function matchesFilter(item: WorkerAfterSalesCase, filter: StatusFilter) { if (filter === 'all') return true - if (filter === 'closed') { - return !['awaiting_worker_response', 'under_review', 'recovery_pending'].includes(item.status) - } + if (filter === 'closed') return isAfterSalesClosed(item.status) return item.status === filter } @@ -77,11 +113,26 @@ export default function WorkerAfterSalesPage() { retry: false, }) const allCases = afterSalesCasesQuery.data?.data.items || [] + const pendingCount = allCases.filter((item) => item.status === 'awaiting_worker_response').length + const countByFilter = useMemo(() => { + const counts: Record = { all: allCases.length } + for (const filter of STATUS_FILTERS) { + if (filter.value === 'all') continue + counts[filter.value] = allCases.filter((item) => matchesFilter(item, filter.value)).length + } + return counts + }, [allCases]) const afterSalesCases = useMemo( - () => allCases.filter((item) => matchesFilter(item, statusFilter)), + () => + allCases + .filter((item) => matchesFilter(item, statusFilter)) + .sort( + (a, b) => + (STATUS_WEIGHT[a.status] ?? 3) - (STATUS_WEIGHT[b.status] ?? 3) || + String(b.createdAt).localeCompare(String(a.createdAt)), + ), [allCases, statusFilter], ) - const pendingCount = allCases.filter((item) => item.status === 'awaiting_worker_response').length return (
- - 售后问题单 - {pendingCount > 0 ? {pendingCount} 条待说明 : null} - - } - extra={ - - } - > - - setStatusFilter(value as StatusFilter)} - block={isMobile} - /> - {afterSalesCasesQuery.isLoading ? ( - 加载中… - ) : afterSalesCases.length === 0 ? ( - - ) : ( - afterSalesCases.map((item) => ( - - )) - )} - - + {pendingCount > 0 ? ( + + ) : null} +
+ ({ + value: filter.value, + label: ( + + {filter.label} + {countByFilter[filter.value] > 0 ? ( + + {countByFilter[filter.value]} + + ) : null} + + ), + }))} + value={statusFilter} + onChange={(value) => setStatusFilter(value as StatusFilter)} + block={isMobile} + /> + +
+ + {afterSalesCasesQuery.isLoading ? ( + 加载中… + ) : afterSalesCasesQuery.isError ? ( + + ) : afterSalesCases.length === 0 ? ( + + ) : ( +
+ {afterSalesCases.map((item) => ( + + ))} +
+ )}
) } @@ -147,9 +215,14 @@ function WorkerAfterSalesItem({ const [form] = Form.useForm() async function submit(values: AfterSalesResponseFormValues) { + const response = String(values.response || '').trim() + if (!response && files.length === 0) { + message.error('请填写说明或至少上传一张补救材料图片') + return + } try { await submitWorkerAfterSalesCaseResponse(item.caseId, { - response: String(values.response || '').trim(), + response, files, }) message.success('售后说明已提交,等待客服处理') @@ -162,31 +235,135 @@ function WorkerAfterSalesItem({ } } + const resolutionLabel = formatWorkerAfterSalesResolution(item.resolutionAction) + const hasRecoveryDetail = + Number(item.recoveryAmount || 0) > 0 || + Number(item.pendingDepositDeductionAmount || 0) > 0 || + Number(item.availableDeductionAmount || 0) > 0 || + Number(item.debtAmount || 0) > 0 + return ( <> -
- - - {formatWorkerAfterSalesStatus(item.status)} - - - {item.productName || item.workOrderNo || item.caseNo} +
+
+ + + {formatWorkerAfterSalesStatus(item.status)} + + + {formatWorkerAfterSalesProblemType(item.problemType)} + + + + {item.productName || item.caseNo} - - {item.caseNo} + + 发起 {formatDateTime(item.createdAt)} +
+ + + {item.platformOrderId || item.workOrderNo || item.caseNo} + + + 售后单号 {item.caseNo} + + {item.shareQuantity ? ( + + 拼单 {item.shareQuantity} 份 + + ) : null} - - {item.complaintNote || '-'} - - {item.complaintFiles.length > 0 ? ( - + +
+
投诉内容
+ + {item.complaintNote || '-'} + + {item.complaintFiles.length > 0 ? ( + + ) : null} +
+ + {item.workerResponse || item.workerResponseFiles.length > 0 ? ( +
+
+ 我提交的说明{item.respondedAt ? ` · ${formatDateTime(item.respondedAt)}` : ''} +
+ {item.workerResponse ? ( + + {item.workerResponse} + + ) : null} + {item.workerResponseFiles.length > 0 ? ( + + ) : null} +
) : null} -
- {item.status === 'awaiting_worker_response' ? ( + + {resolutionLabel || item.resolutionNote || hasRecoveryDetail ? ( +
+
处理结果
+ + {resolutionLabel ? ( + + + {resolutionLabel} + + {item.resolvedAt ? ( + + {formatDateTime(item.resolvedAt)} + + ) : null} + + ) : null} + {item.resolutionNote ? ( + + {item.resolutionNote} + + ) : null} + {hasRecoveryDetail ? ( + + {Number(item.recoveryAmount || 0) > 0 ? ( + + 追缴 {formatMoney(item.recoveryAmount)} + + ) : null} + {Number(item.pendingDepositDeductionAmount || 0) > 0 ? ( + + 押金抵扣 {formatMoney(item.pendingDepositDeductionAmount)} + + ) : null} + {Number(item.availableDeductionAmount || 0) > 0 ? ( + + 余额抵扣 {formatMoney(item.availableDeductionAmount)} + + ) : null} + {Number(item.debtAmount || 0) > 0 ? ( + + 待追缴 {formatMoney(item.debtAmount)},后续验收报酬将自动抵扣 + + ) : null} + + ) : null} + +
+ ) : item.status === 'under_review' ? ( + 已提交说明,等待客服处理 + ) : null} + + {item.status === 'awaiting_worker_response' ? ( +
- ) : item.status === 'under_review' ? ( - 已提交说明,等待客服处理 - ) : item.status === 'recovery_pending' ? ( - - 待追缴 {formatMoney(item.debtAmount)},后续验收报酬将自动抵扣 - - ) : item.resolutionNote ? ( - 处理结果:{item.resolutionNote} - ) : null} -
-
+
+ ) : null} + setResponseOpen(false)} @@ -220,12 +389,39 @@ function WorkerAfterSalesItem({ type="warning" showIcon message="请针对售后问题说明情况或提交补救材料,原验收资料不可在此修改。" - style={{ marginBottom: 16 }} + style={{ marginBottom: 12 }} /> - {item.complaintNote} -
- - +
+
+ 投诉内容 · {formatWorkerAfterSalesProblemType(item.problemType)} +
+ + {item.complaintNote || '-'} + + {item.complaintFiles.length > 0 ? ( + + ) : null} +
+ + + String(value || '').trim() || files.length > 0 + ? Promise.resolve() + : Promise.reject(new Error('请填写说明或至少上传一张图片')), + }, + ]} + > +