Compare commits
2 Commits
c9d0bb88b3
...
8a5f231ae2
Author | SHA1 | Date | |
---|---|---|---|
8a5f231ae2 | |||
00085874c9 |
@ -22,6 +22,11 @@ axiosInstance.interceptors.request.use(
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
(response) => {
|
||||
const config = response.config;
|
||||
// 如果设置了跳过,直接返回原始 response
|
||||
if (config.skipInterceptor) {
|
||||
return response;
|
||||
}
|
||||
const { code, message: msg, data } = response.data;
|
||||
|
||||
if (code === 0) {
|
||||
|
40
src/config/datePresetsConfig.js
Normal file
40
src/config/datePresetsConfig.js
Normal file
@ -0,0 +1,40 @@
|
||||
import dayjs from "dayjs";
|
||||
import quarterOfYear from "dayjs/plugin/quarterOfYear";
|
||||
|
||||
dayjs.extend(quarterOfYear);
|
||||
|
||||
export const datePresets = [
|
||||
{
|
||||
label: "本月",
|
||||
value: [dayjs().startOf("month"), dayjs().endOf("month")],
|
||||
},
|
||||
{
|
||||
label: "上月",
|
||||
value: [
|
||||
dayjs().subtract(1, "month").startOf("month"),
|
||||
dayjs().subtract(1, "month").endOf("month"),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "本季度",
|
||||
value: [dayjs().startOf("quarter"), dayjs().endOf("quarter")],
|
||||
},
|
||||
{
|
||||
label: "上季度",
|
||||
value: [
|
||||
dayjs().subtract(1, "quarter").startOf("quarter"),
|
||||
dayjs().subtract(1, "quarter").endOf("quarter"),
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "本年",
|
||||
value: [dayjs().startOf("year"), dayjs().endOf("year")],
|
||||
},
|
||||
{
|
||||
label: "去年",
|
||||
value: [
|
||||
dayjs().subtract(1, "year").startOf("year"),
|
||||
dayjs().subtract(1, "year").endOf("year"),
|
||||
],
|
||||
},
|
||||
];
|
@ -2,7 +2,9 @@ import {
|
||||
DesktopOutlined,
|
||||
ExperimentOutlined,
|
||||
FileDoneOutlined,
|
||||
PieChartOutlined,
|
||||
UnorderedListOutlined,
|
||||
UsergroupAddOutlined,
|
||||
UserOutlined,
|
||||
} from "@ant-design/icons";
|
||||
|
||||
@ -38,11 +40,32 @@ const menuConfig = [
|
||||
icon: ExperimentOutlined,
|
||||
roles: ["DEVICE_ADMIN"],
|
||||
},
|
||||
{
|
||||
path: "/admin/user-manage",
|
||||
label: "用户管理",
|
||||
icon: UsergroupAddOutlined,
|
||||
roles: ["ADMIN"],
|
||||
},
|
||||
{
|
||||
label: "数据统计",
|
||||
icon: PieChartOutlined,
|
||||
children: [
|
||||
{
|
||||
path: "/admin/stats-device",
|
||||
label: "设备统计",
|
||||
},
|
||||
{
|
||||
path: "/admin/stats-user",
|
||||
label: "使用人统计",
|
||||
},
|
||||
],
|
||||
roles: ["ADMIN"],
|
||||
},
|
||||
{
|
||||
path: "/userdetail",
|
||||
label: "个人信息",
|
||||
icon: UserOutlined,
|
||||
roles: ["USER", "LEADER", "DEVICE_ADMIN", "ADMIN"],
|
||||
roles: ["USER", "LEADER", "DEVICE_ADMIN"],
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -44,6 +44,24 @@ export default function CommonLayout() {
|
||||
}
|
||||
};
|
||||
|
||||
const buildMenuItems = (menu) =>
|
||||
menu.map((item) => {
|
||||
if (item.children) {
|
||||
return {
|
||||
key: item.label, // 可改为 item.path 但注意不冲突
|
||||
label: item.label,
|
||||
icon: item.icon ? React.createElement(item.icon) : null,
|
||||
children: buildMenuItems(item.children),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
key: item.path,
|
||||
label: item.label,
|
||||
icon: item.icon ? React.createElement(item.icon) : null,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Layout style={{ minHeight: "100vh" }}>
|
||||
<Sider
|
||||
@ -56,11 +74,7 @@ export default function CommonLayout() {
|
||||
theme="dark"
|
||||
selectedKeys={[location.pathname]}
|
||||
mode="inline"
|
||||
items={menu.map((item) => ({
|
||||
key: item.path,
|
||||
label: item.label,
|
||||
icon: React.createElement(item.icon),
|
||||
}))}
|
||||
items={buildMenuItems(menu)}
|
||||
onClick={({ key }) => naviagte(key)}
|
||||
/>
|
||||
</Sider>
|
||||
|
140
src/pages/admin/DeviceStats.jsx
Normal file
140
src/pages/admin/DeviceStats.jsx
Normal file
@ -0,0 +1,140 @@
|
||||
import { Button, DatePicker, Input, Space, Spin, Table, message } from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import { useEffect, useState } from "react";
|
||||
import axiosInstance from "../../api/axios";
|
||||
import { datePresets } from "../../config/datePresetsConfig";
|
||||
|
||||
const { RangePicker } = DatePicker;
|
||||
|
||||
export default function DeviceStats() {
|
||||
const [data, setData] = useState([]);
|
||||
const [filteredData, setFilteredData] = useState([]);
|
||||
const [range, setRange] = useState([
|
||||
dayjs().startOf("month"),
|
||||
dayjs().endOf("month"),
|
||||
]);
|
||||
const [search, setSearch] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await axiosInstance.get("/device/usage-stats", {
|
||||
params: {
|
||||
start: range[0].format("YYYY-MM-DD"),
|
||||
end: range[1].format("YYYY-MM-DD"),
|
||||
},
|
||||
});
|
||||
setData(res);
|
||||
setFilteredData(res);
|
||||
} catch (e) {
|
||||
message.error("获取数据失败");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = (value) => {
|
||||
setSearch(value);
|
||||
const filtered = data.filter((item) =>
|
||||
item.deviceName.toLowerCase().includes(value.toLowerCase())
|
||||
);
|
||||
setFilteredData(filtered);
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await axiosInstance.get("/device/usage-stats/export", {
|
||||
params: {
|
||||
start: range[0].format("YYYY-MM-DD"),
|
||||
end: range[1].format("YYYY-MM-DD"),
|
||||
},
|
||||
responseType: "blob", // 二进制流
|
||||
skipInterceptor: true,
|
||||
});
|
||||
|
||||
// 从响应头获取文件名,兼容后端返回
|
||||
const disposition = response.headers["content-disposition"];
|
||||
let fileName = "设备统计数据.xlsx";
|
||||
if (disposition) {
|
||||
const match = disposition.match(/filename="?([^"]+)"?/);
|
||||
if (match && match[1]) fileName = decodeURIComponent(match[1]);
|
||||
}
|
||||
|
||||
// 创建blob对象和下载链接
|
||||
const blob = new Blob([response.data], {
|
||||
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
});
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
message.success("导出成功");
|
||||
} catch (error) {
|
||||
message.error("导出失败,请稍后重试");
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [range]);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: "设备名称",
|
||||
dataIndex: "deviceName",
|
||||
key: "deviceName",
|
||||
sorter: (a, b) => a.deviceName.localeCompare(b.deviceName),
|
||||
},
|
||||
{
|
||||
title: "使用次数",
|
||||
dataIndex: "usageCount",
|
||||
key: "usageCount",
|
||||
sorter: (a, b) => a.usageCount - b.usageCount,
|
||||
},
|
||||
{
|
||||
title: "使用总时长(天)",
|
||||
dataIndex: "totalUsageDays",
|
||||
key: "totalUsageDays",
|
||||
sorter: (a, b) => a.totalUsageDays - b.totalUsageDays,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Spin spinning={loading}>
|
||||
<Space className="p-4">
|
||||
<RangePicker
|
||||
value={range}
|
||||
onChange={(dates) => {
|
||||
if (dates) setRange(dates);
|
||||
}}
|
||||
presets={datePresets}
|
||||
/>
|
||||
<Input.Search
|
||||
allowClear
|
||||
placeholder="搜索设备名称"
|
||||
onSearch={handleSearch}
|
||||
style={{ width: 200 }}
|
||||
/>
|
||||
<Button type="primary" onClick={handleDownload}>
|
||||
导出 Excel
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
<Table
|
||||
rowKey={(record) => record.deviceId}
|
||||
dataSource={filteredData}
|
||||
columns={columns}
|
||||
/>
|
||||
</Spin>
|
||||
);
|
||||
}
|
132
src/pages/admin/UserDetailModal.jsx
Normal file
132
src/pages/admin/UserDetailModal.jsx
Normal 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>
|
||||
);
|
||||
}
|
147
src/pages/admin/UserManage.jsx
Normal file
147
src/pages/admin/UserManage.jsx
Normal 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,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
@ -68,7 +68,6 @@ export default function DeviceDetailModal({
|
||||
message.success("编辑成功");
|
||||
} else {
|
||||
device = await axiosInstance.post(`/device/${userId}`, data);
|
||||
console.log(device);
|
||||
|
||||
message.success("添加成功");
|
||||
}
|
||||
|
@ -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 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,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
@ -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("修改成功");
|
||||
|
@ -1,10 +1,13 @@
|
||||
import {
|
||||
DatePicker,
|
||||
Descriptions,
|
||||
Divider,
|
||||
Form,
|
||||
Image,
|
||||
Input,
|
||||
message,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
} from "antd";
|
||||
import dayjs from "dayjs";
|
||||
@ -15,6 +18,35 @@ import axiosInstance, { baseURL } from "../../api/axios";
|
||||
|
||||
export default function DeviceDetailModal({ visiable, device, onclose }) {
|
||||
const [unavailableTimes, setUnavailableTims] = useState([]);
|
||||
const [form] = Form.useForm();
|
||||
const userId = useSelector((state) => state.auth.userId);
|
||||
const [teams, setTeams] = useState([]);
|
||||
const [initialValues, setInitialValues] = useState();
|
||||
|
||||
const fetchTeams = async () => {
|
||||
const data = await axiosInstance.get("/teams");
|
||||
const teams = data.map((item) => ({
|
||||
label: item.label,
|
||||
value: item.label,
|
||||
}));
|
||||
setTeams(teams);
|
||||
};
|
||||
|
||||
const fetchUser = async () => {
|
||||
const data = await axiosInstance.get(`/userdetail/${userId}`);
|
||||
const values = {
|
||||
name: data.name,
|
||||
phone: data.phone,
|
||||
team: data.team,
|
||||
};
|
||||
setInitialValues(values);
|
||||
form.setFieldsValue(values);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTeams();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUnavailableTimes = async (id) => {
|
||||
const data = await axiosInstance.get(`/device/unavailable-times/${id}`);
|
||||
@ -23,6 +55,7 @@ export default function DeviceDetailModal({ visiable, device, onclose }) {
|
||||
|
||||
if (visiable && device?.deviceId) {
|
||||
fetchUnavailableTimes(device.deviceId);
|
||||
fetchUser();
|
||||
}
|
||||
}, [visiable, device?.deviceId]);
|
||||
|
||||
@ -43,9 +76,6 @@ export default function DeviceDetailModal({ visiable, device, onclose }) {
|
||||
});
|
||||
};
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const userId = useSelector((state) => state.auth.userId);
|
||||
|
||||
const handleOK = async () => {
|
||||
const values = await form.validateFields();
|
||||
|
||||
@ -56,6 +86,9 @@ export default function DeviceDetailModal({ visiable, device, onclose }) {
|
||||
userId,
|
||||
startTime: startTime.format("YYYY-MM-DD"),
|
||||
endTime: endTime.format("YYYY-MM-DD"),
|
||||
applicantName: values.name,
|
||||
applicantContact: values.phone,
|
||||
applicantTeam: values.team,
|
||||
};
|
||||
|
||||
await axiosInstance.post("/reservation", payload);
|
||||
@ -87,7 +120,9 @@ export default function DeviceDetailModal({ visiable, device, onclose }) {
|
||||
</Descriptions.Item>
|
||||
</Descriptions>
|
||||
|
||||
<Form form={form} layout="inline" className="mt-14">
|
||||
<Divider />
|
||||
|
||||
<Form form={form} layout="vertical" initialValues={initialValues}>
|
||||
<Form.Item
|
||||
name="date"
|
||||
label="预约日期"
|
||||
@ -95,6 +130,19 @@ export default function DeviceDetailModal({ visiable, device, onclose }) {
|
||||
>
|
||||
<RangePicker disabledDate={disabledDate} />
|
||||
</Form.Item>
|
||||
<Form.Item name="name" label="姓名" rules={[{ required: true }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="phone" label="联系电话">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="team"
|
||||
label="所属团队"
|
||||
rules={[{ required: true, message: "请选择所属团队" }]}
|
||||
>
|
||||
<Select options={teams} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Space>
|
||||
</Modal>
|
||||
|
@ -8,6 +8,8 @@ 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";
|
||||
import DeviceStats from "../pages/admin/DeviceStats";
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
@ -59,6 +61,20 @@ const router = createBrowserRouter([
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "admin",
|
||||
element: <ProtectedRoute allowedRoles={["ADMIN"]} />,
|
||||
children: [
|
||||
{
|
||||
path: "user-manage",
|
||||
element: <UserManage />,
|
||||
},
|
||||
{
|
||||
path: "stats-device",
|
||||
element: <DeviceStats />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "userdetail",
|
||||
element: <UserDetail />,
|
||||
|
Loading…
x
Reference in New Issue
Block a user