import { useEffect, useMemo, useState } from 'react'; import QRCode from 'qrcode'; import { Card, Table, Button, Space, Input, Select, Tag, Modal, Form, message, Pagination, } from 'antd'; import { UserOutlined, PlusOutlined, EditOutlined, DeleteOutlined, QrcodeOutlined, KeyOutlined, } from '@ant-design/icons'; import { useNavigate } from 'react-router-dom'; import { authApi, employeesApi } from '@/services/api'; import type { User, UserRole, CreateUserRequest, UpdateUserRequest, EmployeeSerial } from '@/types'; const ROLE_LABEL: Record = { admin: '管理员', technician: '技术员', employee: '员工', }; const ROLE_COLOR: Record = { admin: 'red', technician: 'blue', employee: 'green', }; const BACKEND_ROLES: UserRole[] = ['admin', 'technician']; const ROLE_OPTIONS = (Object.keys(ROLE_LABEL) as UserRole[]).map((value) => ({ value, label: ROLE_LABEL[value], })); const canLoginBackend = (role?: UserRole) => !!role && BACKEND_ROLES.includes(role); const getDisplaySerial = (serials?: EmployeeSerial[]) => serials?.find((serial) => serial.isActive) || serials?.[0]; function EmployeeSerialsPage() { const navigate = useNavigate(); const currentUser = authApi.getCurrentUser(); const isAdmin = currentUser?.role === 'admin'; const [employees, setEmployees] = 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(); const [createVisible, setCreateVisible] = useState(false); const [createLoading, setCreateLoading] = useState(false); const [createForm] = Form.useForm(); const createRole = Form.useWatch('role', createForm); const [editingEmployee, setEditingEmployee] = useState(null); const [editLoading, setEditLoading] = useState(false); const [editForm] = Form.useForm(); const [resetPasswordEmployee, setResetPasswordEmployee] = useState(null); const [resetLoading, setResetLoading] = useState(false); const [resetForm] = Form.useForm<{ newPassword: string }>(); const [qrCodeVisible, setQrCodeVisible] = useState(false); const [qrCodeDataUrl, setQrCodeDataUrl] = useState(''); const [selectedSerial, setSelectedSerial] = useState(''); const loadEmployees = async () => { setLoading(true); try { const result = await employeesApi.list({ page, limit, search: search || undefined, role: roleFilter, }); setEmployees(result.data || []); setTotal(result.pagination?.total || 0); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '加载员工列表失败'); setEmployees([]); } finally { setLoading(false); } }; useEffect(() => { loadEmployees(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [page, limit, search, roleFilter]); const openCreate = () => { createForm.resetFields(); createForm.setFieldsValue({ role: 'employee' }); setCreateVisible(true); }; const handleCreate = async (values: CreateUserRequest) => { const payload = { ...values, username: values.employeeNo }; if (!canLoginBackend(values.role)) { delete payload.password; } setCreateLoading(true); try { await employeesApi.create(payload); message.success('员工创建成功,员工码已自动生成'); setCreateVisible(false); createForm.resetFields(); loadEmployees(); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '创建失败'); } finally { setCreateLoading(false); } }; const openEdit = (employee: User) => { setEditingEmployee(employee); editForm.setFieldsValue({ name: employee.name, email: employee.email, phone: employee.phone, employeeNo: employee.employeeNo, position: employee.position, role: employee.role, }); }; const handleEdit = async (values: UpdateUserRequest) => { if (!editingEmployee) return; setEditLoading(true); try { await employeesApi.update(editingEmployee.id, values); message.success('员工资料更新成功'); setEditingEmployee(null); loadEmployees(); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '更新失败'); } finally { setEditLoading(false); } }; const handleResetPassword = async (values: { newPassword: string }) => { if (!resetPasswordEmployee) return; setResetLoading(true); try { await employeesApi.resetPassword(resetPasswordEmployee.id, values.newPassword); message.success('密码重置成功'); setResetPasswordEmployee(null); resetForm.resetFields(); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '重置失败'); } finally { setResetLoading(false); } }; const handleDelete = (employee: User) => { Modal.confirm({ title: '确认删除', content: `确定要删除员工 "${employee.name}" 吗?`, okText: '确定', okType: 'danger', cancelText: '取消', onOk: async () => { try { await employeesApi.delete(employee.id); message.success('删除成功'); loadEmployees(); } catch (err: any) { message.error(err?.response?.data?.message || err.message || '删除失败'); } }, }); }; const handleViewQRCode = async (serialNumber: string) => { try { const queryUrl = `${window.location.origin}/query?serial=${serialNumber}`; const qrCode = await QRCode.toDataURL(queryUrl); setQrCodeDataUrl(qrCode); setSelectedSerial(serialNumber); setQrCodeVisible(true); } catch (err: any) { message.error(err?.message || '生成二维码失败'); } }; const handleQuerySerial = (serialNumber: string) => { setQrCodeVisible(false); navigate(`/query?serial=${serialNumber}`); }; const columns = useMemo( () => [ { title: '姓名', dataIndex: 'name', key: 'name', width: 120 }, { title: '电话', dataIndex: 'phone', key: 'phone', width: 140, render: (v?: string) => v || '-' }, { title: '工号', dataIndex: 'employeeNo', key: 'employeeNo', width: 130, render: (v?: string) => v || '-' }, { title: '岗位', dataIndex: 'position', key: 'position', width: 140, render: (v?: string) => v || '-' }, { title: '角色', dataIndex: 'role', key: 'role', width: 110, render: (role: UserRole) => {ROLE_LABEL[role]}, }, { title: '员工码', dataIndex: 'employeeSerials', key: 'employeeSerials', width: 180, render: (serials?: EmployeeSerial[]) => { const serial = getDisplaySerial(serials); if (!serial) return '-'; return ( {serial.serialNumber} {serial.isActive ? '有效' : '已吊销'} ); }, }, { title: '创建时间', dataIndex: 'createdAt', key: 'createdAt', width: 170, render: (v: string) => new Date(v).toLocaleString('zh-CN'), }, { title: '操作', key: 'actions', width: 320, render: (_: unknown, record: User) => { const serial = getDisplaySerial(record.employeeSerials); return ( {serial && ( )} {canLoginBackend(record.role) && ( )} {record.id !== currentUser?.id && ( )} ); }, }, ], [currentUser?.id] ); return (
权限管理 } extra={ isAdmin && ( ) } > { setPage(1); setSearch(v); }} onChange={(e) => { if (!e.target.value) { setPage(1); setSearch(''); } }} />