feat: 实现管理员用户管理页面

This commit is contained in:
BenjaminNH 2025-07-02 17:06:52 +08:00
parent c9d0bb88b3
commit 00085874c9
7 changed files with 322 additions and 12 deletions

View File

@ -3,6 +3,7 @@ import {
ExperimentOutlined,
FileDoneOutlined,
UnorderedListOutlined,
UsergroupAddOutlined,
UserOutlined,
} from "@ant-design/icons";
@ -38,11 +39,17 @@ const menuConfig = [
icon: ExperimentOutlined,
roles: ["DEVICE_ADMIN"],
},
{
path: "/admin/user-manage",
label: "用户管理",
icon: UsergroupAddOutlined,
roles: ["ADMIN"],
},
{
path: "/userdetail",
label: "个人信息",
icon: UserOutlined,
roles: ["USER", "LEADER", "DEVICE_ADMIN", "ADMIN"],
roles: ["USER", "LEADER", "DEVICE_ADMIN"],
},
];

View File

@ -0,0 +1,132 @@
import { Form, Input, message, Modal, Select } from "antd";
import { useEffect, useState } from "react";
import axiosInstance from "../../api/axios";
import Password from "antd/es/input/Password";
export default function UserDetailModal({
visiable,
mode = "create",
user,
onclose,
onSuccess,
}) {
const [form] = Form.useForm();
const [initialValues, setInitialValues] = useState();
const [teams, setTeams] = useState([]);
const [roles, setRoles] = useState([]);
const fetchTeams = async () => {
const data = await axiosInstance.get("/teams");
setTeams(data);
};
const fetchRoles = async () => {
const data = await axiosInstance.get("/role");
setRoles(data);
};
useEffect(() => {
fetchTeams();
fetchRoles();
}, []);
useEffect(() => {
if (visiable) {
if (mode === "edit") {
const values = {
username: user.username,
name: user.name,
phone: user.phone,
teamId: user.teamId,
roleId: user.roleId,
};
setInitialValues(values);
form.setFieldsValue(values);
} else {
const values = {
username: undefined,
password: undefined,
name: undefined,
phone: undefined,
teamId: undefined,
roleId: undefined,
};
setInitialValues(values);
form.setFieldsValue(values);
}
}
}, [visiable, mode, user, form]);
const handleOk = async () => {
const values = await form.validateFields();
const data = {};
Object.keys(initialValues).forEach((key) => {
if (values[key] !== initialValues[key]) {
data[key] = values[key];
}
});
if (Object.keys(data).length > 0) {
if (mode === "edit") {
await axiosInstance.put(`/user/${user.userId}`, data);
message.success("编辑成功");
} else {
await axiosInstance.post(`/user`, data);
message.success("添加成功");
}
}
onSuccess();
onclose();
};
return (
<Modal
title={mode === "edit" ? "编辑用户" : "添加用户"}
open={visiable}
onCancel={() => {
onclose();
}}
onOk={handleOk}
okText="保存"
>
<Form form={form} layout="vertical" initialValues={initialValues}>
<Form.Item
name="username"
label="用户账号"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
{mode === "create" && (
<Form.Item
name="password"
label="初始密码"
rules={[{ required: true }]}
>
<Input.Password />
</Form.Item>
)}
<Form.Item name="name" label="姓名" rules={[{ required: true }]}>
<Input />
</Form.Item>
<Form.Item name="phone" label="联系电话">
<Input />
</Form.Item>
<Form.Item
name="teamId"
label="所属团队"
rules={[{ required: true, message: "请选择所属团队" }]}
>
<Select options={teams} />
</Form.Item>
<Form.Item
name="roleId"
label="用户角色"
rules={[{ required: true, message: "请选择用户角色" }]}
>
<Select options={roles} />
</Form.Item>
</Form>
</Modal>
);
}

View File

@ -0,0 +1,147 @@
import { Button, Flex, Input, Popconfirm, Space, Table } from "antd";
import Column from "antd/es/table/Column";
import { useEffect, useState } from "react";
import axiosInstance from "../../api/axios";
import DeviceDetailModal from "../deviceAdmin/DeviceDetailModal";
import UserDetailModal from "./UserDetailModal";
export default function UserManage() {
const [users, setUsers] = useState([]);
const [teams, setTeams] = useState([]);
const [modalMode, setModalMode] = useState();
const [selectedUser, setSelectedUser] = useState();
const [modalOpen, setModalOpen] = useState(false);
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
total: 0,
});
const fetchData = async (pagination, name) => {
const data = await axiosInstance.get("/user", {
params: {
page: pagination.current,
size: pagination.pageSize,
name: name,
},
});
setUsers(data.records);
setPagination({
...pagination,
total: data.total,
});
};
const fetchTeams = async () => {
const data = await axiosInstance.get("/teams");
setTeams(data);
};
useEffect(() => {
// fetchTeams();
fetchData({
current: 1,
pageSize: 10,
});
}, []);
const handlePageChange = async (pagination) => {
await fetchData(pagination);
};
const handleSearch = async (value) => {
await fetchData(
{
...pagination,
current: 1,
},
value
);
};
const handleDelete = async (record) => {
await axiosInstance.delete(`/user/${record.userId}`);
fetchData({
current: 1,
pageSize: 10,
});
};
//
return (
<>
<Flex justify="space-between">
<Input.Search
placeholder="请输入用户姓名"
onSearch={handleSearch}
style={{ width: "200px" }}
className="m-4"
/>
<Button
className="m-4"
type="primary"
onClick={() => {
setModalMode("create");
setSelectedUser(null);
setModalOpen(true);
}}
>
添加用户
</Button>
</Flex>
<Table
rowKey="userId"
dataSource={users}
pagination={pagination}
onChange={handlePageChange}
>
<Column title="姓名" key="name" dataIndex="name" />
<Column title="账号" key="username" dataIndex="username" />
<Column title="联系电话" key="phone" dataIndex="phone" />
<Column title="所属团队" key="team" dataIndex="team" />
<Column
title="操作"
render={(_, record) => {
return (
<Space>
<Button
size="small"
onClick={() => {
setSelectedUser(record);
setModalOpen(true);
setModalMode("edit");
}}
>
编辑
</Button>
<Popconfirm
title="删除用户"
description="确认要删除该用户吗?"
onConfirm={() => handleDelete(record)}
>
<Button danger size="small">
删除
</Button>
</Popconfirm>
</Space>
);
}}
/>
</Table>
<UserDetailModal
visiable={modalOpen}
mode={modalMode}
user={selectedUser}
onclose={() => {
setModalOpen(false);
setSelectedUser(null);
}}
onSuccess={async () => {
await fetchData({
...pagination,
current: 1,
});
}}
/>
</>
);
}

View File

@ -68,7 +68,6 @@ export default function DeviceDetailModal({
message.success("编辑成功");
} else {
device = await axiosInstance.post(`/device/${userId}`, data);
console.log(device);
message.success("添加成功");
}

View File

@ -1,4 +1,13 @@
import { Button, Flex, Input, message, Space, Table, Tag } from "antd";
import {
Button,
Flex,
Input,
message,
Popconfirm,
Space,
Table,
Tag,
} from "antd";
import Column from "antd/es/table/Column";
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
@ -132,14 +141,15 @@ export default function DeviceManage() {
>
编辑
</Button>
<Button
color="red"
variant="solid"
size="small"
onClick={() => handleDelete(record.deviceId)}
<Popconfirm
title="删除设备"
description="确认要删除该设备吗?"
onConfirm={() => handleDelete(record.deviceId)}
>
删除
</Button>
<Button color="red" variant="solid" size="small">
删除
</Button>
</Popconfirm>
</Space>
);
}}
@ -154,7 +164,10 @@ export default function DeviceManage() {
setSelectedDevice(null);
}}
onSuccess={async () => {
await fetchData(pagination);
await fetchData({
...pagination,
current: 1,
});
}}
/>
</>

View File

@ -9,6 +9,7 @@ export default function UserDetail() {
const [form] = Form.useForm();
const [showPassword, setShowPassword] = useState(false);
const [user, setUser] = useState({
userId: "",
username: "",
team: "",
name: "",
@ -43,7 +44,7 @@ export default function UserDetail() {
}
delete changedFields.confirmPassword;
const newUser = await axiosInstance.patch(`/user/${userId}`, changedFields);
const newUser = await axiosInstance.put(`/user/${userId}`, changedFields);
setUser(newUser);
form.setFieldsValue(newUser);
message.success("修改成功");

View File

@ -8,6 +8,7 @@ import MyReservation from "../pages/user/MyReservation";
import Reserve from "../pages/user/Reserve";
import ProtectedRoute from "./ProtectedRoute";
import DeviceManage from "../pages/deviceAdmin/DeviceManage";
import UserManage from "../pages/admin/UserManage";
const router = createBrowserRouter([
{
@ -59,6 +60,16 @@ const router = createBrowserRouter([
},
],
},
{
path: "admin",
element: <ProtectedRoute allowedRoles={["ADMIN"]} />,
children: [
{
path: "user-manage",
element: <UserManage />,
},
],
},
{
path: "userdetail",
element: <UserDetail />,