476 lines
16 KiB
TypeScript
476 lines
16 KiB
TypeScript
import React, {useEffect, useState} from 'react';
|
|
import {Table, Button, Modal, Form, Input, Space, message, Switch, TreeSelect, Select, Tag} from 'antd';
|
|
import {PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, TeamOutlined} from '@ant-design/icons';
|
|
import type {UserResponse, Role} from './types';
|
|
import type {DepartmentDTO} from '../Department/types';
|
|
import {resetPassword, assignRoles, getAllRoles} from './service';
|
|
import {useTableData} from '@/hooks/useTableData';
|
|
import type {FixedType, AlignType, SortOrder} from 'rc-table/lib/interface';
|
|
import {Response} from "@/utils/request.ts";
|
|
import {TablePaginationConfig} from "antd/es/table";
|
|
import {FilterValue, SorterResult} from "antd/es/table/interface";
|
|
import { getDepartmentTree } from '../Department/service'; // 导入部门树接口
|
|
|
|
interface TreeNode {
|
|
title: string;
|
|
value: number;
|
|
children?: TreeNode[];
|
|
}
|
|
|
|
const UserPage: React.FC = () => {
|
|
const {
|
|
list: users,
|
|
pagination,
|
|
loading,
|
|
loadData: fetchUsers,
|
|
handleCreate,
|
|
handleUpdate,
|
|
handleDelete
|
|
} = useTableData({
|
|
service: {
|
|
baseUrl: '/api/v1/user'
|
|
},
|
|
defaultParams: {
|
|
sortField: 'createTime',
|
|
sortOrder: 'descend'
|
|
}
|
|
});
|
|
|
|
const [departments, setDepartments] = useState<DepartmentDTO[]>([]);
|
|
const [modalVisible, setModalVisible] = useState(false);
|
|
const [resetPasswordModalVisible, setResetPasswordModalVisible] = useState(false);
|
|
const [editingUser, setEditingUser] = useState<UserResponse | null>(null);
|
|
const [form] = Form.useForm();
|
|
const [passwordForm] = Form.useForm();
|
|
const [roleModalVisible, setRoleModalVisible] = useState(false);
|
|
const [selectedUser, setSelectedUser] = useState<UserResponse | null>(null);
|
|
const [selectedRoles, setSelectedRoles] = useState<number[]>([]);
|
|
const [allRoles, setAllRoles] = useState<RoleResponse[]>([]);
|
|
|
|
useEffect(() => {
|
|
getAllRoles().then(roles => setAllRoles(roles));
|
|
}, []);
|
|
|
|
const handleAdd = () => {
|
|
setEditingUser(null);
|
|
form.resetFields();
|
|
form.setFieldsValue({
|
|
enabled: true
|
|
});
|
|
setModalVisible(true);
|
|
};
|
|
|
|
const handleEdit = (record: UserResponse) => {
|
|
setEditingUser(record);
|
|
form.setFieldsValue({
|
|
...record
|
|
});
|
|
setModalVisible(true);
|
|
};
|
|
|
|
const handleResetPassword = (record: UserResponse) => {
|
|
setEditingUser(record);
|
|
passwordForm.resetFields();
|
|
setResetPasswordModalVisible(true);
|
|
};
|
|
|
|
const handleResetPasswordSubmit = async () => {
|
|
try {
|
|
const values = await passwordForm.validateFields();
|
|
if (editingUser) {
|
|
await resetPassword(editingUser.id, values.password);
|
|
message.success('密码重置成功');
|
|
setResetPasswordModalVisible(false);
|
|
}
|
|
} catch (error: any) {
|
|
message.error(error.message || '密码重置失败');
|
|
}
|
|
};
|
|
|
|
const handleSubmit = async (values: any) => {
|
|
try {
|
|
if (editingUser) {
|
|
const success = await handleUpdate(editingUser.id, {
|
|
...values,
|
|
enabled: values.enabled ?? true
|
|
});
|
|
if (success) {
|
|
message.success('更新成功');
|
|
setModalVisible(false);
|
|
fetchUsers();
|
|
}
|
|
} else {
|
|
const success = await handleCreate({
|
|
...values,
|
|
enabled: values.enabled ?? true
|
|
});
|
|
if (success) {
|
|
message.success('创建成功');
|
|
setModalVisible(false);
|
|
fetchUsers();
|
|
}
|
|
}
|
|
} catch (error: any) {
|
|
message.error(error.message || '操作失败');
|
|
}
|
|
};
|
|
|
|
const loadDepartmentTree = async () => {
|
|
try {
|
|
const data = await getDepartmentTree();
|
|
setDepartments(data);
|
|
} catch (error) {
|
|
message.error('加载部门数据失败');
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadDepartmentTree();
|
|
}, []);
|
|
|
|
const getTreeData = (departments: DepartmentDTO[]): TreeNode[] => {
|
|
return departments.map(dept => ({
|
|
title: dept.name,
|
|
value: dept.id,
|
|
children: dept.children ? getTreeData(dept.children) : undefined
|
|
}));
|
|
};
|
|
|
|
const handleAssignRoles = (record: UserResponse) => {
|
|
setSelectedUser(record);
|
|
setSelectedRoles(record.roles?.map(role => role.id) || []);
|
|
setRoleModalVisible(true);
|
|
};
|
|
|
|
const handleAssignRoleSubmit = async () => {
|
|
if (selectedUser) {
|
|
try {
|
|
await assignRoles(selectedUser.id, selectedRoles);
|
|
message.success("角色分配成功")
|
|
setRoleModalVisible(false);
|
|
fetchUsers();
|
|
} catch (error) {
|
|
message.error("角色分配失败")
|
|
console.log(error);
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
title: 'ID',
|
|
dataIndex: 'id',
|
|
key: 'id',
|
|
width: 60,
|
|
fixed: 'left' as FixedType,
|
|
sorter: true
|
|
},
|
|
{
|
|
title: '用户名',
|
|
dataIndex: 'username',
|
|
key: 'username',
|
|
width: 100,
|
|
sorter: true
|
|
},
|
|
{
|
|
title: '昵称',
|
|
dataIndex: 'nickname',
|
|
key: 'nickname',
|
|
width: 100,
|
|
},
|
|
{
|
|
title: '邮箱',
|
|
dataIndex: 'email',
|
|
key: 'email',
|
|
width: 200,
|
|
},
|
|
{
|
|
title: '部门',
|
|
dataIndex: 'departmentName',
|
|
key: 'departmentName',
|
|
width: 150,
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'enabled',
|
|
key: 'enabled',
|
|
width: 100,
|
|
render: (enabled: boolean) => (
|
|
<Tag color={enabled ? 'success' : 'error'}>
|
|
{enabled ? '启用' : '禁用'}
|
|
</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '手机号',
|
|
dataIndex: 'phone',
|
|
key: 'phone',
|
|
width: 120,
|
|
},
|
|
{
|
|
title: '角色',
|
|
dataIndex: 'roles',
|
|
key: 'roles',
|
|
width: 120,
|
|
ellipsis: true,
|
|
render: (roles: Role[]) => roles?.map(role => role.name).join(', ') || '-'
|
|
},
|
|
{
|
|
title: '创建时间',
|
|
dataIndex: 'createTime',
|
|
key: 'createTime',
|
|
width: 150,
|
|
sorter: true,
|
|
defaultSortOrder: 'descend' as SortOrder
|
|
},
|
|
{
|
|
title: '更新时间',
|
|
dataIndex: 'updateTime',
|
|
key: 'updateTime',
|
|
width: 150,
|
|
sorter: true
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
width: 320,
|
|
fixed: 'right' as FixedType,
|
|
render: (_: any, record: UserResponse) => (
|
|
<Space size={0}>
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
icon={<EditOutlined/>}
|
|
onClick={() => handleEdit(record)}
|
|
>
|
|
编辑
|
|
</Button>
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
icon={<KeyOutlined/>}
|
|
onClick={() => handleResetPassword(record)}
|
|
>
|
|
重置密码
|
|
</Button>
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
danger
|
|
icon={<DeleteOutlined/>}
|
|
onClick={() => handleDelete(record.id)}
|
|
disabled={record.username === 'admin'}
|
|
>
|
|
删除
|
|
</Button>
|
|
<Button
|
|
type="link"
|
|
size="small"
|
|
icon={<TeamOutlined/>}
|
|
onClick={() => handleAssignRoles(record)}
|
|
disabled={record.username === 'admin'}
|
|
>
|
|
分配角色
|
|
</Button>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
const handleTableChange = (
|
|
pagination: TablePaginationConfig,
|
|
filters: Record<string, FilterValue | null>,
|
|
sorter: SorterResult<UserResponse> | SorterResult<UserResponse>[]
|
|
) => {
|
|
const {field, order} = Array.isArray(sorter) ? sorter[0] : sorter;
|
|
fetchUsers({
|
|
sortField: field as string,
|
|
sortOrder: order
|
|
});
|
|
};
|
|
|
|
return (
|
|
<div style={{padding: '24px'}}>
|
|
<div style={{marginBottom: 16}}>
|
|
<Button type="primary" icon={<PlusOutlined/>} onClick={handleAdd}>
|
|
新增用户
|
|
</Button>
|
|
</div>
|
|
|
|
<Table
|
|
loading={loading}
|
|
columns={columns}
|
|
dataSource={users}
|
|
rowKey="id"
|
|
scroll={{x: 1500}}
|
|
pagination={pagination}
|
|
onChange={handleTableChange}
|
|
size="middle"
|
|
bordered
|
|
/>
|
|
|
|
<Modal
|
|
title={editingUser ? '编辑用户' : '新增用户'}
|
|
open={modalVisible}
|
|
onOk={() => form.validateFields().then(handleSubmit)}
|
|
onCancel={() => setModalVisible(false)}
|
|
width={600}
|
|
destroyOnClose
|
|
>
|
|
<Form
|
|
form={form}
|
|
layout="vertical"
|
|
>
|
|
<Form.Item
|
|
name="username"
|
|
label="用户名"
|
|
rules={[{required: true, message: '请输入用户名'}]}
|
|
>
|
|
<Input placeholder="请输入用户名" disabled={!!editingUser}/>
|
|
</Form.Item>
|
|
|
|
{!editingUser && (
|
|
<Form.Item
|
|
name="password"
|
|
label="密码"
|
|
rules={[
|
|
{required: true, message: '请输入密码'},
|
|
{min: 6, message: '密码长度不能小于6位'}
|
|
]}
|
|
>
|
|
<Input.Password placeholder="请输入密码"/>
|
|
</Form.Item>
|
|
)}
|
|
|
|
<Form.Item
|
|
name="nickname"
|
|
label="昵称"
|
|
>
|
|
<Input placeholder="请输入昵称"/>
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="email"
|
|
label="邮箱"
|
|
rules={[{type: 'email', message: '请输入正确的邮箱格式'}]}
|
|
>
|
|
<Input placeholder="请输入邮箱"/>
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="phone"
|
|
label="手机号"
|
|
>
|
|
<Input placeholder="请输入手机号"/>
|
|
</Form.Item>
|
|
<Form.Item name="departmentId" label="所属部门">
|
|
<TreeSelect
|
|
treeData={getTreeData(departments)}
|
|
placeholder="请选择所属部门"
|
|
allowClear
|
|
treeDefaultExpandAll
|
|
showSearch
|
|
treeNodeFilterProp="title"
|
|
/>
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="enabled"
|
|
label="状态"
|
|
valuePropName="checked"
|
|
initialValue={true}
|
|
>
|
|
<Switch/>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
|
|
{/* 注释掉RoleModal相关代码
|
|
{selectedUser && (
|
|
<RoleModal
|
|
userId={selectedUser.id}
|
|
visible={roleModalVisible}
|
|
onCancel={() => {
|
|
setRoleModalVisible(false);
|
|
setSelectedUser(null);
|
|
}}
|
|
onSuccess={() => {
|
|
setRoleModalVisible(false);
|
|
setSelectedUser(null);
|
|
fetchUsers();
|
|
}}
|
|
/>
|
|
)}
|
|
*/}
|
|
|
|
<Modal
|
|
title="重置密码"
|
|
open={resetPasswordModalVisible}
|
|
onOk={handleResetPasswordSubmit}
|
|
onCancel={() => setResetPasswordModalVisible(false)}
|
|
destroyOnClose
|
|
>
|
|
<Form
|
|
form={passwordForm}
|
|
layout="vertical"
|
|
>
|
|
<Form.Item
|
|
name="password"
|
|
label="新密码"
|
|
rules={[
|
|
{required: true, message: '请输入新密码'},
|
|
{min: 6, message: '密码长度不能小于6位'}
|
|
]}
|
|
>
|
|
<Input.Password placeholder="请输入新密码"/>
|
|
</Form.Item>
|
|
<Form.Item
|
|
name="confirmPassword"
|
|
label="确认密码"
|
|
dependencies={['password']}
|
|
rules={[
|
|
{required: true, message: '请确认新密码'},
|
|
({getFieldValue}) => ({
|
|
validator(_, value) {
|
|
if (!value || getFieldValue('password') === value) {
|
|
return Promise.resolve();
|
|
}
|
|
return Promise.reject(new Error('两次输入的密码不一致'));
|
|
},
|
|
}),
|
|
]}
|
|
>
|
|
<Input.Password placeholder="请确认新密码"/>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
|
|
<Modal
|
|
title="分配角色"
|
|
open={roleModalVisible}
|
|
onOk={handleAssignRoleSubmit}
|
|
onCancel={() => setRoleModalVisible(false)}
|
|
width={600}
|
|
destroyOnClose
|
|
>
|
|
<Form layout="vertical">
|
|
<Form.Item label="选择角色">
|
|
<Select
|
|
mode="multiple"
|
|
placeholder="请选择角色"
|
|
value={selectedRoles}
|
|
onChange={setSelectedRoles}
|
|
style={{width: '100%'}}
|
|
>
|
|
{allRoles.map(role => (
|
|
<Select.Option key={role.id} value={role.id}>
|
|
{role.name}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default UserPage;
|