import { useEffect, useState } from 'react'; import { Button, Card, Form, Image, Input, Modal, Pagination, Space, Table, Tag, Upload, message, } from 'antd'; import { DeleteOutlined, EyeOutlined, LinkOutlined, PlusOutlined, QrcodeOutlined, StopOutlined, UploadOutlined, } from '@ant-design/icons'; import type { UploadFile } from 'antd'; import { productTracesApi } from '@/services/api'; import type { CreateProductTraceRequest, ProductTrace, UpdateProductTraceRequest } from '@/types'; type ProductTraceFormValues = Omit & { manufactureDate: string; }; function formatDate(value: string) { if (!value) return '-'; return new Date(value).toLocaleDateString('zh-CN'); } function toDateInputValue(value: string) { if (!value) return ''; return new Date(value).toISOString().slice(0, 10); } function dateInputToISOString(value: string) { return new Date(`${value}T00:00:00+08:00`).toISOString(); } function ProductTracesPage() { const [traces, setTraces] = useState([]); const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const [limit, setLimit] = useState(10); const [total, setTotal] = useState(0); const [search, setSearch] = useState(''); const [modalOpen, setModalOpen] = useState(false); const [editingTrace, setEditingTrace] = useState(null); const [saving, setSaving] = useState(false); const [wechatFile, setWechatFile] = useState(null); const [fileList, setFileList] = useState([]); const [form] = Form.useForm(); const [qrModalOpen, setQrModalOpen] = useState(false); const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); const [qrUrl, setQrUrl] = useState(''); const [selectedSerialNumber, setSelectedSerialNumber] = useState(''); const loadTraces = async () => { setLoading(true); try { const result = await productTracesApi.list({ page, limit, search: search || undefined, }); setTraces(result.data || []); setTotal(result.pagination?.total || 0); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '加载产品溯源失败'); setTraces([]); setTotal(0); } finally { setLoading(false); } }; useEffect(() => { loadTraces(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [page, limit, search]); const resetFormState = () => { form.resetFields(); setEditingTrace(null); setWechatFile(null); setFileList([]); }; const openCreate = () => { resetFormState(); setModalOpen(true); }; const openEdit = (trace: ProductTrace) => { setEditingTrace(trace); setWechatFile(null); setFileList([]); form.setFieldsValue({ companyName: trace.companyName, companyAddress: trace.companyAddress, companyPhone: trace.companyPhone, deviceInfo: trace.deviceInfo, warrantyPeriod: trace.warrantyPeriod, manufactureDate: toDateInputValue(trace.manufactureDate), serialNumber: trace.serialNumber, officialWebsite: trace.officialWebsite, }); setModalOpen(true); }; const uploadWechatQrCodeIfNeeded = async (serialNumber: string) => { if (!wechatFile) return undefined; return productTracesApi.uploadWechatQrCode(serialNumber, wechatFile); }; const handleSave = async (values: ProductTraceFormValues) => { setSaving(true); try { let saved: ProductTrace; if (editingTrace) { const payload: UpdateProductTraceRequest = { companyName: values.companyName, companyAddress: values.companyAddress, companyPhone: values.companyPhone, deviceInfo: values.deviceInfo, warrantyPeriod: values.warrantyPeriod, manufactureDate: dateInputToISOString(values.manufactureDate), officialWebsite: values.officialWebsite || '', }; saved = await productTracesApi.update(editingTrace.serialNumber, payload); } else { const payload: CreateProductTraceRequest = { ...values, officialWebsite: values.officialWebsite || '', serialNumber: values.serialNumber.trim(), manufactureDate: dateInputToISOString(values.manufactureDate), }; saved = await productTracesApi.create(payload); } const uploaded = await uploadWechatQrCodeIfNeeded(saved.serialNumber); if (uploaded) { saved = uploaded; } message.success(editingTrace ? '保存成功' : `创建成功:${saved.serialNumber}`); setModalOpen(false); resetFormState(); loadTraces(); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '保存失败'); } finally { setSaving(false); } }; const handleGenerateQrCode = async (trace: ProductTrace) => { try { const result = await productTracesApi.generateQrCode(trace.serialNumber); setQrCodeDataUrl(result.qrCodeData); setQrUrl(result.queryUrl); setSelectedSerialNumber(trace.serialNumber); setQrModalOpen(true); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '生成二维码失败'); } }; const handleDownloadQrCode = () => { if (!qrCodeDataUrl || !selectedSerialNumber) return; const link = document.createElement('a'); link.download = `${selectedSerialNumber}.png`; link.href = qrCodeDataUrl; link.click(); }; const handleRevoke = (trace: ProductTrace) => { Modal.confirm({ title: '停用产品溯源', content: `确定要停用产品序列号 ${trace.serialNumber} 吗?`, okText: '停用', okType: 'danger', cancelText: '取消', onOk: async () => { try { await productTracesApi.revoke(trace.serialNumber); message.success('已停用'); loadTraces(); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '停用失败'); } }, }); }; const handleDelete = (trace: ProductTrace) => { Modal.confirm({ title: '删除产品溯源', content: `确定要删除产品序列号 ${trace.serialNumber} 吗?此操作不可恢复。`, okText: '删除', okType: 'danger', cancelText: '取消', onOk: async () => { try { await productTracesApi.delete(trace.serialNumber); message.success('删除成功'); loadTraces(); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '删除失败'); } }, }); }; const columns = [ { title: '产品序列号', dataIndex: 'serialNumber', key: 'serialNumber', width: 180, render: (value: string) => ( {value} ), }, { title: '企业名称', dataIndex: 'companyName', key: 'companyName', }, { title: '设备信息', dataIndex: 'deviceInfo', key: 'deviceInfo', ellipsis: true, }, { title: '质保期', dataIndex: 'warrantyPeriod', key: 'warrantyPeriod', width: 140, }, { title: '出厂日期', dataIndex: 'manufactureDate', key: 'manufactureDate', width: 140, render: (value: string) => formatDate(value), }, { title: '状态', dataIndex: 'isActive', key: 'isActive', width: 90, render: (value: boolean) => {value ? '有效' : '停用'}, }, { title: '操作', key: 'actions', width: 260, render: (_: unknown, record: ProductTrace) => ( {record.isActive && ( )} ), }, ]; return (
产品溯源 } extra={ } > { setPage(1); setSearch(value); }} onChange={(event) => { if (!event.target.value) { setPage(1); setSearch(''); } }} />
`共计 ${value} 条记录`} onChange={(newPage, newLimit) => { setPage(newPage); setLimit(newLimit); }} />
{ setModalOpen(false); resetFormState(); }} footer={null} width={680} >
} placeholder="https://example.com" /> { setWechatFile(file); setFileList([file]); return false; }} onRemove={() => { setWechatFile(null); setFileList([]); }} > {fileList.length === 0 ? (
上传
) : null}
{editingTrace?.wechatQrCode && !wechatFile && ( )}
setQrModalOpen(false)} footer={[ , , ]} >
{qrCodeDataUrl && ( <> 产品溯源二维码

{selectedSerialNumber}

{qrUrl}

)}
); } export default ProductTracesPage;