Add employee code assignment function
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Card, Table, Input, Select, Button, Space, message, Modal, Tag, Spin } from 'antd';
|
||||
import { TeamOutlined, DeleteOutlined, EyeOutlined, StopOutlined } from '@ant-design/icons';
|
||||
import type { Company } from '@/types';
|
||||
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;
|
||||
@@ -25,16 +25,23 @@ function ManagePage() {
|
||||
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]);
|
||||
}, [searchTerm, page, pageSize]);
|
||||
|
||||
const loadCompanies = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 直接使用 apiClient 来调用后端接口
|
||||
const token = localStorage.getItem('authToken');
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -43,9 +50,9 @@ function ManagePage() {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
let url = '/api/companies';
|
||||
let url = `/api/companies?page=${page}&limit=${pageSize}`;
|
||||
if (searchTerm) {
|
||||
url += `?search=${encodeURIComponent(searchTerm)}`;
|
||||
url += `&search=${encodeURIComponent(searchTerm)}`;
|
||||
}
|
||||
|
||||
const response = await fetch(url, { headers });
|
||||
@@ -53,8 +60,10 @@ function ManagePage() {
|
||||
|
||||
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 || '获取企业列表失败');
|
||||
}
|
||||
@@ -62,6 +71,7 @@ function ManagePage() {
|
||||
console.error('Load companies error:', error);
|
||||
message.error(error.message || '加载企业列表失败');
|
||||
setCompanies([]);
|
||||
setTotal(0);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -224,6 +234,71 @@ function ManagePage() {
|
||||
});
|
||||
};
|
||||
|
||||
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('生成成功!');
|
||||
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 handlePageChange = (newPage: number, newPageSize: number) => {
|
||||
setPage(newPage);
|
||||
setPageSize(newPageSize);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '企业名称',
|
||||
@@ -300,14 +375,19 @@ function ManagePage() {
|
||||
</Space>
|
||||
}
|
||||
extra={
|
||||
<Input.Search
|
||||
placeholder="搜索企业"
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
onSearch={setSearchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
value={searchTerm}
|
||||
/>
|
||||
<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
|
||||
@@ -315,12 +395,18 @@ function ManagePage() {
|
||||
dataSource={companies}
|
||||
rowKey="companyName"
|
||||
loading={loading}
|
||||
pagination={{
|
||||
pageSize: 10,
|
||||
showSizeChanger: true,
|
||||
showTotal: (total) => `共 ${total} 家企业`,
|
||||
}}
|
||||
pagination={false}
|
||||
/>
|
||||
<div style={{ marginTop: 16 }}>
|
||||
<Pagination
|
||||
current={page}
|
||||
pageSize={pageSize}
|
||||
total={total}
|
||||
onChange={handlePageChange}
|
||||
showSizeChanger={true}
|
||||
showTotal={() => `共计 ${total} 条记录`}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Modal
|
||||
@@ -420,6 +506,124 @@ function ManagePage() {
|
||||
)}
|
||||
</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="quantity"
|
||||
rules={[{ required: true, message: '请输入序列号数量' }]}
|
||||
>
|
||||
<InputNumber min={1} max={100} style={{ width: '100%' }} />
|
||||
</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="qrColor"
|
||||
initialValue="#000000"
|
||||
>
|
||||
<ColorPicker
|
||||
value={qrColor}
|
||||
onChange={(color: Color) => {
|
||||
const hexColor = color.toHexString();
|
||||
setQrColor(hexColor);
|
||||
}}
|
||||
/>
|
||||
</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>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user