deploy-ease-platform/frontend/src/pages/System/User/index.tsx
2024-12-27 21:51:01 +08:00

468 lines
16 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import { Button, Modal, Form, Input, Space, message, Switch, TreeSelect, Select, Tag, Dropdown } from 'antd';
import { PlusOutlined, EditOutlined, DeleteOutlined, KeyOutlined, TeamOutlined, MoreOutlined } from '@ant-design/icons';
import type { MenuProps } from 'antd';
import { useTableData } from '@/hooks/useTableData';
import * as service from './service';
import type { UserResponse, UserRequest, UserQuery, Role } from './types';
import type { DepartmentResponse } from '../Department/types';
import { getDepartmentTree } from '../Department/service';
import {
Table,
TableHeader,
TableBody,
TableHead,
TableRow,
TableCell,
} from "@/components/ui/table";
interface Column {
accessorKey?: keyof UserResponse;
id?: string;
header: string;
size: number;
cell?: (props: { row: { original: UserResponse } }) => React.ReactNode;
}
interface TreeNode {
title: string;
value: number;
children?: TreeNode[];
}
const UserPage: React.FC = () => {
const [form] = Form.useForm();
const [passwordForm] = Form.useForm();
const [modalVisible, setModalVisible] = useState(false);
const [resetPasswordModalVisible, setResetPasswordModalVisible] = useState(false);
const [editingUser, setEditingUser] = useState<UserResponse | null>(null);
const [departments, setDepartments] = useState<DepartmentResponse[]>([]);
const [roleModalVisible, setRoleModalVisible] = useState(false);
const [selectedUser, setSelectedUser] = useState<UserResponse | null>(null);
const [selectedRoles, setSelectedRoles] = useState<number[]>([]);
const [allRoles, setAllRoles] = useState<Role[]>([]);
const {
list,
loading,
pagination,
handleTableChange,
handleCreate,
handleUpdate,
handleDelete,
refresh
} = useTableData<UserResponse, UserQuery, UserRequest, UserRequest>({
service: {
list: service.getUsers,
create: service.createUser,
update: service.updateUser,
delete: service.deleteUser
},
defaultParams: {
sortField: 'createTime',
sortOrder: 'desc'
},
config: {
message: {
createSuccess: '创建用户成功',
updateSuccess: '更新用户成功',
deleteSuccess: '删除用户成功'
}
}
});
useEffect(() => {
service.getAllRoles().then(roles => setAllRoles(roles));
loadDepartmentTree();
}, []);
const loadDepartmentTree = async () => {
try {
const data = await getDepartmentTree();
setDepartments(data);
} catch (error) {
message.error('加载部门数据失败');
}
};
const getTreeData = (departments: DepartmentResponse[]): TreeNode[] => {
return departments.map(dept => ({
title: dept.name,
value: dept.id,
children: dept.children ? getTreeData(dept.children) : undefined
}));
};
const handleAdd = () => {
setEditingUser(null);
form.resetFields();
form.setFieldsValue({
enabled: true
});
setModalVisible(true);
};
const handleEdit = (record: UserResponse) => {
setEditingUser(record);
form.setFieldsValue({
...record,
password: undefined // 不显示密码
});
setModalVisible(true);
};
const handleResetPassword = (record: UserResponse) => {
setEditingUser(record);
passwordForm.resetFields();
setResetPasswordModalVisible(true);
};
const handleResetPasswordSubmit = async () => {
try {
const values = await passwordForm.validateFields();
if (editingUser) {
await service.resetPassword(editingUser.id, values.password);
message.success('密码重置成功');
setResetPasswordModalVisible(false);
refresh();
}
} catch (error: any) {
message.error(error.message || '密码重置失败');
}
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
if (editingUser) {
await handleUpdate(editingUser.id, values);
} else {
await handleCreate(values);
}
setModalVisible(false);
refresh();
} catch (error: any) {
message.error(error.message || '操作失败');
}
};
const handleAssignRoles = (record: UserResponse) => {
setSelectedUser(record);
setSelectedRoles(record.roles?.map(role => role.id) || []);
setRoleModalVisible(true);
};
const handleAssignRoleSubmit = async () => {
if (selectedUser) {
try {
await service.assignRoles(selectedUser.id, selectedRoles);
message.success('角色分配成功');
setRoleModalVisible(false);
refresh();
} catch (error) {
message.error('角色分配失败');
}
}
};
const columns: Column[] = [
{
accessorKey: 'id',
header: 'ID',
size: 60,
},
{
accessorKey: 'username',
header: '用户名',
size: 100,
},
{
accessorKey: 'nickname',
header: '昵称',
size: 100,
},
{
accessorKey: 'email',
header: '邮箱',
size: 200,
},
{
accessorKey: 'departmentName',
header: '部门',
size: 150,
},
{
accessorKey: 'enabled',
header: '状态',
size: 100,
cell: ({ row }) => (
<Tag color={row.original.enabled ? 'success' : 'error'}>
{row.original.enabled ? '启用' : '禁用'}
</Tag>
),
},
{
accessorKey: 'phone',
header: '手机号',
size: 120,
},
{
header: '角色',
size: 120,
cell: ({ row }) => row.original.roles?.map(role => role.name).join(', ') || '-',
},
{
accessorKey: 'createTime',
header: '创建时间',
size: 150,
},
{
accessorKey: 'updateTime',
header: '更新时间',
size: 150,
},
{
id: 'actions',
header: '操作',
size: 180,
cell: ({ row }) => {
const record = row.original;
const items: MenuProps['items'] = [
{
key: 'resetPassword',
icon: <KeyOutlined />,
label: '重置密码',
onClick: () => handleResetPassword(record)
},
{
key: 'assignRoles',
icon: <TeamOutlined />,
label: '分配角色',
onClick: () => handleAssignRoles(record),
disabled: record.username === 'admin'
}
];
if (record.username !== 'admin') {
items.push({
key: 'delete',
icon: <DeleteOutlined />,
label: '删除',
danger: true,
onClick: () => handleDelete(record.id)
});
}
return (
<Space>
<Button
type="link"
icon={<EditOutlined />}
onClick={() => handleEdit(record)}
>
</Button>
<Dropdown menu={{ items }} trigger={['click']}>
<Button type="link" icon={<MoreOutlined />} />
</Dropdown>
</Space>
);
},
},
];
return (
<div className="space-y-4">
<div className="flex justify-between items-center">
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleAdd}
>
</Button>
</div>
<div className="rounded-md border bg-white">
<Table>
<TableHeader>
<TableRow>
{columns.map((column) => (
<TableHead
key={column.accessorKey || column.id}
style={{ width: column.size }}
>
{column.header}
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{list.map((row) => (
<TableRow key={row.id}>
{columns.map((column) => (
<TableCell key={column.accessorKey || column.id}>
{column.cell
? column.cell({ row: { original: row } })
: column.accessorKey
? String(row[column.accessorKey])
: null}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
<Modal
title={editingUser ? '编辑用户' : '新增用户'}
open={modalVisible}
onOk={handleSubmit}
onCancel={() => setModalVisible(false)}
width={600}
>
<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 checkedChildren="启用" unCheckedChildren="禁用" />
</Form.Item>
</Form>
</Modal>
<Modal
title="重置密码"
open={resetPasswordModalVisible}
onOk={handleResetPasswordSubmit}
onCancel={() => setResetPasswordModalVisible(false)}
>
<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}
>
<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;