718 lines
23 KiB
TypeScript
718 lines
23 KiB
TypeScript
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<CompanyData[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [searchTerm, setSearchTerm] = useState('');
|
||
const [selectedCompany, setSelectedCompany] = useState<any>(null);
|
||
const [detailModalVisible, setDetailModalVisible] = useState(false);
|
||
const [detailLoading, setDetailLoading] = useState(false);
|
||
const [companyDetail, setCompanyDetail] = useState<any>(null);
|
||
const [qrCodeModalVisible, setQrCodeModalVisible] = useState(false);
|
||
const [qrCodeDataUrl, setQrCodeDataUrl] = useState<string>('');
|
||
const [selectedSerial, setSelectedSerial] = useState<string>('');
|
||
const [generateModalVisible, setGenerateModalVisible] = useState(false);
|
||
const [generateLoading, setGenerateLoading] = useState(false);
|
||
const [generateForm] = Form.useForm();
|
||
const [qrColor, setQrColor] = useState<string>('#000000');
|
||
const [generatedData, setGeneratedData] = useState<any>(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) => (
|
||
<Tag color={status === 'active' ? 'green' : 'red'}>
|
||
{status === 'active' ? '正常' : '已吊销'}
|
||
</Tag>
|
||
),
|
||
},
|
||
{
|
||
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) => (
|
||
<Space>
|
||
<Button
|
||
type="link"
|
||
icon={<EyeOutlined />}
|
||
onClick={() => handleViewDetail(record)}
|
||
>
|
||
查看
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
danger
|
||
icon={<StopOutlined />}
|
||
onClick={() => handleRevoke(record)}
|
||
>
|
||
吊销
|
||
</Button>
|
||
<Button
|
||
type="link"
|
||
danger
|
||
icon={<DeleteOutlined />}
|
||
onClick={() => handleDelete(record)}
|
||
>
|
||
删除
|
||
</Button>
|
||
</Space>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
<Card
|
||
title={
|
||
<Space>
|
||
<TeamOutlined />
|
||
<span>企业管理</span>
|
||
</Space>
|
||
}
|
||
extra={
|
||
<Space>
|
||
<Input.Search
|
||
placeholder="搜索企业"
|
||
allowClear
|
||
style={{ width: 200 }}
|
||
onSearch={setSearchTerm}
|
||
onChange={(e) => setSearchTerm(e.target.value)}
|
||
value={searchTerm}
|
||
/>
|
||
<Button type="primary" icon={<PlusOutlined />} onClick={() => setGenerateModalVisible(true)}>
|
||
生成序列号
|
||
</Button>
|
||
</Space>
|
||
}
|
||
>
|
||
<Table
|
||
columns={columns}
|
||
dataSource={companies}
|
||
rowKey="companyName"
|
||
loading={loading}
|
||
pagination={false}
|
||
/>
|
||
<div style={{ marginTop: 16 }}>
|
||
<Pagination
|
||
current={page}
|
||
pageSize={pageSize}
|
||
total={total}
|
||
onChange={handlePageChange}
|
||
showSizeChanger={true}
|
||
showTotal={() => `共计 ${total} 条记录`}
|
||
/>
|
||
</div>
|
||
</Card>
|
||
|
||
<Modal
|
||
title="企业详情"
|
||
open={detailModalVisible}
|
||
onCancel={() => setDetailModalVisible(false)}
|
||
footer={null}
|
||
width={800}
|
||
>
|
||
{detailLoading ? (
|
||
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||
<Spin size="large" />
|
||
</div>
|
||
) : companyDetail ? (
|
||
<div>
|
||
<p><strong>企业名称:</strong> {companyDetail.companyName}</p>
|
||
<p><strong>序列号总数:</strong> {companyDetail.serialCount}</p>
|
||
<p><strong>活跃序列号:</strong> {companyDetail.activeCount}</p>
|
||
<p><strong>已吊销序列号:</strong> {companyDetail.disabledCount || 0}</p>
|
||
<p><strong>已过期序列号:</strong> {companyDetail.expiredCount || 0}</p>
|
||
|
||
{companyDetail.serials && companyDetail.serials.length > 0 && (
|
||
<div style={{ marginTop: '16px' }}>
|
||
<h4>序列号列表</h4>
|
||
<Table
|
||
columns={[
|
||
{ title: '序列号', dataIndex: 'serialNumber', key: 'serialNumber' },
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'isActive',
|
||
key: 'isActive',
|
||
render: (isActive: boolean) => (
|
||
<Tag color={isActive ? 'green' : 'red'}>
|
||
{isActive ? '有效' : '已吊销'}
|
||
</Tag>
|
||
),
|
||
},
|
||
{
|
||
title: '有效期至',
|
||
dataIndex: 'validUntil',
|
||
key: 'validUntil',
|
||
render: (date: string) => new Date(date).toLocaleString('zh-CN'),
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'actions',
|
||
render: (_: any, record: any) => (
|
||
<Space>
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
icon={<EyeOutlined />}
|
||
onClick={() => handleViewQRCode(record.serialNumber)}
|
||
>
|
||
查看二维码
|
||
</Button>
|
||
{record.isActive && (
|
||
<Button
|
||
type="link"
|
||
size="small"
|
||
danger
|
||
icon={<StopOutlined />}
|
||
onClick={() => handleRevokeSerial(record.serialNumber)}
|
||
>
|
||
吊销
|
||
</Button>
|
||
)}
|
||
</Space>
|
||
),
|
||
},
|
||
]}
|
||
dataSource={companyDetail.serials}
|
||
rowKey="serialNumber"
|
||
pagination={false}
|
||
size="small"
|
||
/>
|
||
</div>
|
||
)}
|
||
</div>
|
||
) : null}
|
||
</Modal>
|
||
|
||
<Modal
|
||
title="序列号二维码"
|
||
open={qrCodeModalVisible}
|
||
onCancel={() => setQrCodeModalVisible(false)}
|
||
footer={null}
|
||
width={400}
|
||
>
|
||
<div style={{ textAlign: 'center' }}>
|
||
{qrCodeDataUrl && (
|
||
<>
|
||
<img src={qrCodeDataUrl} alt="QR Code" style={{ width: '200px', height: '200px', cursor: 'pointer' }} onClick={() => handleQuerySerial(selectedSerial)} />
|
||
<p style={{ marginTop: '12px', fontFamily: 'monospace', fontSize: '16px', fontWeight: 'bold', color: '#165DFF' }}>{selectedSerial}</p>
|
||
<p style={{ marginTop: '8px', fontSize: '12px', color: '#999' }}>点击二维码可查询序列号</p>
|
||
</>
|
||
)}
|
||
</div>
|
||
</Modal>
|
||
|
||
<Modal
|
||
title="生成企业序列号"
|
||
open={generateModalVisible}
|
||
onCancel={() => {
|
||
setGenerateModalVisible(false);
|
||
generateForm.resetFields();
|
||
}}
|
||
footer={null}
|
||
width={600}
|
||
>
|
||
<Form
|
||
form={generateForm}
|
||
layout="vertical"
|
||
onFinish={handleGenerate}
|
||
initialValues={{
|
||
serialOption: 'auto',
|
||
quantity: 1,
|
||
validOption: 'days',
|
||
validDays: 365,
|
||
}}
|
||
>
|
||
<Form.Item
|
||
label="企业名称"
|
||
name="companyName"
|
||
rules={[{ required: true, message: '请输入企业名称' }]}
|
||
>
|
||
<Input placeholder="输入企业名称(如:浙江贝凡)" />
|
||
</Form.Item>
|
||
|
||
<Form.Item label="序列号设置" name="serialOption">
|
||
<Radio.Group>
|
||
<Radio value="auto">自动生成</Radio>
|
||
<Radio value="custom">自定义前缀</Radio>
|
||
</Radio.Group>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
noStyle
|
||
shouldUpdate={(prevValues, currentValues) => prevValues.serialOption !== currentValues.serialOption}
|
||
>
|
||
{({ getFieldValue }) =>
|
||
getFieldValue('serialOption') === 'custom' ? (
|
||
<Form.Item
|
||
label="自定义前缀"
|
||
name="serialPrefix"
|
||
rules={[{ max: 10, message: '前缀不能超过10个字符' }]}
|
||
>
|
||
<Input placeholder="输入自定义前缀(如:MYCOMPANY)" maxLength={10} />
|
||
</Form.Item>
|
||
) : null
|
||
}
|
||
</Form.Item>
|
||
|
||
<Form.Item label="有效期设置" name="validOption">
|
||
<Radio.Group>
|
||
<Radio value="days">按天数</Radio>
|
||
<Radio value="date">按日期</Radio>
|
||
</Radio.Group>
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
noStyle
|
||
shouldUpdate={(prevValues, currentValues) => prevValues.validOption !== currentValues.validOption}
|
||
>
|
||
{({ getFieldValue }) =>
|
||
getFieldValue('validOption') === 'days' ? (
|
||
<Form.Item
|
||
label="有效天数"
|
||
name="validDays"
|
||
rules={[{ required: true, message: '请输入有效天数' }]}
|
||
>
|
||
<InputNumber min={1} max={3650} style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
) : (
|
||
<Form.Item
|
||
label="有效期至"
|
||
name="validUntil"
|
||
rules={[{ required: true, message: '请选择有效期' }]}
|
||
>
|
||
<DatePicker showTime style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
)
|
||
}
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="生成数量"
|
||
name="quantity"
|
||
rules={[{ required: true, message: '请输入生成数量' }]}
|
||
>
|
||
<InputNumber min={1} max={100} style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
|
||
<Form.Item
|
||
label="二维码颜色"
|
||
name="qrColor"
|
||
initialValue="#000000"
|
||
>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
||
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
|
||
{colorPresets.map((color) => (
|
||
<div
|
||
key={color}
|
||
onClick={() => 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',
|
||
}}
|
||
/>
|
||
))}
|
||
</div>
|
||
<ColorPicker
|
||
value={qrColor}
|
||
onChange={(color: Color) => {
|
||
const hexColor = color.toHexString();
|
||
setQrColor(hexColor);
|
||
}}
|
||
/>
|
||
</div>
|
||
</Form.Item>
|
||
|
||
<Form.Item>
|
||
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||
<Button onClick={() => setGenerateModalVisible(false)}>取消</Button>
|
||
<Button type="primary" htmlType="submit" loading={generateLoading}>
|
||
生成
|
||
</Button>
|
||
</Space>
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
|
||
<Modal
|
||
title="生成成功"
|
||
open={generateSuccessModalVisible}
|
||
onCancel={() => setGenerateSuccessModalVisible(false)}
|
||
footer={null}
|
||
width={600}
|
||
>
|
||
{generatedData && (
|
||
<div>
|
||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
||
<div>
|
||
<p><strong>企业名称:</strong> {generatedData.companyName || generatedData.serials?.[0]?.companyName}</p>
|
||
<p><strong>生成数量:</strong> {generatedData.serials?.length || 0}</p>
|
||
{generatedData.serials && generatedData.serials.length > 0 && (
|
||
<p><strong>有效期至:</strong> {new Date(generatedData.serials[0].validUntil).toLocaleString('zh-CN')}</p>
|
||
)}
|
||
</div>
|
||
|
||
{qrCodeDataUrl && (
|
||
<div style={{ textAlign: 'center' }}>
|
||
<img src={qrCodeDataUrl} alt="QR Code" style={{ width: '200px', height: '200px' }} />
|
||
{generatedData.serials && generatedData.serials.length > 0 && (
|
||
<p style={{ marginTop: '12px', fontFamily: 'monospace', fontSize: '16px', fontWeight: 'bold', color: '#165DFF' }}>{generatedData.serials[0].serialNumber}</p>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
<Space>
|
||
<Button type="primary" onClick={handleViewQuery}>查询序列号</Button>
|
||
<Button onClick={handleDownloadQR}>下载二维码</Button>
|
||
<Button onClick={() => {
|
||
setGenerateSuccessModalVisible(false);
|
||
generateForm.resetFields();
|
||
}}>生成新的序列号</Button>
|
||
</Space>
|
||
</Space>
|
||
</div>
|
||
)}
|
||
</Modal>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default ManagePage; |