Add employee code assignment function

This commit is contained in:
2026-03-02 12:58:05 +08:00
parent d2dac6091e
commit 76ea5a2e06
9 changed files with 1068 additions and 160 deletions

View File

@@ -0,0 +1,416 @@
import { useEffect, useState } from 'react';
import { Card, Table, Input, Button, Space, message, Modal, Tag, Form, Select, InputNumber, Pagination } from 'antd';
import { UserOutlined, PlusOutlined, StopOutlined, EditOutlined, QrcodeOutlined, DeleteOutlined } from '@ant-design/icons';
import { employeeSerialApi } from '@/services/api';
import type { EmployeeSerial } from '@/types';
function EmployeeSerialsPage() {
const [serials, setSerials] = useState<EmployeeSerial[]>([]);
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<EmployeeSerial | null>(null);
const [qrCodeModalVisible, setQrCodeModalVisible] = useState(false);
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
const [generateForm] = Form.useForm();
const [editForm] = Form.useForm();
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);
message.success(result.message || '生成成功');
setGenerateModalVisible(false);
generateForm.resetFields();
loadSerials();
} catch (error: any) {
message.error(error.message || '生成失败');
} finally {
setGenerateLoading(false);
}
};
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) => (
<Tag color={isActive ? 'green' : 'red'}>
{isActive ? '有效' : '已吊销'}
</Tag>
),
},
{
title: '创建时间',
dataIndex: 'createdAt',
key: 'createdAt',
render: (date: string) => new Date(date).toLocaleString('zh-CN'),
},
{
title: '操作',
key: 'actions',
render: (_: any, record: EmployeeSerial) => (
<Space>
<Button
type="link"
size="small"
icon={<QrcodeOutlined />}
onClick={() => handleViewQrCode(record)}
>
</Button>
<Button
type="link"
size="small"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
</Button>
{record.isActive && (
<Button
type="link"
size="small"
danger
icon={<StopOutlined />}
onClick={() => handleRevoke(record)}
>
</Button>
)}
<Button
type="link"
size="small"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(record)}
>
</Button>
</Space>
),
},
];
return (
<div>
<Card
title={
<Space>
<UserOutlined />
<span></span>
</Space>
}
extra={
<Space>
<Input.Search
placeholder="搜索序列号/企业/部门/员工"
allowClear
style={{ width: 250 }}
onSearch={handleSearch}
onChange={(e) => {
if (!e.target.value) {
handleSearch('');
}
}}
/>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={() => setGenerateModalVisible(true)}
>
</Button>
</Space>
}
>
<Table
columns={columns}
dataSource={serials}
rowKey="serialNumber"
loading={loading}
pagination={false}
/>
<div style={{ marginTop: 16 }}>
<Pagination
current={page}
pageSize={limit}
total={total}
onChange={handlePageChange}
showSizeChanger={true}
showTotal={(t) => `共计 ${t} 条记录`}
/>
</div>
</Card>
<Modal
title="生成员工序列号"
open={generateModalVisible}
onCancel={() => {
setGenerateModalVisible(false);
generateForm.resetFields();
}}
footer={null}
width={500}
>
<Form
form={generateForm}
layout="vertical"
onFinish={handleGenerate}
>
<Form.Item
name="companyName"
label="企业名称"
rules={[{ required: true, message: '请输入企业名称' }]}
>
<Input placeholder="请输入企业名称" />
</Form.Item>
<Form.Item
name="department"
label="部门"
rules={[{ required: true, message: '请输入部门' }]}
>
<Input placeholder="请输入部门" />
</Form.Item>
<Form.Item
name="employeeName"
label="员工姓名"
rules={[{ required: true, message: '请输入员工姓名' }]}
>
<Input placeholder="请输入员工姓名" />
</Form.Item>
<Form.Item
name="quantity"
label="生成数量"
rules={[{ required: true, message: '请输入生成数量' }]}
initialValue={1}
>
<InputNumber min={1} max={1000} style={{ width: '100%' }} />
</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={editModalVisible}
onCancel={() => {
setEditModalVisible(false);
editForm.resetFields();
}}
footer={null}
width={500}
>
<Form
form={editForm}
layout="vertical"
onFinish={handleUpdate}
>
<Form.Item
name="companyName"
label="企业名称"
>
<Input placeholder="请输入企业名称" />
</Form.Item>
<Form.Item
name="department"
label="部门"
>
<Input placeholder="请输入部门" />
</Form.Item>
<Form.Item
name="employeeName"
label="员工姓名"
>
<Input placeholder="请输入员工姓名" />
</Form.Item>
<Form.Item
name="isActive"
label="状态"
>
<Select placeholder="请选择状态">
<Select.Option value={true}></Select.Option>
<Select.Option value={false}></Select.Option>
</Select>
</Form.Item>
<Form.Item>
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
<Button onClick={() => setEditModalVisible(false)}></Button>
<Button type="primary" htmlType="submit" loading={editLoading}>
</Button>
</Space>
</Form.Item>
</Form>
</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' }} />
<p style={{ marginTop: '12px', fontFamily: 'monospace', fontSize: '16px', fontWeight: 'bold', color: '#165DFF' }}>
{selectedSerial?.serialNumber}
</p>
<p style={{ marginTop: '8px', fontSize: '12px', color: '#999' }}>
{selectedSerial?.companyName} - {selectedSerial?.department} - {selectedSerial?.employeeName}
</p>
</>
)}
</div>
</Modal>
</div>
);
}
export default EmployeeSerialsPage;