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:
@@ -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<AftersalesServiceType, string> = {
|
||||
@@ -81,6 +82,7 @@ function AftersalesDetailPage() {
|
||||
|
||||
const [reassignModalVisible, setReassignModalVisible] = useState(false);
|
||||
const [reassignTechnicianId, setReassignTechnicianId] = useState<number | undefined>();
|
||||
const [assignableUsers, setAssignableUsers] = useState<User[]>([]);
|
||||
|
||||
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() {
|
||||
</Button>
|
||||
{isAdmin && !isClosed && (
|
||||
<>
|
||||
<Button icon={<UserSwitchOutlined />} onClick={() => setReassignModalVisible(true)}>
|
||||
<Button icon={<UserSwitchOutlined />} onClick={openReassign}>
|
||||
重新分配
|
||||
</Button>
|
||||
<Button danger icon={<StopOutlined />} onClick={handleForceClose}>
|
||||
@@ -424,25 +438,28 @@ function AftersalesDetailPage() {
|
||||
<Modal
|
||||
title="重新分配技术员"
|
||||
open={reassignModalVisible}
|
||||
onCancel={() => setReassignModalVisible(false)}
|
||||
onCancel={() => {
|
||||
setReassignModalVisible(false);
|
||||
setReassignTechnicianId(undefined);
|
||||
}}
|
||||
onOk={handleReassign}
|
||||
okText="确认"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="新技术员 ID" required>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="请输入技术员的用户 ID"
|
||||
<Form.Item label="选择技术员" required>
|
||||
<Select
|
||||
placeholder="请选择技术员或管理员"
|
||||
value={reassignTechnicianId}
|
||||
onChange={(e) =>
|
||||
setReassignTechnicianId(e.target.value ? Number(e.target.value) : undefined)
|
||||
}
|
||||
onChange={(v) => setReassignTechnicianId(v)}
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
options={assignableUsers.map((u) => ({
|
||||
value: u.id,
|
||||
label: `${u.name}(${u.username})${u.role === 'admin' ? ' · 管理员' : ' · 技术员'}`,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<p style={{ color: '#888', fontSize: 12 }}>
|
||||
后续会在用户管理页提供技术员列表选择,目前先输入 ID。
|
||||
</p>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user