diff --git a/src/App.tsx b/src/App.tsx index 92f0bb9..906445d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,7 +4,6 @@ import LoginPage from './pages/Login'; import AdminLayout from './components/AdminLayout'; import PublicQueryPage from './pages/PublicQuery'; import DashboardPage from './pages/Dashboard'; -import GeneratePage from './pages/Generate'; import ManagePage from './pages/Manage'; import ProfilePage from './pages/Profile'; import EmployeeSerialsPage from './pages/EmployeeSerials'; @@ -49,7 +48,6 @@ function App() { }> } /> } /> - } /> } /> } /> } /> diff --git a/src/components/AdminLayout.tsx b/src/components/AdminLayout.tsx index df34039..61e7949 100644 --- a/src/components/AdminLayout.tsx +++ b/src/components/AdminLayout.tsx @@ -2,11 +2,9 @@ import { Outlet, useNavigate, useLocation } from 'react-router-dom'; import { Layout, Menu, Dropdown, Avatar, message, Modal } from 'antd'; import { DashboardOutlined, - QrcodeOutlined, TeamOutlined, UserOutlined, LogoutOutlined, - LockOutlined, ExclamationCircleOutlined, IdcardOutlined, } from '@ant-design/icons'; @@ -28,12 +26,6 @@ function AdminLayout() { label: '控制台', onClick: () => navigate('/admin/dashboard'), }, - { - key: 'generate', - icon: , - label: '生成二维码', - onClick: () => navigate('/admin/generate'), - }, { key: 'manage', icon: , @@ -89,7 +81,6 @@ function AdminLayout() { const getSelectedKey = () => { const path = location.pathname; if (path.includes('/dashboard')) return 'dashboard'; - if (path.includes('/generate')) return 'generate'; if (path.includes('/manage')) return 'manage'; if (path.includes('/employee-serials')) return 'employee-serials'; if (path.includes('/profile')) return 'profile'; @@ -99,7 +90,6 @@ function AdminLayout() { const getTitle = () => { const path = location.pathname; if (path.includes('/dashboard')) return '控制台'; - if (path.includes('/generate')) return '生成二维码'; if (path.includes('/manage')) return '企业管理'; if (path.includes('/employee-serials')) return '员工管理'; if (path.includes('/profile')) return '用户资料'; diff --git a/src/pages/EmployeeSerials.tsx b/src/pages/EmployeeSerials.tsx index 9d0e015..8590a15 100644 --- a/src/pages/EmployeeSerials.tsx +++ b/src/pages/EmployeeSerials.tsx @@ -1,7 +1,10 @@ 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 { 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'; function EmployeeSerialsPage() { @@ -20,6 +23,20 @@ function EmployeeSerialsPage() { const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); const [generateForm] = Form.useForm(); const [editForm] = Form.useForm(); + const [qrColor, setQrColor] = useState('#000000'); + const [generatedData, setGeneratedData] = useState(null); + const [generateSuccessModalVisible, setGenerateSuccessModalVisible] = useState(false); + const navigate = useNavigate(); + + const colorPresets = [ + '#000000', + '#165DFF', + '#52C41A', + '#FAAD14', + '#FF4D4F', + '#722ED1', + '#EB2F96', + ]; useEffect(() => { loadSerials(); @@ -48,6 +65,21 @@ function EmployeeSerialsPage() { setGenerateLoading(true); try { 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 || '生成成功'); setGenerateModalVisible(false); 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) => { setSelectedSerial(serial); editForm.setFieldsValue({ @@ -324,6 +371,38 @@ function EmployeeSerialsPage() { > + +
+
+ {colorPresets.map((color) => ( +
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', + }} + /> + ))} +
+ { + const hexColor = color.toHexString(); + setQrColor(hexColor); + }} + /> +
+ @@ -388,6 +467,45 @@ function EmployeeSerialsPage() { + setGenerateSuccessModalVisible(false)} + footer={null} + width={600} + > + {generatedData && ( +
+ +
+

企业名称: {generatedData.serials?.[0]?.companyName}

+

部门: {generatedData.serials?.[0]?.department}

+

员工姓名: {generatedData.serials?.[0]?.employeeName}

+

生成数量: {generatedData.serials?.length || 0}

+
+ + {qrCodeDataUrl && ( +
+ QR Code + {generatedData.serials && generatedData.serials.length > 0 && ( +

{generatedData.serials[0].serialNumber}

+ )} +
+ )} + + + + + + +
+
+ )} +
+ (null); - const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); - const [modalVisible, setModalVisible] = useState(false); - const [qrColor, setQrColor] = useState('#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 ( -
- - - 生成二维码 - - } - bordered={false} - > -
- - - - -
- -
-
- - - - - {generateType === 'employee' && ( - <> - - - - - - - - - )} - - - - 自动生成 - 自定义前缀 - - - - prevValues.serialOption !== currentValues.serialOption} - > - {({ getFieldValue }) => - getFieldValue('serialOption') === 'custom' ? ( - - - - ) : null - } - - - - - - - {generateType === 'company' && ( - <> - - - 按天数 - 按日期 - - - - prevValues.validOption !== currentValues.validOption} - > - {({ getFieldValue }) => - getFieldValue('validOption') === 'days' ? ( - - - - ) : ( - - - - ) - } - - - )} - - {generateType === 'employee' && ( -
- 员工序列号无有效期限制,长期有效 -
- )} - - -
-
- {colorPresets.map((color) => ( -
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', - }} - /> - ))} -
- { - const hexColor = color.toHexString(); - setQrColor(hexColor); - }} - showText - /> -
- - - - - - -
- - - setModalVisible(false)} - footer={null} - width={600} - > - {generatedData && ( -
- -
- {generateType === 'company' ? ( - <> -

企业名称: {generatedData.companyName || generatedData.serials?.[0]?.companyName}

-

生成数量: {generatedData.serials?.length || 0}

- {generatedData.serials && generatedData.serials.length > 0 && ( -

有效期至: {new Date(generatedData.serials[0].validUntil).toLocaleString('zh-CN')}

- )} - - ) : ( - <> -

企业名称: {generatedData.serials?.[0]?.companyName}

-

部门: {generatedData.serials?.[0]?.department}

-

员工姓名: {generatedData.serials?.[0]?.employeeName}

-

生成数量: {generatedData.serials?.length || 0}

- - )} -
- - {qrCodeDataUrl && ( -
- QR Code - {generatedData.serials && generatedData.serials.length > 0 && ( -

{generatedData.serials[0].serialNumber}

- )} -
- )} - - - - - - -
-
- )} -
-
- ); -} - -export default GeneratePage; \ No newline at end of file diff --git a/src/pages/Manage.tsx b/src/pages/Manage.tsx index 7a926b9..13852ba 100644 --- a/src/pages/Manage.tsx +++ b/src/pages/Manage.tsx @@ -50,12 +50,19 @@ function ManagePage() { headers.Authorization = `Bearer ${token}`; } - let url = `/api/companies?page=${page}&limit=${pageSize}`; - if (searchTerm) { - url += `&search=${encodeURIComponent(searchTerm)}`; - } + 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) { @@ -276,6 +283,7 @@ function ManagePage() { } message.success('生成成功!'); + setGenerateSuccessModalVisible(true); loadCompanies(); } else { throw new Error(data.error || '生成失败'); @@ -294,6 +302,26 @@ function ManagePage() { 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); @@ -560,14 +588,6 @@ function ManagePage() { } - - - - 按天数 @@ -600,18 +620,45 @@ function ManagePage() { } + + + + - { - const hexColor = color.toHexString(); - setQrColor(hexColor); - }} - /> +
+
+ {colorPresets.map((color) => ( +
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', + }} + /> + ))} +
+ { + const hexColor = color.toHexString(); + setQrColor(hexColor); + }} + /> +
@@ -624,6 +671,46 @@ function ManagePage() { + + setGenerateSuccessModalVisible(false)} + footer={null} + width={600} + > + {generatedData && ( +
+ +
+

企业名称: {generatedData.companyName || generatedData.serials?.[0]?.companyName}

+

生成数量: {generatedData.serials?.length || 0}

+ {generatedData.serials && generatedData.serials.length > 0 && ( +

有效期至: {new Date(generatedData.serials[0].validUntil).toLocaleString('zh-CN')}

+ )} +
+ + {qrCodeDataUrl && ( +
+ QR Code + {generatedData.serials && generatedData.serials.length > 0 && ( +

{generatedData.serials[0].serialNumber}

+ )} +
+ )} + + + + + + +
+
+ )} +
); } diff --git a/src/pages/Profile.tsx b/src/pages/Profile.tsx index 3798ca2..146cb3b 100644 --- a/src/pages/Profile.tsx +++ b/src/pages/Profile.tsx @@ -37,6 +37,14 @@ function ProfilePage() { email: values.email, }); 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('更新资料成功!'); } catch (error: any) { message.error(error.message || '更新资料失败'); diff --git a/src/pages/styles/Generate.css b/src/pages/styles/Generate.css deleted file mode 100644 index bdbd244..0000000 --- a/src/pages/styles/Generate.css +++ /dev/null @@ -1,3 +0,0 @@ -.generate-page { - padding: 24px; -} diff --git a/src/services/api.ts b/src/services/api.ts index 8cb6c33..349a08e 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -242,7 +242,7 @@ export const employeeSerialApi = { if (params.toString()) url += `?${params.toString()}`; const response = await apiClient.get(url); - if (response.data.data) { + if (response.data) { return response.data as EmployeeSerialResponse; } throw new Error('获取员工序列号列表失败');