增加售后相关的

This commit is contained in:
yml2213
2026-08-21 14:30:00 +08:00
parent 874b093a4b
commit f4d32a7c07
18 changed files with 1903 additions and 3 deletions
@@ -10,6 +10,7 @@ import {
import { useQuery, useQueryClient } from '@tanstack/react-query'
import {
App,
Alert,
Button,
Card,
Descriptions,
@@ -36,6 +37,7 @@ import ImagePreviewList from '@/components/files/ImagePreviewList'
import ImageUpload from '@/components/files/ImageUpload'
import {
fetchWorkerCancelRequests,
fetchWorkerAfterSalesCases,
fetchWorkerOrderFeedbacks,
fetchWorkerMyOrderOverview,
fetchWorkerMyOrders,
@@ -47,6 +49,7 @@ import {
supplementWorkerAcceptedOrderEvidence,
submitWorkerAcceptance,
submitWorkerCancelRequest,
submitWorkerAfterSalesCaseResponse,
submitWorkerOrderFeedback,
} from '@/services/worker'
import type {
@@ -54,6 +57,7 @@ import type {
UploadedFile,
WorkOrder,
WorkerAcceptance,
WorkerAfterSalesCase,
} from '@/types/worker-platform'
import { formatDateTime } from '@/utils/date-time'
import { getImageIdentity } from '@/utils/image-identity'
@@ -87,6 +91,10 @@ type FeedbackFormValues = {
content?: string
}
type AfterSalesResponseFormValues = {
response?: string
}
type NoteTheme = 'success' | 'processing' | 'danger' | 'warning' | 'purple' | 'default'
const PRESET_NOTE_TAGS: Array<{ label: string; theme: NoteTheme }> = [
@@ -201,11 +209,14 @@ export default function WorkerOrdersPage() {
const [noteOrder, setNoteOrder] = useState<WorkOrder | null>(null)
const [cancelRequestOrder, setCancelRequestOrder] = useState<WorkOrder | null>(null)
const [feedbackOrder, setFeedbackOrder] = useState<WorkOrder | null>(null)
const [afterSalesCase, setAfterSalesCase] = useState<WorkerAfterSalesCase | null>(null)
const [acceptanceFiles, setAcceptanceFiles] = useState<UploadedFile[]>([])
const [afterSalesResponseFiles, setAfterSalesResponseFiles] = useState<UploadedFile[]>([])
const [form] = Form.useForm<AcceptanceFormValues>()
const [noteForm] = Form.useForm<WorkerNoteFormValues>()
const [cancelRequestForm] = Form.useForm<CancelRequestFormValues>()
const [feedbackForm] = Form.useForm<FeedbackFormValues>()
const [afterSalesResponseForm] = Form.useForm<AfterSalesResponseFormValues>()
const ordersQuery = useQuery({
queryKey: ['worker-my-orders', status, keyword, page, pageSize],
@@ -240,9 +251,15 @@ export default function WorkerOrdersPage() {
queryFn: fetchWorkerOrderFeedbacks,
retry: false,
})
const afterSalesCasesQuery = useQuery({
queryKey: ['worker-after-sales-cases'],
queryFn: fetchWorkerAfterSalesCases,
retry: false,
})
const overviewByStatus = new Map(
(overviewQuery.data?.data.items || []).map((item) => [item.status, item]),
)
const afterSalesCases = afterSalesCasesQuery.data?.data.items || []
async function refreshAll() {
await Promise.all([
@@ -250,6 +267,7 @@ export default function WorkerOrdersPage() {
queryClient.invalidateQueries({ queryKey: ['worker-my-orders-overview'] }),
queryClient.invalidateQueries({ queryKey: ['worker-cancel-requests'] }),
queryClient.invalidateQueries({ queryKey: ['worker-order-feedbacks'] }),
queryClient.invalidateQueries({ queryKey: ['worker-after-sales-cases'] }),
])
}
@@ -377,6 +395,29 @@ export default function WorkerOrdersPage() {
}
}
function openAfterSalesResponseModal(item: WorkerAfterSalesCase) {
setAfterSalesCase(item)
setAfterSalesResponseFiles([])
afterSalesResponseForm.resetFields()
}
async function submitAfterSalesResponse(values: AfterSalesResponseFormValues) {
if (!afterSalesCase) return
try {
await submitWorkerAfterSalesCaseResponse(afterSalesCase.caseId, {
response: String(values.response || '').trim(),
files: afterSalesResponseFiles,
})
message.success('售后说明已提交,等待客服处理')
setAfterSalesCase(null)
setAfterSalesResponseFiles([])
afterSalesResponseForm.resetFields()
await refreshAll()
} catch (error) {
message.error(error instanceof Error ? error.message : '提交售后说明失败')
}
}
async function saveOrderNote(values: WorkerNoteFormValues) {
if (!noteOrder) return
try {
@@ -727,6 +768,65 @@ export default function WorkerOrdersPage() {
return (
<>
{afterSalesCases.length > 0 ? (
<Card
className="worker-after-sales-card"
title="售后问题单"
size="small"
extra={
<Button
size="small"
icon={<ReloadOutlined />}
loading={afterSalesCasesQuery.isFetching}
onClick={() => afterSalesCasesQuery.refetch()}
>
</Button>
}
>
<Space direction="vertical" size={12} style={{ width: '100%' }}>
{afterSalesCases.map((item) => (
<div key={item.caseId} className="worker-order-detail-value">
<Space wrap size={[8, 6]}>
<Tag color={resolveWorkerAfterSalesStatusColor(item.status)}>
{formatWorkerAfterSalesStatus(item.status)}
</Tag>
<Typography.Text strong>
{item.productName || item.workOrderNo || item.caseNo}
</Typography.Text>
<Typography.Text type="secondary">{item.caseNo}</Typography.Text>
</Space>
<Typography.Paragraph style={{ margin: '8px 0 4px' }}>
{item.complaintNote || '-'}
</Typography.Paragraph>
{item.complaintFiles.length > 0 ? (
<ImagePreviewList files={item.complaintFiles} size={52} maxVisible={4} />
) : null}
{item.status === 'awaiting_worker_response' ? (
<Button
type="primary"
size="small"
style={{ marginTop: 8 }}
onClick={() => openAfterSalesResponseModal(item)}
>
</Button>
) : item.status === 'under_review' ? (
<Typography.Text type="secondary"></Typography.Text>
) : item.status === 'recovery_pending' ? (
<Typography.Text type="danger">
{formatMoney(item.debtAmount)}
</Typography.Text>
) : item.resolutionNote ? (
<Typography.Text type="secondary">
{item.resolutionNote}
</Typography.Text>
) : null}
</div>
))}
</Space>
</Card>
) : null}
{isMobile ? (
<div className="worker-orders-mobile-page">
{/* 顶部水平滑动状态切换胶囊条 */}
@@ -1406,6 +1506,52 @@ export default function WorkerOrdersPage() {
</Form.Item>
</Form>
</Modal>
<Modal
title={`售后说明 · ${afterSalesCase?.caseNo || ''}`}
open={Boolean(afterSalesCase)}
width={isMobile ? '92%' : 560}
onCancel={() => {
setAfterSalesCase(null)
setAfterSalesResponseFiles([])
afterSalesResponseForm.resetFields()
}}
onOk={() => afterSalesResponseForm.submit()}
okText="提交说明"
destroyOnHidden
>
{afterSalesCase ? (
<>
<Alert
type="warning"
showIcon
message="请针对售后问题说明情况或提交补救材料,原验收资料不可在此修改。"
style={{ marginBottom: 16 }}
/>
<Typography.Paragraph type="secondary">
{afterSalesCase.complaintNote}
</Typography.Paragraph>
<Form
form={afterSalesResponseForm}
layout="vertical"
onFinish={submitAfterSalesResponse}
>
<Form.Item label="说明" name="response">
<Input.TextArea rows={4} maxLength={2000} showCount />
</Form.Item>
<Form.Item label="补救材料图片">
<ImageUpload
scene="worker-acceptance"
scope="worker"
value={afterSalesResponseFiles}
onChange={setAfterSalesResponseFiles}
maxCount={10}
showPasteHint
/>
</Form.Item>
</Form>
</>
) : null}
</Modal>
<Modal
title={`订单备注 · ${getDisplayOrderNo(noteOrder)}`}
open={Boolean(noteOrder)}
@@ -1815,6 +1961,20 @@ function getSettlementLabel(status: string) {
return '预计'
}
function formatWorkerAfterSalesStatus(status: string) {
if (status === 'awaiting_worker_response') return '待说明'
if (status === 'under_review') return '处理中'
if (status === 'recovery_pending') return '追缴中'
return '已关闭'
}
function resolveWorkerAfterSalesStatusColor(status: string) {
if (status === 'awaiting_worker_response') return 'orange'
if (status === 'under_review') return 'blue'
if (status === 'recovery_pending') return 'red'
return 'green'
}
function formatStatus(status: string) {
const labels: Record<string, string> = {
open: '已超时',