import { useEffect, useState } from 'react'; import { Card, Table, Input, Button, Space, message, Modal, Tag, Spin, Form, Radio, InputNumber, DatePicker, ColorPicker, Pagination } from 'antd'; import { TeamOutlined, DeleteOutlined, EyeOutlined, StopOutlined, PlusOutlined } from '@ant-design/icons'; import QRCode from 'qrcode'; import { useNavigate } from 'react-router-dom'; import type { Color } from 'antd/es/color-picker'; interface CompanyData { companyName: string; serialCount: number; firstCreated: string; lastCreated: string; activeCount: number; status: 'active' | 'disabled'; } function ManagePage() { const [companies, setCompanies] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [selectedCompany, setSelectedCompany] = useState(null); const [detailModalVisible, setDetailModalVisible] = useState(false); const [detailLoading, setDetailLoading] = useState(false); const [companyDetail, setCompanyDetail] = useState(null); const [qrCodeModalVisible, setQrCodeModalVisible] = useState(false); const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); const [selectedSerial, setSelectedSerial] = useState(''); const [generateModalVisible, setGenerateModalVisible] = useState(false); const [generateLoading, setGenerateLoading] = useState(false); const [generateForm] = Form.useForm(); const [qrColor, setQrColor] = useState('#000000'); const [generatedData, setGeneratedData] = useState(null); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(10); const [total, setTotal] = useState(0); const navigate = useNavigate(); useEffect(() => { loadCompanies(); }, [searchTerm, page, pageSize]); const loadCompanies = async () => { setLoading(true); try { const token = localStorage.getItem('authToken'); const headers: any = { 'Content-Type': 'application/json', }; if (token) { headers.Authorization = `Bearer ${token}`; } let url = '/api/companies'; const params = new URLSearchParams(); if (page > 1) params.append('page', String(page)); if (pageSize !== 10) params.append('limit', String(pageSize)); if (searchTerm) params.append('search', searchTerm); if (params.toString()) url += `?${params.toString()}`; const response = await fetch(url, { headers }); if (!response.ok) { throw new Error(`请求失败: ${response.status}`); } const data = await response.json(); if (data.data) { setCompanies(data.data); setTotal(data.pagination?.total || data.data.length); } else if (data.message) { setCompanies([]); setTotal(0); } else { throw new Error(data.error || '获取企业列表失败'); } } catch (error: any) { console.error('Load companies error:', error); message.error(error.message || '加载企业列表失败'); setCompanies([]); setTotal(0); } finally { setLoading(false); } }; const handleViewDetail = async (company: CompanyData) => { setSelectedCompany(company); setDetailModalVisible(true); setDetailLoading(true); try { const token = localStorage.getItem('authToken'); const headers: any = { 'Content-Type': 'application/json', }; if (token) { headers.Authorization = `Bearer ${token}`; } const response = await fetch(`/api/companies/${encodeURIComponent(company.companyName)}`, { headers }); const data = await response.json(); if (data.data) { setCompanyDetail(data.data); } else { throw new Error(data.error || '获取企业详情失败'); } } catch (error: any) { message.error(error.message || '获取企业详情失败'); } finally { setDetailLoading(false); } }; const handleDelete = async (company: CompanyData) => { Modal.confirm({ title: '确认删除', content: `确定要删除企业 "${company.companyName}" 吗?这将删除该企业的所有序列号!`, okText: '确定', okType: 'danger', cancelText: '取消', onOk: async () => { try { const token = localStorage.getItem('authToken'); const headers: any = { 'Content-Type': 'application/json', }; if (token) { headers.Authorization = `Bearer ${token}`; } const response = await fetch(`/api/companies/${encodeURIComponent(company.companyName)}`, { method: 'DELETE', headers, }); const data = await response.json(); if (data.message) { message.success('删除成功'); loadCompanies(); } else { throw new Error(data.error || '删除失败'); } } catch (error: any) { message.error(error.message || '删除失败'); } }, }); }; const handleRevoke = async (company: CompanyData) => { Modal.confirm({ title: '确认吊销', content: `确定要吊销企业 "${company.companyName}" 吗?这将使该企业的所有序列号失效!`, okText: '确定', okType: 'danger', cancelText: '取消', onOk: async () => { try { const token = localStorage.getItem('authToken'); const headers: any = { 'Content-Type': 'application/json', }; if (token) { headers.Authorization = `Bearer ${token}`; } const response = await fetch(`/api/companies/${encodeURIComponent(company.companyName)}/revoke`, { method: 'POST', headers, }); const data = await response.json(); if (data.message) { message.success('吊销成功'); loadCompanies(); } else { throw new Error(data.error || '吊销失败'); } } catch (error: any) { message.error(error.message || '吊销失败'); } }, }); }; const handleViewQRCode = async (serialNumber: string) => { try { const baseUrl = window.location.origin; const queryUrl = `${baseUrl}/query?serial=${serialNumber}`; const qrCode = await QRCode.toDataURL(queryUrl); setQrCodeDataUrl(qrCode); setSelectedSerial(serialNumber); setQrCodeModalVisible(true); } catch (error) { message.error('生成二维码失败'); } }; const handleQuerySerial = (serialNumber: string) => { setQrCodeModalVisible(false); navigate(`/query?serial=${serialNumber}`); }; const handleRevokeSerial = async (serialNumber: string) => { Modal.confirm({ title: '确认吊销', content: `确定要吊销序列号 "${serialNumber}" 吗?`, okText: '确定', okType: 'danger', cancelText: '取消', onOk: async () => { try { const token = localStorage.getItem('authToken'); const headers: any = { 'Content-Type': 'application/json', }; if (token) { headers.Authorization = `Bearer ${token}`; } const response = await fetch(`/api/serials/${encodeURIComponent(serialNumber)}/revoke`, { method: 'POST', headers, }); const data = await response.json(); if (data.message) { message.success('吊销成功'); if (selectedCompany) { handleViewDetail(selectedCompany); } } else { throw new Error(data.error || '吊销失败'); } } catch (error: any) { message.error(error.message || '吊销失败'); } }, }); }; const handleGenerate = async (values: any) => { setGenerateLoading(true); try { const token = localStorage.getItem('authToken'); const headers: any = { 'Content-Type': 'application/json', }; if (token) { headers.Authorization = `Bearer ${token}`; } const payload = { companyName: values.companyName, quantity: values.quantity, validDays: values.validOption === 'days' ? values.validDays : undefined, serialPrefix: values.serialOption === 'custom' ? values.serialPrefix : undefined, }; const response = await fetch('/api/serials/generate', { method: 'POST', headers, body: JSON.stringify(payload), }); const data = await response.json(); if (data.serials) { setGeneratedData(data); if (data.serials && data.serials.length > 0) { const baseUrl = window.location.origin; const queryUrl = `${baseUrl}/query?serial=${data.serials[0].serialNumber}`; const qrCode = await QRCode.toDataURL(queryUrl, { color: { dark: qrColor, light: '#ffffff', }, }); setQrCodeDataUrl(qrCode); } message.success('生成成功!'); setGenerateSuccessModalVisible(true); loadCompanies(); } else { throw new Error(data.error || '生成失败'); } } 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 [generateSuccessModalVisible, setGenerateSuccessModalVisible] = useState(false); const colorPresets = [ '#000000', '#165DFF', '#52C41A', '#FAAD14', '#FF4D4F', '#722ED1', '#EB2F96', ]; const handlePageChange = (newPage: number, newPageSize: number) => { setPage(newPage); setPageSize(newPageSize); }; const columns = [ { title: '企业名称', dataIndex: 'companyName', key: 'companyName', }, { title: '序列号数量', dataIndex: 'serialCount', key: 'serialCount', }, { title: '状态', dataIndex: 'status', key: 'status', render: (status: string) => ( {status === 'active' ? '正常' : '已吊销'} ), }, { title: '创建时间', dataIndex: 'firstCreated', key: 'firstCreated', render: (date: string) => new Date(date).toLocaleString('zh-CN'), }, { title: '最后更新', dataIndex: 'lastCreated', key: 'lastCreated', render: (date: string) => new Date(date).toLocaleString('zh-CN'), }, { title: '操作', key: 'actions', render: (_: any, record: CompanyData) => ( ), }, ]; return (
企业管理 } extra={ setSearchTerm(e.target.value)} value={searchTerm} /> } >
`共计 ${total} 条记录`} />
setDetailModalVisible(false)} footer={null} width={800} > {detailLoading ? (
) : companyDetail ? (

企业名称: {companyDetail.companyName}

序列号总数: {companyDetail.serialCount}

活跃序列号: {companyDetail.activeCount}

已吊销序列号: {companyDetail.disabledCount || 0}

已过期序列号: {companyDetail.expiredCount || 0}

{companyDetail.serials && companyDetail.serials.length > 0 && (

序列号列表

( {isActive ? '有效' : '已吊销'} ), }, { title: '有效期至', dataIndex: 'validUntil', key: 'validUntil', render: (date: string) => new Date(date).toLocaleString('zh-CN'), }, { title: '操作', key: 'actions', render: (_: any, record: any) => ( {record.isActive && ( )} ), }, ]} dataSource={companyDetail.serials} rowKey="serialNumber" pagination={false} size="small" /> )} ) : null} setQrCodeModalVisible(false)} footer={null} width={400} >
{qrCodeDataUrl && ( <> QR Code handleQuerySerial(selectedSerial)} />

{selectedSerial}

点击二维码可查询序列号

)}
{ setGenerateModalVisible(false); generateForm.resetFields(); }} footer={null} width={600} >
自动生成 自定义前缀 prevValues.serialOption !== currentValues.serialOption} > {({ getFieldValue }) => getFieldValue('serialOption') === 'custom' ? ( ) : null } 按天数 按日期 prevValues.validOption !== currentValues.validOption} > {({ getFieldValue }) => getFieldValue('validOption') === 'days' ? ( ) : ( ) }
{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); }} />
setGenerateSuccessModalVisible(false)} footer={null} width={600} > {generatedData && (

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

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

{generatedData.serials && generatedData.serials.length > 0 && (

有效期至: {new Date(generatedData.serials[0].validUntil).toLocaleString('zh-CN')}

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

{generatedData.serials[0].serialNumber}

)}
)}
)}
); } export default ManagePage;