增加售后相关的
This commit is contained in:
@@ -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: '已超时',
|
||||
|
||||
Reference in New Issue
Block a user