import { useEffect, useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import { Card, Form, Input, Select, Button, Space, Tag, message, Modal, Spin, Steps, Descriptions, Divider, } from 'antd'; import { ArrowLeftOutlined, FileTextOutlined, PrinterOutlined, QrcodeOutlined, SaveOutlined, SendOutlined, StopOutlined, UserSwitchOutlined, } from '@ant-design/icons'; import { aftersalesApi, authApi, usersApi } from '@/services/api'; import logo from '@/assets/img/logo.png?url'; import type { AftersalesOrder, AftersalesServiceType, AftersalesWorkOrderStatus, UpdateAftersalesRequest, User, } from '@/types'; import './styles/AftersalesDetail.css'; const SERVICE_TYPE_LABEL: Record = { software: '软件故障', hardware: '硬件故障', maintenance: '售后维保', }; const WORK_ORDER_STATUS_LABEL: Record = { created: '待处理', pending_confirmation: '待客户确认', closed: '已完成', rejected: '已退回', }; const WORK_ORDER_STATUS_COLOR: Record = { created: 'default', pending_confirmation: 'processing', closed: 'success', rejected: 'warning', }; const AUTHORIZATION_STATUS_LABEL = { pending: '待确认', authorized: '已授权', unauthorized: '未授权', } as const; function statusStepIndex(status: AftersalesWorkOrderStatus): number { switch (status) { case 'created': return 0; case 'pending_confirmation': return 1; case 'closed': case 'rejected': return 2; } } function formatDateTime(value?: string) { if (!value) return '-'; return new Date(value).toLocaleString('zh-CN'); } function AftersalesDetailPage() { const { serialNumber = '' } = useParams<{ serialNumber: string }>(); const navigate = useNavigate(); const currentUser = authApi.getCurrentUser(); const isAdmin = currentUser?.role === 'admin'; const [order, setOrder] = useState(null); const [loading, setLoading] = useState(true); const [saving, setSaving] = useState(false); const [submitting, setSubmitting] = useState(false); const [form] = Form.useForm(); const [qrModalVisible, setQrModalVisible] = useState(false); const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); const [qrUrl, setQrUrl] = useState(''); const [electronicFormVisible, setElectronicFormVisible] = useState(false); const [reassignModalVisible, setReassignModalVisible] = useState(false); const [reassignTechnicianId, setReassignTechnicianId] = useState(); const [assignableUsers, setAssignableUsers] = useState([]); const loadOrder = async () => { setLoading(true); try { const data = await aftersalesApi.get(serialNumber); setOrder(data); form.setFieldsValue({ companyAddress: data.companyAddress, contactName: data.contactName, contactPhone: data.contactPhone, serviceType: data.serviceType, issueDescription: data.issueDescription, resolutionNote: data.resolutionNote, }); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '加载工单失败'); } finally { setLoading(false); } }; useEffect(() => { if (serialNumber) loadOrder(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [serialNumber]); const openReassign = async () => { setReassignModalVisible(true); if (assignableUsers.length === 0) { try { const users = await usersApi.assignable(); setAssignableUsers(users); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '加载技术员列表失败'); } } }; const handleSave = async (values: UpdateAftersalesRequest) => { if (!order) return; setSaving(true); try { const updated = await aftersalesApi.update(order.serialNumber, values); setOrder(updated); message.success('保存成功'); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '保存失败'); } finally { setSaving(false); } }; const handleSubmitForConfirmation = async () => { if (!order) return; const resolutionNote = form.getFieldValue('resolutionNote')?.trim(); if (!resolutionNote) { message.error('请先填写处理结果'); return; } Modal.confirm({ title: '确认提交', content: '提交后工单将进入"待客户确认"状态,客户扫码确认后才能关闭工单。', okText: '提交', cancelText: '取消', onOk: async () => { setSubmitting(true); try { const updated = await aftersalesApi.submit(order.serialNumber, resolutionNote); setOrder(updated); message.success('已提交客户确认'); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '提交失败'); } finally { setSubmitting(false); } }, }); }; const handleGenerateQrCode = async () => { if (!order) return; try { const result = await aftersalesApi.generateQrCode(order.serialNumber); const data = result.qrCodeData.startsWith('data:') ? result.qrCodeData : `data:image/png;base64,${result.qrCodeData}`; setQrCodeDataUrl(data); setQrUrl(result.queryUrl); setQrModalVisible(true); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '生成二维码失败'); } }; const handleDownloadQR = () => { if (!order || !qrCodeDataUrl) return; const link = document.createElement('a'); link.download = `${order.serialNumber}.png`; link.href = qrCodeDataUrl; link.click(); }; const handlePrintElectronicForm = () => { const formNode = document.querySelector('.aftersales-electronic-form'); if (!formNode) return; const printWindow = window.open('', '_blank', 'width=960,height=720'); if (!printWindow) { message.error('无法打开打印窗口,请检查浏览器弹窗设置'); return; } printWindow.document.write(` ${order?.serialNumber || '售后电子表单'} ${formNode.outerHTML} `); printWindow.document.close(); printWindow.focus(); setTimeout(() => printWindow.print(), 300); }; const handleForceClose = () => { if (!order) return; Modal.confirm({ title: '强制关闭工单', content: '强制关闭后工单状态将变为"已完成",且不可恢复。', okText: '强制关闭', okType: 'danger', cancelText: '取消', onOk: async () => { try { const updated = await aftersalesApi.forceClose(order.serialNumber); setOrder(updated); message.success('工单已强制关闭'); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '强制关闭失败'); } }, }); }; const handleReassign = async () => { if (!order || !reassignTechnicianId) { message.error('请输入新的技术员 ID'); return; } try { const updated = await aftersalesApi.reassign(order.serialNumber, reassignTechnicianId); setOrder(updated); message.success('重新分配成功'); setReassignModalVisible(false); setReassignTechnicianId(undefined); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '重新分配失败'); } }; if (loading) { return (
); } if (!order) { return null; } const isClosed = order.workOrderStatus === 'closed'; const isPendingCustomer = order.workOrderStatus === 'pending_confirmation'; const canSubmit = !isClosed && !isPendingCustomer && (isAdmin || order.technicianId === currentUser?.id); const canEdit = !isClosed && (isAdmin || order.technicianId === currentUser?.id); const stepStatus = order.workOrderStatus === 'rejected' ? 'error' : isClosed ? 'finish' : 'process'; return (
{isAdmin && !isClosed && ( <> )} } > {order.companyName} {order.technician?.name || '-'} {order.creator?.name || '-'} {order.authorizationStatus === 'authorized' ? '已授权' : order.authorizationStatus === 'unauthorized' ? '未授权' : '待确认'} {order.authorizationStatus === 'authorized' && order.signature && (
客户确认签名 {order.confirmedAt && ( 签署时间:{new Date(order.confirmedAt).toLocaleString('zh-CN')} )}
客户确认签名
)}
setReassignTechnicianId(v)} showSearch optionFilterProp="label" options={assignableUsers.map((u) => ({ value: u.id, label: `${u.name}(${u.username})${u.role === 'admin' ? ' · 管理员' : ' · 技术员'}`, }))} />
); } export default AftersalesDetailPage;