diff --git a/src/pages/EmployeeSerials.tsx b/src/pages/EmployeeSerials.tsx index c1f9709..3a39b36 100644 --- a/src/pages/EmployeeSerials.tsx +++ b/src/pages/EmployeeSerials.tsx @@ -1,289 +1,255 @@ -import { useEffect, useState } from 'react'; -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'; -import { authApi } from '@/services/api'; -import EmployeeAccountsPanel from '@/components/EmployeeAccountsPanel'; +import { useEffect, useMemo, useState } from 'react'; +import { + Card, + Table, + Button, + Space, + Input, + Select, + Tag, + Modal, + Form, + message, + Pagination, + InputNumber, +} from 'antd'; +import { + UserOutlined, + PlusOutlined, + EditOutlined, + DeleteOutlined, + KeyOutlined, + IdcardOutlined, +} from '@ant-design/icons'; +import { usersApi, authApi, employeeSerialApi } from '@/services/api'; +import type { User, UserRole, CreateUserRequest, UpdateUserRequest, EmployeeSerial } from '@/types'; + +const ROLE_LABEL: Record = { + admin: '管理员', + technician: '技术员', + employee: '员工(不可登录后台)', + user: '普通用户', +}; + +const ROLE_COLOR: Record = { + admin: 'red', + technician: 'blue', + employee: 'green', + user: 'default', +}; function EmployeeSerialsPage() { - const [serials, setSerials] = useState([]); - 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(null); - const [qrCodeModalVisible, setQrCodeModalVisible] = useState(false); - 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 currentUser = authApi.getCurrentUser(); const isAdmin = currentUser?.role === 'admin'; - const colorPresets = [ - '#000000', - '#165DFF', - '#52C41A', - '#FAAD14', - '#FF4D4F', - '#722ED1', - '#EB2F96', - ]; + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const [page, setPage] = useState(1); + const [limit, setLimit] = useState(10); + const [total, setTotal] = useState(0); + const [search, setSearch] = useState(''); + const [roleFilter, setRoleFilter] = useState(); - useEffect(() => { - loadSerials(); - }, [page, limit, searchTerm]); + const [createVisible, setCreateVisible] = useState(false); + const [createLoading, setCreateLoading] = useState(false); + const [createForm] = Form.useForm(); - const handlePageChange = (newPage: number, newLimit: number) => { - setPage(newPage); - setLimit(newLimit); - }; + const [editingUser, setEditingUser] = useState(null); + const [editLoading, setEditLoading] = useState(false); + const [editForm] = Form.useForm(); - const loadSerials = async () => { + const [resetPasswordUser, setResetPasswordUser] = useState(null); + const [resetLoading, setResetLoading] = useState(false); + const [resetForm] = Form.useForm<{ newPassword: string }>(); + + const [assignUser, setAssignUser] = useState(null); + const [assignLoading, setAssignLoading] = useState(false); + const [assignForm] = Form.useForm<{ companyName: string; position: string; quantity: number }>(); + + const [serialsVisible, setSerialsVisible] = useState(false); + const [serialsLoading, setSerialsLoading] = useState(false); + const [serials, setSerials] = useState([]); + + const loadUsers = 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([]); + const result = await usersApi.list({ + page, + limit, + search: search || undefined, + role: roleFilter, + }); + setUsers(result.data || []); + setTotal(result.pagination?.total || 0); + } catch (err: any) { + message.error(err?.response?.data?.message || err.message || '加载员工列表失败'); + setUsers([]); } finally { setLoading(false); } }; - const handleGenerate = async (values: { companyName: string; position: string; employeeName: string; quantity: number }) => { - setGenerateLoading(true); + useEffect(() => { + loadUsers(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [page, limit, search, roleFilter]); + + const handleCreate = async (values: CreateUserRequest) => { + setCreateLoading(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(); - loadSerials(); - } catch (error: any) { - message.error(error.message || '生成失败'); + await usersApi.create(values); + message.success('员工创建成功'); + setCreateVisible(false); + createForm.resetFields(); + loadUsers(); + } catch (err: any) { + message.error(err?.response?.data?.message || err.message || '创建失败'); } finally { - setGenerateLoading(false); + setCreateLoading(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}`); - setGenerateSuccessModalVisible(false); - generateForm.resetFields(); - } - }; - - const handleEdit = (serial: EmployeeSerial) => { - setSelectedSerial(serial); + const openEdit = (user: User) => { + setEditingUser(user); editForm.setFieldsValue({ - companyName: serial.companyName, - position: serial.position, // 映射 position 到 position - employeeName: serial.employeeName, - isActive: serial.isActive, + name: user.name, + email: user.email, + role: user.role, }); - setEditModalVisible(true); }; - const handleUpdate = async (values: { companyName?: string; position?: string; employeeName?: string; isActive?: boolean }) => { - if (!selectedSerial) return; + const handleEdit = async (values: UpdateUserRequest) => { + if (!editingUser) return; setEditLoading(true); try { - await employeeSerialApi.update(selectedSerial.serialNumber, { - companyName: values.companyName, - position: values.position, // 映射 position 到 position - employeeName: values.employeeName, - isActive: values.isActive, - }); - message.success('更新成功'); - setEditModalVisible(false); - loadSerials(); - } catch (error: any) { - message.error(error.message || '更新失败'); + await usersApi.update(editingUser.id, values); + message.success('员工资料更新成功'); + setEditingUser(null); + loadUsers(); + } catch (err: any) { + message.error(err?.response?.data?.message || err.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); + const handleResetPassword = async (values: { newPassword: string }) => { + if (!resetPasswordUser) return; + setResetLoading(true); 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 || '生成二维码失败'); + await usersApi.resetPassword(resetPasswordUser.id, values.newPassword); + message.success('密码重置成功'); + setResetPasswordUser(null); + resetForm.resetFields(); + } catch (err: any) { + message.error(err?.response?.data?.message || err.message || '重置失败'); + } finally { + setResetLoading(false); } }; - const handleSearch = (value: string) => { - setSearchTerm(value); - setPage(1); + const handleDelete = (user: User) => { + Modal.confirm({ + title: '确认删除', + content: `确定要删除员工 "${user.username}" 吗?`, + okText: '确定', + okType: 'danger', + cancelText: '取消', + onOk: async () => { + try { + await usersApi.delete(user.id); + message.success('删除成功'); + loadUsers(); + } catch (err: any) { + message.error(err?.response?.data?.message || err.message || '删除失败'); + } + }, + }); }; - const columns = [ - { - title: '序列号', - dataIndex: 'serialNumber', - key: 'serialNumber', - width: 180, - }, - { - title: '企业名称', - dataIndex: 'companyName', - key: 'companyName', - }, - { - title: '职位', - dataIndex: 'position', - key: 'position', - }, - { - title: '员工姓名', - dataIndex: 'employeeName', - key: 'employeeName', - }, - { - title: '状态', - dataIndex: 'isActive', - key: 'isActive', - render: (isActive: boolean) => ( - - {isActive ? '有效' : '已吊销'} - - ), - }, - { - title: '创建时间', - dataIndex: 'createdAt', - key: 'createdAt', - render: (date: string) => new Date(date).toLocaleString('zh-CN'), - }, - { - title: '操作', - key: 'actions', - render: (_: any, record: EmployeeSerial) => ( - - - - {record.isActive && ( - - )} - - - ), - }, - ]; + + + + {record.id !== currentUser?.id && ( + + )} + + ), + }, + ], + [currentUser?.id] + ); return (
@@ -295,247 +261,149 @@ function EmployeeSerialsPage() { } extra={ - - { - if (!e.target.value) { - handleSearch(''); - } - }} - /> - - + ) } > - + + { + setPage(1); + setSearch(v); + }} + onChange={(e) => { + if (!e.target.value) { + setPage(1); + setSearch(''); + } + }} + /> +
{ + setPage(newPage); + setLimit(newLimit); + }} + showSizeChanger showTotal={(t) => `共计 ${t} 条记录`} />
- {isAdmin &&
} - { - setGenerateModalVisible(false); - generateForm.resetFields(); - }} - footer={null} - width={500} - > -
- - + setCreateVisible(false)} footer={null} width={480}> + + + - - + + - - + + - + + + + + + + + + + + + + - -
-
- {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); - }} - /> -
- - - + + - { - setEditModalVisible(false); - editForm.resetFields(); - }} - footer={null} - width={500} - > -
- - - - - - - - - - - - - - - - - - - -
- - setGenerateSuccessModalVisible(false)} - footer={null} - width={600} - > - {generatedData && ( -
- -
-

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

-

职位: {generatedData.serials?.[0]?.position}

-

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

-

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

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

{generatedData.serials[0].serialNumber}

- )} -
- )} - - - - - - -
-
- )} -
- - setQrCodeModalVisible(false)} - footer={null} - width={400} - > -
- {qrCodeDataUrl && ( - <> - QR Code -

- {selectedSerial?.serialNumber} -

-

- {selectedSerial?.companyName} - {selectedSerial?.position} - {selectedSerial?.employeeName} -

- - )} -
+ setSerialsVisible(false)} footer={null} width={900}> +
{v ? '有效' : '已吊销'} }, + { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', render: (v: string) => new Date(v).toLocaleString('zh-CN') }, + ]} + /> );