Remove Generate.tsx
This commit is contained in:
@@ -4,7 +4,6 @@ import LoginPage from './pages/Login';
|
|||||||
import AdminLayout from './components/AdminLayout';
|
import AdminLayout from './components/AdminLayout';
|
||||||
import PublicQueryPage from './pages/PublicQuery';
|
import PublicQueryPage from './pages/PublicQuery';
|
||||||
import DashboardPage from './pages/Dashboard';
|
import DashboardPage from './pages/Dashboard';
|
||||||
import GeneratePage from './pages/Generate';
|
|
||||||
import ManagePage from './pages/Manage';
|
import ManagePage from './pages/Manage';
|
||||||
import ProfilePage from './pages/Profile';
|
import ProfilePage from './pages/Profile';
|
||||||
import EmployeeSerialsPage from './pages/EmployeeSerials';
|
import EmployeeSerialsPage from './pages/EmployeeSerials';
|
||||||
@@ -49,7 +48,6 @@ function App() {
|
|||||||
<Route element={<AdminRoutes />}>
|
<Route element={<AdminRoutes />}>
|
||||||
<Route path="/admin" element={<Navigate to="dashboard" replace />} />
|
<Route path="/admin" element={<Navigate to="dashboard" replace />} />
|
||||||
<Route path="/admin/dashboard" element={<DashboardPage />} />
|
<Route path="/admin/dashboard" element={<DashboardPage />} />
|
||||||
<Route path="/admin/generate" element={<GeneratePage />} />
|
|
||||||
<Route path="/admin/manage" element={<ManagePage />} />
|
<Route path="/admin/manage" element={<ManagePage />} />
|
||||||
<Route path="/admin/employee-serials" element={<EmployeeSerialsPage />} />
|
<Route path="/admin/employee-serials" element={<EmployeeSerialsPage />} />
|
||||||
<Route path="/admin/profile" element={<ProfilePage />} />
|
<Route path="/admin/profile" element={<ProfilePage />} />
|
||||||
|
|||||||
@@ -2,11 +2,9 @@ import { Outlet, useNavigate, useLocation } from 'react-router-dom';
|
|||||||
import { Layout, Menu, Dropdown, Avatar, message, Modal } from 'antd';
|
import { Layout, Menu, Dropdown, Avatar, message, Modal } from 'antd';
|
||||||
import {
|
import {
|
||||||
DashboardOutlined,
|
DashboardOutlined,
|
||||||
QrcodeOutlined,
|
|
||||||
TeamOutlined,
|
TeamOutlined,
|
||||||
UserOutlined,
|
UserOutlined,
|
||||||
LogoutOutlined,
|
LogoutOutlined,
|
||||||
LockOutlined,
|
|
||||||
ExclamationCircleOutlined,
|
ExclamationCircleOutlined,
|
||||||
IdcardOutlined,
|
IdcardOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
@@ -28,12 +26,6 @@ function AdminLayout() {
|
|||||||
label: '控制台',
|
label: '控制台',
|
||||||
onClick: () => navigate('/admin/dashboard'),
|
onClick: () => navigate('/admin/dashboard'),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'generate',
|
|
||||||
icon: <QrcodeOutlined />,
|
|
||||||
label: '生成二维码',
|
|
||||||
onClick: () => navigate('/admin/generate'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'manage',
|
key: 'manage',
|
||||||
icon: <TeamOutlined />,
|
icon: <TeamOutlined />,
|
||||||
@@ -89,7 +81,6 @@ function AdminLayout() {
|
|||||||
const getSelectedKey = () => {
|
const getSelectedKey = () => {
|
||||||
const path = location.pathname;
|
const path = location.pathname;
|
||||||
if (path.includes('/dashboard')) return 'dashboard';
|
if (path.includes('/dashboard')) return 'dashboard';
|
||||||
if (path.includes('/generate')) return 'generate';
|
|
||||||
if (path.includes('/manage')) return 'manage';
|
if (path.includes('/manage')) return 'manage';
|
||||||
if (path.includes('/employee-serials')) return 'employee-serials';
|
if (path.includes('/employee-serials')) return 'employee-serials';
|
||||||
if (path.includes('/profile')) return 'profile';
|
if (path.includes('/profile')) return 'profile';
|
||||||
@@ -99,7 +90,6 @@ function AdminLayout() {
|
|||||||
const getTitle = () => {
|
const getTitle = () => {
|
||||||
const path = location.pathname;
|
const path = location.pathname;
|
||||||
if (path.includes('/dashboard')) return '控制台';
|
if (path.includes('/dashboard')) return '控制台';
|
||||||
if (path.includes('/generate')) return '生成二维码';
|
|
||||||
if (path.includes('/manage')) return '企业管理';
|
if (path.includes('/manage')) return '企业管理';
|
||||||
if (path.includes('/employee-serials')) return '员工管理';
|
if (path.includes('/employee-serials')) return '员工管理';
|
||||||
if (path.includes('/profile')) return '用户资料';
|
if (path.includes('/profile')) return '用户资料';
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { Card, Table, Input, Button, Space, message, Modal, Tag, Form, Select, InputNumber, Pagination } from 'antd';
|
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 { UserOutlined, PlusOutlined, StopOutlined, EditOutlined, QrcodeOutlined, DeleteOutlined } from '@ant-design/icons';
|
||||||
import { employeeSerialApi } from '@/services/api';
|
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';
|
import type { EmployeeSerial } from '@/types';
|
||||||
|
|
||||||
function EmployeeSerialsPage() {
|
function EmployeeSerialsPage() {
|
||||||
@@ -20,6 +23,20 @@ function EmployeeSerialsPage() {
|
|||||||
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
|
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
|
||||||
const [generateForm] = Form.useForm();
|
const [generateForm] = Form.useForm();
|
||||||
const [editForm] = Form.useForm();
|
const [editForm] = Form.useForm();
|
||||||
|
const [qrColor, setQrColor] = useState('#000000');
|
||||||
|
const [generatedData, setGeneratedData] = useState<any>(null);
|
||||||
|
const [generateSuccessModalVisible, setGenerateSuccessModalVisible] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const colorPresets = [
|
||||||
|
'#000000',
|
||||||
|
'#165DFF',
|
||||||
|
'#52C41A',
|
||||||
|
'#FAAD14',
|
||||||
|
'#FF4D4F',
|
||||||
|
'#722ED1',
|
||||||
|
'#EB2F96',
|
||||||
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadSerials();
|
loadSerials();
|
||||||
@@ -48,6 +65,21 @@ function EmployeeSerialsPage() {
|
|||||||
setGenerateLoading(true);
|
setGenerateLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await employeeSerialApi.generate(values);
|
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 || '生成成功');
|
message.success(result.message || '生成成功');
|
||||||
setGenerateModalVisible(false);
|
setGenerateModalVisible(false);
|
||||||
generateForm.resetFields();
|
generateForm.resetFields();
|
||||||
@@ -59,6 +91,21 @@ function EmployeeSerialsPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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) => {
|
const handleEdit = (serial: EmployeeSerial) => {
|
||||||
setSelectedSerial(serial);
|
setSelectedSerial(serial);
|
||||||
editForm.setFieldsValue({
|
editForm.setFieldsValue({
|
||||||
@@ -324,6 +371,38 @@ function EmployeeSerialsPage() {
|
|||||||
>
|
>
|
||||||
<InputNumber min={1} max={1000} style={{ width: '100%' }} />
|
<InputNumber min={1} max={1000} style={{ width: '100%' }} />
|
||||||
</Form.Item>
|
</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>
|
<Form.Item>
|
||||||
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
<Space style={{ width: '100%', justifyContent: 'flex-end' }}>
|
||||||
<Button onClick={() => setGenerateModalVisible(false)}>取消</Button>
|
<Button onClick={() => setGenerateModalVisible(false)}>取消</Button>
|
||||||
@@ -388,6 +467,45 @@ function EmployeeSerialsPage() {
|
|||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</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.serials?.[0]?.companyName}</p>
|
||||||
|
<p><strong>部门:</strong> {generatedData.serials?.[0]?.department}</p>
|
||||||
|
<p><strong>员工姓名:</strong> {generatedData.serials?.[0]?.employeeName}</p>
|
||||||
|
<p><strong>生成数量:</strong> {generatedData.serials?.length || 0}</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>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
title="员工二维码"
|
title="员工二维码"
|
||||||
open={qrCodeModalVisible}
|
open={qrCodeModalVisible}
|
||||||
|
|||||||
@@ -1,387 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { Form, Input, Button, Card, Radio, InputNumber, DatePicker, message, Modal, Space, ColorPicker } from 'antd';
|
|
||||||
import { QrcodeOutlined, UserOutlined } from '@ant-design/icons';
|
|
||||||
import QRCode from 'qrcode';
|
|
||||||
import type { Color } from 'antd/es/color-picker';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import './styles/Generate.css';
|
|
||||||
|
|
||||||
function GeneratePage() {
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [generatedData, setGeneratedData] = useState<any>(null);
|
|
||||||
const [qrCodeDataUrl, setQrCodeDataUrl] = useState<string>('');
|
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
|
||||||
const [qrColor, setQrColor] = useState<string>('#000000');
|
|
||||||
const [generateType, setGenerateType] = useState<'company' | 'employee'>('company');
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const colorPresets = [
|
|
||||||
'#000000',
|
|
||||||
'#165DFF',
|
|
||||||
'#52C41A',
|
|
||||||
'#FAAD14',
|
|
||||||
'#FF4D4F',
|
|
||||||
'#722ED1',
|
|
||||||
'#EB2F96',
|
|
||||||
];
|
|
||||||
|
|
||||||
const handleGenerate = async (values: any) => {
|
|
||||||
setLoading(true);
|
|
||||||
try {
|
|
||||||
const token = localStorage.getItem('authToken');
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
};
|
|
||||||
if (token) {
|
|
||||||
headers.Authorization = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data: any;
|
|
||||||
|
|
||||||
if (generateType === 'company') {
|
|
||||||
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),
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
setModalVisible(true);
|
|
||||||
message.success('生成成功!');
|
|
||||||
} else {
|
|
||||||
throw new Error(data.error || '生成失败');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const payload = {
|
|
||||||
companyName: values.companyName,
|
|
||||||
department: values.department,
|
|
||||||
employeeName: values.employeeName,
|
|
||||||
quantity: values.quantity,
|
|
||||||
serialPrefix: values.serialOption === 'custom' ? values.serialPrefix : undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const response = await fetch('/api/employee-serials/generate', {
|
|
||||||
method: 'POST',
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify(payload),
|
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
setModalVisible(true);
|
|
||||||
message.success('生成成功!');
|
|
||||||
} else {
|
|
||||||
throw new Error(data.error || '生成失败');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error: any) {
|
|
||||||
message.error(error.message || '生成失败');
|
|
||||||
} finally {
|
|
||||||
setLoading(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}`);
|
|
||||||
setModalVisible(false);
|
|
||||||
form.resetFields();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Card
|
|
||||||
title={
|
|
||||||
<Space>
|
|
||||||
<QrcodeOutlined />
|
|
||||||
<span>生成二维码</span>
|
|
||||||
</Space>
|
|
||||||
}
|
|
||||||
bordered={false}
|
|
||||||
>
|
|
||||||
<div style={{ marginBottom: 16 }}>
|
|
||||||
<Button.Group>
|
|
||||||
<Button
|
|
||||||
type={generateType === 'company' ? 'primary' : 'default'}
|
|
||||||
onClick={() => { setGenerateType('company'); form.resetFields(); }}
|
|
||||||
>
|
|
||||||
企业序列号
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
type={generateType === 'employee' ? 'primary' : 'default'}
|
|
||||||
onClick={() => { setGenerateType('employee'); form.resetFields(); }}
|
|
||||||
>
|
|
||||||
员工序列号
|
|
||||||
</Button>
|
|
||||||
</Button.Group>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style={{ maxWidth: 500 }}>
|
|
||||||
<Form
|
|
||||||
form={form}
|
|
||||||
onFinish={handleGenerate}
|
|
||||||
layout="vertical"
|
|
||||||
initialValues={{
|
|
||||||
serialOption: 'auto',
|
|
||||||
quantity: 1,
|
|
||||||
validOption: 'days',
|
|
||||||
validDays: 365,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
label="企业名称"
|
|
||||||
name="companyName"
|
|
||||||
rules={[{ required: true, message: '请输入企业名称' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="输入企业名称(如:浙江贝凡)" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
{generateType === 'employee' && (
|
|
||||||
<>
|
|
||||||
<Form.Item
|
|
||||||
label="部门"
|
|
||||||
name="department"
|
|
||||||
rules={[{ required: true, message: '请输入部门' }]}
|
|
||||||
>
|
|
||||||
<Input placeholder="输入部门(如:研发部)" />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
|
||||||
label="员工姓名"
|
|
||||||
name="employeeName"
|
|
||||||
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={generateType === 'employee' ? "如:EMP001" : "如: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>
|
|
||||||
|
|
||||||
{generateType === 'company' && (
|
|
||||||
<>
|
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{generateType === 'employee' && (
|
|
||||||
<div style={{ color: '#999', fontSize: 12, marginBottom: 16 }}>
|
|
||||||
员工序列号无有效期限制,长期有效
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<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: '32px',
|
|
||||||
height: '32px',
|
|
||||||
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);
|
|
||||||
}}
|
|
||||||
showText
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item>
|
|
||||||
<Button
|
|
||||||
type="primary"
|
|
||||||
htmlType="submit"
|
|
||||||
loading={loading}
|
|
||||||
size="large"
|
|
||||||
block
|
|
||||||
icon={generateType === 'company' ? <QrcodeOutlined /> : <UserOutlined />}
|
|
||||||
>
|
|
||||||
生成{generateType === 'company' ? '企业' : '员工'}序列号和二维码
|
|
||||||
</Button>
|
|
||||||
</Form.Item>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Modal
|
|
||||||
title="生成成功"
|
|
||||||
open={modalVisible}
|
|
||||||
onCancel={() => setModalVisible(false)}
|
|
||||||
footer={null}
|
|
||||||
width={600}
|
|
||||||
>
|
|
||||||
{generatedData && (
|
|
||||||
<div>
|
|
||||||
<Space direction="vertical" style={{ width: '100%' }} size="middle">
|
|
||||||
<div>
|
|
||||||
{generateType === 'company' ? (
|
|
||||||
<>
|
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<p><strong>企业名称:</strong> {generatedData.serials?.[0]?.companyName}</p>
|
|
||||||
<p><strong>部门:</strong> {generatedData.serials?.[0]?.department}</p>
|
|
||||||
<p><strong>员工姓名:</strong> {generatedData.serials?.[0]?.employeeName}</p>
|
|
||||||
<p><strong>生成数量:</strong> {generatedData.serials?.length || 0}</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={() => {
|
|
||||||
setModalVisible(false);
|
|
||||||
form.resetFields();
|
|
||||||
}}>生成新的二维码</Button>
|
|
||||||
</Space>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GeneratePage;
|
|
||||||
@@ -50,12 +50,19 @@ function ManagePage() {
|
|||||||
headers.Authorization = `Bearer ${token}`;
|
headers.Authorization = `Bearer ${token}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let url = `/api/companies?page=${page}&limit=${pageSize}`;
|
let url = '/api/companies';
|
||||||
if (searchTerm) {
|
const params = new URLSearchParams();
|
||||||
url += `&search=${encodeURIComponent(searchTerm)}`;
|
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 });
|
const response = await fetch(url, { headers });
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`请求失败: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data.data) {
|
if (data.data) {
|
||||||
@@ -276,6 +283,7 @@ function ManagePage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message.success('生成成功!');
|
message.success('生成成功!');
|
||||||
|
setGenerateSuccessModalVisible(true);
|
||||||
loadCompanies();
|
loadCompanies();
|
||||||
} else {
|
} else {
|
||||||
throw new Error(data.error || '生成失败');
|
throw new Error(data.error || '生成失败');
|
||||||
@@ -294,6 +302,26 @@ function ManagePage() {
|
|||||||
link.click();
|
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) => {
|
const handlePageChange = (newPage: number, newPageSize: number) => {
|
||||||
setPage(newPage);
|
setPage(newPage);
|
||||||
setPageSize(newPageSize);
|
setPageSize(newPageSize);
|
||||||
@@ -560,14 +588,6 @@ function ManagePage() {
|
|||||||
}
|
}
|
||||||
</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="validOption">
|
<Form.Item label="有效期设置" name="validOption">
|
||||||
<Radio.Group>
|
<Radio.Group>
|
||||||
<Radio value="days">按天数</Radio>
|
<Radio value="days">按天数</Radio>
|
||||||
@@ -600,11 +620,37 @@ function ManagePage() {
|
|||||||
}
|
}
|
||||||
</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
|
<Form.Item
|
||||||
label="二维码颜色"
|
label="二维码颜色"
|
||||||
name="qrColor"
|
name="qrColor"
|
||||||
initialValue="#000000"
|
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
|
<ColorPicker
|
||||||
value={qrColor}
|
value={qrColor}
|
||||||
onChange={(color: Color) => {
|
onChange={(color: Color) => {
|
||||||
@@ -612,6 +658,7 @@ function ManagePage() {
|
|||||||
setQrColor(hexColor);
|
setQrColor(hexColor);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
@@ -624,6 +671,46 @@ function ManagePage() {
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,14 @@ function ProfilePage() {
|
|||||||
email: values.email,
|
email: values.email,
|
||||||
});
|
});
|
||||||
setUser(updatedUser);
|
setUser(updatedUser);
|
||||||
|
localStorage.setItem('currentUser', JSON.stringify(updatedUser));
|
||||||
|
profileForm.setFieldsValue({
|
||||||
|
username: updatedUser.username,
|
||||||
|
name: updatedUser.name,
|
||||||
|
email: updatedUser.email,
|
||||||
|
role: updatedUser.role,
|
||||||
|
createdAt: new Date(updatedUser.createdAt).toLocaleString('zh-CN'),
|
||||||
|
});
|
||||||
message.success('更新资料成功!');
|
message.success('更新资料成功!');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error.message || '更新资料失败');
|
message.error(error.message || '更新资料失败');
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
.generate-page {
|
|
||||||
padding: 24px;
|
|
||||||
}
|
|
||||||
@@ -242,7 +242,7 @@ export const employeeSerialApi = {
|
|||||||
if (params.toString()) url += `?${params.toString()}`;
|
if (params.toString()) url += `?${params.toString()}`;
|
||||||
|
|
||||||
const response = await apiClient.get(url);
|
const response = await apiClient.get(url);
|
||||||
if (response.data.data) {
|
if (response.data) {
|
||||||
return response.data as EmployeeSerialResponse;
|
return response.data as EmployeeSerialResponse;
|
||||||
}
|
}
|
||||||
throw new Error('获取员工序列号列表失败');
|
throw new Error('获取员工序列号列表失败');
|
||||||
|
|||||||
Reference in New Issue
Block a user