Remove Generate.tsx

This commit is contained in:
2026-03-02 13:36:21 +08:00
parent 54d6b31da6
commit 76d5cdf542
8 changed files with 234 additions and 423 deletions

View File

@@ -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 />} />

View File

@@ -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 '用户资料';

View File

@@ -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}

View File

@@ -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;

View File

@@ -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>
); );
} }

View File

@@ -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 || '更新资料失败');

View File

@@ -1,3 +0,0 @@
.generate-page {
padding: 24px;
}

View File

@@ -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('获取员工序列号列表失败');