From fe784f9e2b95159739d6fe157e0b859506fa148c Mon Sep 17 00:00:00 2001 From: Frudrax Cheng Date: Thu, 28 May 2026 09:30:30 +0800 Subject: [PATCH] feat: merge account management into employee page --- src/App.tsx | 2 - src/components/AdminLayout.tsx | 15 +- src/components/EmployeeAccountsPanel.tsx | 416 +++++++++++++++++++++++ src/pages/EmployeeSerials.tsx | 7 +- src/pages/Users.tsx | 2 + src/types/index.ts | 2 +- 6 files changed, 426 insertions(+), 18 deletions(-) create mode 100644 src/components/EmployeeAccountsPanel.tsx diff --git a/src/App.tsx b/src/App.tsx index b4ea82a..9bbf5eb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,7 +10,6 @@ import EmployeeSerialsPage from './pages/EmployeeSerials'; import AftersalesPage from './pages/Aftersales'; import AftersalesDetailPage from './pages/AftersalesDetail'; import AftersalesConfirmPage from './pages/AftersalesConfirm'; -import UsersPage from './pages/Users'; const PrivateRoute = () => { const user = authApi.getCurrentUser(); @@ -53,7 +52,6 @@ function App() { } /> } /> } /> - } /> } /> diff --git a/src/components/AdminLayout.tsx b/src/components/AdminLayout.tsx index 2310565..8b047d6 100644 --- a/src/components/AdminLayout.tsx +++ b/src/components/AdminLayout.tsx @@ -8,7 +8,6 @@ import { ExclamationCircleOutlined, IdcardOutlined, ToolOutlined, - UsergroupAddOutlined, } from '@ant-design/icons'; import { authApi } from '@/services/api'; import './styles/AdminLayout.css'; @@ -46,16 +45,6 @@ function AdminLayout() { label: '售后工单', onClick: () => navigate('/admin/aftersales'), }, - ...(user?.role === 'admin' - ? [ - { - key: 'users', - icon: , - label: '用户管理', - onClick: () => navigate('/admin/users'), - }, - ] - : []), ]; const handleLogout = () => { @@ -102,7 +91,6 @@ function AdminLayout() { if (path.includes('/manage')) return 'manage'; if (path.includes('/employee-serials')) return 'employee-serials'; if (path.includes('/aftersales')) return 'aftersales'; - if (path.includes('/users')) return 'users'; if (path.includes('/profile')) return 'profile'; return 'dashboard'; }; @@ -113,7 +101,6 @@ function AdminLayout() { if (path.includes('/manage')) return '企业管理'; if (path.includes('/employee-serials')) return '员工管理'; if (path.includes('/aftersales')) return '售后工单'; - if (path.includes('/users')) return '用户管理'; if (path.includes('/profile')) return '用户资料'; return '控制台'; }; @@ -159,4 +146,4 @@ function AdminLayout() { ); } -export default AdminLayout; \ No newline at end of file +export default AdminLayout; diff --git a/src/components/EmployeeAccountsPanel.tsx b/src/components/EmployeeAccountsPanel.tsx new file mode 100644 index 0000000..d88f2c6 --- /dev/null +++ b/src/components/EmployeeAccountsPanel.tsx @@ -0,0 +1,416 @@ +import { useEffect, useState } from 'react'; +import { + Card, + Table, + Button, + Space, + Input, + Select, + Tag, + Modal, + Form, + message, + Pagination, +} from 'antd'; +import { + UsergroupAddOutlined, + PlusOutlined, + EditOutlined, + DeleteOutlined, + KeyOutlined, +} from '@ant-design/icons'; +import { usersApi, authApi } from '@/services/api'; +import type { User, UserRole, CreateUserRequest, UpdateUserRequest } from '@/types'; + +const ROLE_LABEL: Record = { + admin: '管理员', + technician: '技术员', + employee: '员工(不可登录后台)', + user: '普通用户', +}; + +const ROLE_COLOR: Record = { + admin: 'red', + technician: 'blue', + employee: 'green', + user: 'default', +}; + +function EmployeeAccountsPanel() { + const currentUser = authApi.getCurrentUser(); + + 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(); + + const [createVisible, setCreateVisible] = useState(false); + const [createLoading, setCreateLoading] = useState(false); + const [createForm] = Form.useForm(); + + const [editingUser, setEditingUser] = useState(null); + const [editLoading, setEditLoading] = useState(false); + const [editForm] = Form.useForm(); + + const [resetPasswordUser, setResetPasswordUser] = useState(null); + const [resetLoading, setResetLoading] = useState(false); + const [resetForm] = Form.useForm<{ newPassword: string }>(); + + const loadUsers = async () => { + setLoading(true); + try { + 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); + } + }; + + useEffect(() => { + loadUsers(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [page, limit, search, roleFilter]); + + const handleCreate = async (values: CreateUserRequest) => { + setCreateLoading(true); + try { + await usersApi.create(values); + message.success('员工账号创建成功'); + setCreateVisible(false); + createForm.resetFields(); + loadUsers(); + } catch (err: any) { + message.error(err?.response?.data?.message || err.message || '创建失败'); + } finally { + setCreateLoading(false); + } + }; + + const openEdit = (user: User) => { + setEditingUser(user); + editForm.setFieldsValue({ + name: user.name, + email: user.email, + role: user.role, + }); + }; + + const handleEdit = async (values: UpdateUserRequest) => { + if (!editingUser) return; + setEditLoading(true); + try { + 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 handleResetPassword = async (values: { newPassword: string }) => { + if (!resetPasswordUser) return; + setResetLoading(true); + try { + 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 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: 'username', + key: 'username', + render: (text: string) => {text}, + }, + { + title: '姓名', + dataIndex: 'name', + key: 'name', + }, + { + title: '邮箱', + dataIndex: 'email', + key: 'email', + render: (text?: string) => text || '-', + }, + { + title: '角色', + dataIndex: 'role', + key: 'role', + width: 100, + render: (role: UserRole) => {ROLE_LABEL[role]}, + }, + { + title: '创建时间', + dataIndex: 'createdAt', + key: 'createdAt', + width: 170, + render: (date: string) => new Date(date).toLocaleString('zh-CN'), + }, + { + title: '操作', + key: 'actions', + width: 260, + render: (_: any, record: User) => ( + + + + {record.id !== currentUser?.id && ( + + )} + + ), + }, + ]; + + return ( + + + 员工账号管理 + + } + extra={ + + } + > + + { + setPage(1); + setSearch(v); + }} + onChange={(e) => { + if (!e.target.value) { + setPage(1); + setSearch(''); + } + }} + /> + + + + + + + + + + + + + + + + + + +