feat: 实现团队管理页面

This commit is contained in:
BenjaminNH 2025-07-04 13:16:36 +08:00
parent f155518ba4
commit 666b8c16c2
6 changed files with 186 additions and 2 deletions

View File

@ -0,0 +1,34 @@
import { useState } from "react";
import { Popconfirm, Button, message } from "antd";
export default function TeamDeleteButton({ record, onConfirm }) {
const [visiable, setVisiable] = useState(false);
const handleClick = (record) => {
if (record.size > 0) {
message.warning("该团队下还有成员,无法删除");
} else {
setVisiable(true);
}
};
const handleConfirm = () => {
onConfirm();
setVisiable(false);
};
return (
<Popconfirm
title="删除团队"
description="确认要删除该团队吗?"
open={visiable}
onConfirm={handleConfirm}
onCancel={() => {
setVisiable(false);
}}
>
<Button danger size="small" onClick={() => handleClick(record)}>
删除
</Button>
</Popconfirm>
);
}

View File

@ -1,4 +1,5 @@
import { import {
ApartmentOutlined,
DesktopOutlined, DesktopOutlined,
ExperimentOutlined, ExperimentOutlined,
FileDoneOutlined, FileDoneOutlined,
@ -61,6 +62,12 @@ const menuConfig = [
], ],
roles: ["ADMIN"], roles: ["ADMIN"],
}, },
{
path: "/admin/team-manage",
icon: ApartmentOutlined,
label: "团队管理",
roles: ["ADMIN"],
},
{ {
path: "/userdetail", path: "/userdetail",
label: "个人信息", label: "个人信息",

View File

@ -0,0 +1,138 @@
import { Button, Flex, Input, Space, Table, message } from "antd";
import Column from "antd/es/table/Column";
import { useEffect, useState } from "react";
import axiosInstance from "../../api/axios";
import TeamDeleteButton from "../../components/TeamDeleteButton";
export default function TeamManage() {
const [teams, setTeams] = useState([]);
const [data, setData] = useState([]);
const [searchName, setSearchName] = useState();
const [editingId, setEditingId] = useState();
const [editingName, setEditingName] = useState("");
const [newTeamName, setNewTeamName] = useState();
const fetchData = async () => {
const data = await axiosInstance.get("/teams");
setData(data);
setTeams(data);
setSearchName(null);
};
useEffect(() => {
fetchData();
}, []);
const handleSearch = (value) => {
setSearchName(value);
const filtered = data.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase())
);
setTeams(filtered);
};
const handleDelete = async (record) => {
await axiosInstance.delete(`/team/${record.id}`);
message.success("删除成功");
fetchData();
};
const handleEdit = (record) => {
setEditingId(record.id);
setEditingName(record.name);
};
const handleSave = async (teamId) => {
if (!editingName.trim()) return message.warning("请输入新名称");
await axiosInstance.put(`/team/${teamId}`, { name: editingName });
setEditingId(null);
message.success("修改成功");
await fetchData();
};
const handleAdd = async () => {
if (!newTeamName.trim()) {
message.warning("请输入团队名称");
return;
}
await axiosInstance.post("/team", {
name: newTeamName,
});
message.success("添加成功");
setNewTeamName(null);
fetchData();
};
return (
<>
<Flex justify="space-between">
<Input.Search
placeholder="请输入团队名称"
onSearch={handleSearch}
onChange={(e) => setSearchName(e.target.value)}
value={searchName}
style={{ width: "200px" }}
className="m-4"
/>
<Input.Search
placeholder="请输入新团队名称"
enterButton="添加团队"
onSearch={handleAdd}
value={newTeamName}
onChange={(e) => setNewTeamName(e.target.value)}
className="m-4"
style={{ width: "300px" }}
/>
</Flex>
<Table rowKey="id" dataSource={teams}>
<Column
title="团队名"
key="name"
render={(_, record) => {
if (record.id === editingId) {
return (
<Input
value={editingName}
onChange={(e) => setEditingName(e.target.value)}
/>
);
}
return record.name;
}}
/>
<Column title="下属人数" key="size" dataIndex="size" />
<Column
title="操作"
render={(_, record) => {
return (
<Space>
{record.id === editingId ? (
<Space>
<Button
size="small"
type="primary"
onClick={() => handleSave(record.id)}
>
保存
</Button>
<Button size="small" onClick={() => setEditingId(null)}>
取消
</Button>
</Space>
) : (
<Button size="small" onClick={() => handleEdit(record)}>
编辑
</Button>
)}
<TeamDeleteButton
record={record}
onConfirm={() => handleDelete(record)}
/>
</Space>
);
}}
/>
</Table>
</>
);
}

View File

@ -16,7 +16,7 @@ export default function UserDetailModal({
const [teams, setTeams] = useState([]); const [teams, setTeams] = useState([]);
const fetchTeams = async () => { const fetchTeams = async () => {
const data = await axiosInstance.get("/teams"); const data = await axiosInstance.get("/team-label");
setTeams(data); setTeams(data);
}; };

View File

@ -24,7 +24,7 @@ export default function DeviceDetailModal({ visiable, device, onclose }) {
const [initialValues, setInitialValues] = useState(); const [initialValues, setInitialValues] = useState();
const fetchTeams = async () => { const fetchTeams = async () => {
const data = await axiosInstance.get("/teams"); const data = await axiosInstance.get("/team-label");
const teams = data.map((item) => ({ const teams = data.map((item) => ({
label: item.label, label: item.label,
value: item.label, value: item.label,

View File

@ -11,6 +11,7 @@ import DeviceManage from "../pages/deviceAdmin/DeviceManage";
import UserManage from "../pages/admin/UserManage"; import UserManage from "../pages/admin/UserManage";
import DeviceStats from "../pages/admin/DeviceStats"; import DeviceStats from "../pages/admin/DeviceStats";
import ReservationStats from "../pages/admin/ReservationStats"; import ReservationStats from "../pages/admin/ReservationStats";
import TeamManage from "../pages/admin/TeamManage";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
@ -78,6 +79,10 @@ const router = createBrowserRouter([
path: "stats-reservation", path: "stats-reservation",
element: <ReservationStats />, element: <ReservationStats />,
}, },
{
path: "team-manage",
element: <TeamManage />,
},
], ],
}, },
{ {