Add user management page and technician picker for reassign

- New /admin/users page (admin only) for creating technicians,
  editing role/email, resetting passwords, deleting users
- AftersalesDetail reassign modal now uses a searchable Select
  populated from /api/users/assignable instead of raw user ID input
- Menu entry only shown to admins

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Frudrax Cheng
2026-05-26 10:58:02 +08:00
parent 6fef517556
commit eab66bc3e9
8 changed files with 564 additions and 14 deletions
+55
View File
@@ -10,6 +10,10 @@ import type {
CreateAftersalesRequest,
UpdateAftersalesRequest,
CustomerConfirmRequest,
CreateUserRequest,
UpdateUserRequest,
UserListFilter,
UserListResponse,
} from '@/types';
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api';
@@ -433,4 +437,55 @@ export const aftersalesApi = {
}
throw new Error(response.data.error || '提交确认失败');
},
};
export const usersApi = {
list: async (filter?: UserListFilter) => {
const params = new URLSearchParams();
if (filter?.page && filter.page > 1) params.append('page', String(filter.page));
if (filter?.limit && filter.limit !== 20) params.append('limit', String(filter.limit));
if (filter?.role) params.append('role', filter.role);
if (filter?.search) params.append('search', filter.search);
const url = params.toString() ? `/users?${params.toString()}` : '/users';
const response = await apiClient.get(url);
return response.data as UserListResponse;
},
create: async (data: CreateUserRequest) => {
const response = await apiClient.post('/users', data);
if (response.data.user) {
return response.data.user as User;
}
throw new Error(response.data.error || '创建用户失败');
},
update: async (id: number, data: UpdateUserRequest) => {
const response = await apiClient.patch(`/users/${id}`, data);
if (response.data.user) {
return response.data.user as User;
}
throw new Error(response.data.error || '更新用户失败');
},
resetPassword: async (id: number, newPassword: string) => {
const response = await apiClient.post(`/users/${id}/reset-password`, { newPassword });
if (response.data.message) {
return true;
}
throw new Error(response.data.error || '重置密码失败');
},
delete: async (id: number) => {
const response = await apiClient.delete(`/users/${id}`);
if (response.data.message) {
return true;
}
throw new Error(response.data.error || '删除用户失败');
},
assignable: async () => {
const response = await apiClient.get('/users/assignable');
return (response.data?.data || []) as User[];
},
};