diff --git a/AGENTS.md b/AGENTS.md index d5ffac8..5c9d69d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -90,6 +90,7 @@ src/ - `dashboardApi` - Dashboard statistics - `employeeSerialApi` - Employee serial management - `aftersalesApi` - Aftersales work orders (admin + public) + - `usersApi` - User management (admin only); also exposes `assignable` for technician/admin picker - Auth token automatically added via axios interceptor - All API calls return typed responses based on `src/types/index.ts` diff --git a/README.md b/README.md index 1b52800..2ce20c4 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ frontend/ │ │ ├── Aftersales.tsx # 售后工单列表(管理后台) │ │ ├── AftersalesDetail.tsx # 售后工单详情(管理后台) │ │ ├── AftersalesConfirm.tsx # 售后工单扫码确认(公开) +│ │ ├── Users.tsx # 用户管理(仅管理员) │ │ └── Profile.tsx │ ├── services/ # API 服务层 │ │ └── api.ts @@ -109,6 +110,8 @@ VITE_API_BASE_URL=/api - 技术员创建工单、填写处理结果、提交客户确认 - 管理员可重新分配技术员或强制关闭工单 - 工单状态机:待处理 → 待客户确认 → 已完成 / 已退回 +- 用户管理(仅管理员可见) + - 创建技术员/管理员账号、修改角色、重置密码、删除用户 - 用户资料管理 ## License diff --git a/src/App.tsx b/src/App.tsx index 9bbf5eb..b4ea82a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -10,6 +10,7 @@ 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(); @@ -52,6 +53,7 @@ function App() { } /> } /> } /> + } /> } /> diff --git a/src/components/AdminLayout.tsx b/src/components/AdminLayout.tsx index 5e8751b..2310565 100644 --- a/src/components/AdminLayout.tsx +++ b/src/components/AdminLayout.tsx @@ -8,6 +8,7 @@ import { ExclamationCircleOutlined, IdcardOutlined, ToolOutlined, + UsergroupAddOutlined, } from '@ant-design/icons'; import { authApi } from '@/services/api'; import './styles/AdminLayout.css'; @@ -45,6 +46,16 @@ function AdminLayout() { label: '售后工单', onClick: () => navigate('/admin/aftersales'), }, + ...(user?.role === 'admin' + ? [ + { + key: 'users', + icon: , + label: '用户管理', + onClick: () => navigate('/admin/users'), + }, + ] + : []), ]; const handleLogout = () => { @@ -91,6 +102,7 @@ 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'; }; @@ -101,6 +113,7 @@ 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 '控制台'; }; diff --git a/src/pages/AftersalesDetail.tsx b/src/pages/AftersalesDetail.tsx index f5f13dd..9061740 100644 --- a/src/pages/AftersalesDetail.tsx +++ b/src/pages/AftersalesDetail.tsx @@ -23,12 +23,13 @@ import { StopOutlined, UserSwitchOutlined, } from '@ant-design/icons'; -import { aftersalesApi, authApi } from '@/services/api'; +import { aftersalesApi, authApi, usersApi } from '@/services/api'; import type { AftersalesOrder, AftersalesServiceType, AftersalesWorkOrderStatus, UpdateAftersalesRequest, + User, } from '@/types'; const SERVICE_TYPE_LABEL: Record = { @@ -81,6 +82,7 @@ function AftersalesDetailPage() { const [reassignModalVisible, setReassignModalVisible] = useState(false); const [reassignTechnicianId, setReassignTechnicianId] = useState(); + const [assignableUsers, setAssignableUsers] = useState([]); const loadOrder = async () => { setLoading(true); @@ -107,6 +109,18 @@ function AftersalesDetailPage() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [serialNumber]); + const openReassign = async () => { + setReassignModalVisible(true); + if (assignableUsers.length === 0) { + try { + const users = await usersApi.assignable(); + setAssignableUsers(users); + } catch (err: any) { + message.error(err?.response?.data?.message || err.message || '加载技术员列表失败'); + } + } + }; + const handleSave = async (values: UpdateAftersalesRequest) => { if (!order) return; setSaving(true); @@ -260,7 +274,7 @@ function AftersalesDetailPage() { {isAdmin && !isClosed && ( <> -