import { useEffect, useState } from 'react'; import { Card, Table, Input, Button, Space, message, Modal, Tag, Form, Select, InputNumber, Pagination, ColorPicker } from 'antd'; import { UserOutlined, PlusOutlined, StopOutlined, EditOutlined, QrcodeOutlined, DeleteOutlined } from '@ant-design/icons'; import { employeeSerialApi } from '@/services/api'; import QRCode from 'qrcode'; import { useNavigate } from 'react-router-dom'; import type { Color } from 'antd/es/color-picker'; import type { EmployeeSerial } from '@/types'; function EmployeeSerialsPage() { const [serials, setSerials] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [page, setPage] = useState(1); const [limit, setLimit] = useState(10); const [total, setTotal] = useState(0); const [generateModalVisible, setGenerateModalVisible] = useState(false); const [generateLoading, setGenerateLoading] = useState(false); const [editModalVisible, setEditModalVisible] = useState(false); const [editLoading, setEditLoading] = useState(false); const [selectedSerial, setSelectedSerial] = useState(null); const [qrCodeModalVisible, setQrCodeModalVisible] = useState(false); const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); const [generateForm] = Form.useForm(); const [editForm] = Form.useForm(); const [qrColor, setQrColor] = useState('#000000'); const [generatedData, setGeneratedData] = useState(null); const [generateSuccessModalVisible, setGenerateSuccessModalVisible] = useState(false); const navigate = useNavigate(); const colorPresets = [ '#000000', '#165DFF', '#52C41A', '#FAAD14', '#FF4D4F', '#722ED1', '#EB2F96', ]; useEffect(() => { loadSerials(); }, [page, limit, searchTerm]); const handlePageChange = (newPage: number, newLimit: number) => { setPage(newPage); setLimit(newLimit); }; const loadSerials = async () => { setLoading(true); try { const result = await employeeSerialApi.list({ page, limit, search: searchTerm || undefined }); setSerials(result.data); setTotal(result.pagination.total); } catch (error: any) { message.error(error.message || '加载员工序列号列表失败'); setSerials([]); } finally { setLoading(false); } }; const handleGenerate = async (values: { companyName: string; department: string; employeeName: string; quantity: number }) => { setGenerateLoading(true); try { const result = await employeeSerialApi.generate(values); if (result.serials && result.serials.length > 0) { const baseUrl = window.location.origin; const queryUrl = `${baseUrl}/query?serial=${result.serials[0].serialNumber}`; const qrCode = await QRCode.toDataURL(queryUrl, { color: { dark: qrColor, light: '#ffffff', }, }); setQrCodeDataUrl(qrCode); setGeneratedData(result); setGenerateSuccessModalVisible(true); } message.success(result.message || '生成成功'); setGenerateModalVisible(false); generateForm.resetFields(); loadSerials(); } catch (error: any) { message.error(error.message || '生成失败'); } finally { setGenerateLoading(false); } }; const handleDownloadQR = () => { const link = document.createElement('a'); link.download = `qrcode-${generatedData?.serials?.[0]?.serialNumber}.png`; link.href = qrCodeDataUrl; link.click(); }; const handleViewQuery = () => { if (generatedData?.serials?.[0]?.serialNumber) { navigate(`/query?serial=${generatedData.serials[0].serialNumber}`); setGenerateSuccessModalVisible(false); generateForm.resetFields(); } }; const handleEdit = (serial: EmployeeSerial) => { setSelectedSerial(serial); editForm.setFieldsValue({ companyName: serial.companyName, department: serial.department, employeeName: serial.employeeName, isActive: serial.isActive, }); setEditModalVisible(true); }; const handleUpdate = async (values: { companyName?: string; department?: string; employeeName?: string; isActive?: boolean }) => { if (!selectedSerial) return; setEditLoading(true); try { await employeeSerialApi.update(selectedSerial.serialNumber, values); message.success('更新成功'); setEditModalVisible(false); loadSerials(); } catch (error: any) { message.error(error.message || '更新失败'); } finally { setEditLoading(false); } }; const handleRevoke = async (serial: EmployeeSerial) => { Modal.confirm({ title: '确认吊销', content: `确定要吊销序列号 "${serial.serialNumber}" 吗?`, okText: '确定', okType: 'danger', cancelText: '取消', onOk: async () => { try { await employeeSerialApi.revoke(serial.serialNumber); message.success('吊销成功'); loadSerials(); } catch (error: any) { message.error(error.message || '吊销失败'); } }, }); }; const handleDelete = async (serial: EmployeeSerial) => { Modal.confirm({ title: '确认删除', content: `确定要删除序列号 "${serial.serialNumber}" 吗?此操作不可恢复!`, okText: '确定', okType: 'danger', cancelText: '取消', onOk: async () => { try { await employeeSerialApi.delete(serial.serialNumber); message.success('删除成功'); loadSerials(); } catch (error: any) { message.error(error.message || '删除失败'); } }, }); }; const handleViewQrCode = async (serial: EmployeeSerial) => { setSelectedSerial(serial); try { const baseUrl = window.location.origin; const result = await employeeSerialApi.generateQrCode(serial.serialNumber, `${baseUrl}/query`); if (result.qrCodeData) { const qrDataUrl = result.qrCodeData.startsWith('data:') ? result.qrCodeData : `data:image/png;base64,${result.qrCodeData}`; setQrCodeDataUrl(qrDataUrl); setQrCodeModalVisible(true); } } catch (error: any) { message.error(error.message || '生成二维码失败'); } }; const handleSearch = (value: string) => { setSearchTerm(value); setPage(1); }; const columns = [ { title: '序列号', dataIndex: 'serialNumber', key: 'serialNumber', width: 180, }, { title: '企业名称', dataIndex: 'companyName', key: 'companyName', }, { title: '部门', dataIndex: 'department', key: 'department', }, { title: '员工姓名', dataIndex: 'employeeName', key: 'employeeName', }, { title: '状态', dataIndex: 'isActive', key: 'isActive', render: (isActive: boolean) => ( {isActive ? '有效' : '已吊销'} ), }, { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', render: (date: string) => new Date(date).toLocaleString('zh-CN'), }, { title: '操作', key: 'actions', render: (_: any, record: EmployeeSerial) => ( {record.isActive && ( )} ), }, ]; return (
员工管理 } extra={ { if (!e.target.value) { handleSearch(''); } }} /> } >
`共计 ${t} 条记录`} />
{ setGenerateModalVisible(false); generateForm.resetFields(); }} footer={null} width={500} >
{colorPresets.map((color) => (
setQrColor(color)} style={{ width: '28px', height: '28px', backgroundColor: color, border: qrColor === color ? '2px solid #165DFF' : '2px solid #d9d9d9', borderRadius: '4px', cursor: 'pointer', transition: 'all 0.2s', }} /> ))}
{ const hexColor = color.toHexString(); setQrColor(hexColor); }} />
{ setEditModalVisible(false); editForm.resetFields(); }} footer={null} width={500} >
setGenerateSuccessModalVisible(false)} footer={null} width={600} > {generatedData && (

企业名称: {generatedData.serials?.[0]?.companyName}

部门: {generatedData.serials?.[0]?.department}

员工姓名: {generatedData.serials?.[0]?.employeeName}

生成数量: {generatedData.serials?.length || 0}

{qrCodeDataUrl && (
QR Code {generatedData.serials && generatedData.serials.length > 0 && (

{generatedData.serials[0].serialNumber}

)}
)}
)}
setQrCodeModalVisible(false)} footer={null} width={400} >
{qrCodeDataUrl && ( <> QR Code

{selectedSerial?.serialNumber}

{selectedSerial?.companyName} - {selectedSerial?.department} - {selectedSerial?.employeeName}

)}
); } export default EmployeeSerialsPage;