Compare commits
10 Commits
cd6d64e0ce
...
002b39da3f
Author | SHA1 | Date | |
---|---|---|---|
002b39da3f | |||
09534937dc | |||
31146a7b6b | |||
ba0d7cd8e2 | |||
0b2c143225 | |||
4c554401a9 | |||
087fa440b6 | |||
b34646f869 | |||
87cc58be92 | |||
fce4a8da6c |
@ -1,10 +1,10 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React</title>
|
<title>电科院材料所实验室仪器设备预约系统</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "equip-reserve-frontend",
|
"name": "equip-reserve-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
ApartmentOutlined,
|
ApartmentOutlined,
|
||||||
|
ContainerOutlined,
|
||||||
DesktopOutlined,
|
DesktopOutlined,
|
||||||
ExperimentOutlined,
|
ExperimentOutlined,
|
||||||
FileDoneOutlined,
|
FileDoneOutlined,
|
||||||
@ -14,13 +15,15 @@ const menuConfig = [
|
|||||||
path: "/user/reserve",
|
path: "/user/reserve",
|
||||||
label: "设备预约",
|
label: "设备预约",
|
||||||
icon: DesktopOutlined,
|
icon: DesktopOutlined,
|
||||||
roles: ["USER"],
|
roles: ["USER", "LEADER", "DEVICE_ADMIN"],
|
||||||
|
order: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/user/my-reservation",
|
path: "/user/my-reservation",
|
||||||
label: "我的预约",
|
label: "我的预约",
|
||||||
icon: UnorderedListOutlined,
|
icon: ContainerOutlined,
|
||||||
roles: ["USER"],
|
roles: ["USER", "LEADER", "DEVICE_ADMIN"],
|
||||||
|
order: 5,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -28,36 +31,43 @@ const menuConfig = [
|
|||||||
label: "预约审批",
|
label: "预约审批",
|
||||||
icon: FileDoneOutlined,
|
icon: FileDoneOutlined,
|
||||||
roles: ["LEADER", "DEVICE_ADMIN"],
|
roles: ["LEADER", "DEVICE_ADMIN"],
|
||||||
|
order: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/my-approval",
|
path: "/my-approval",
|
||||||
label: "审批记录",
|
label: "审批记录",
|
||||||
icon: UnorderedListOutlined,
|
icon: UnorderedListOutlined,
|
||||||
roles: ["LEADER", "DEVICE_ADMIN"],
|
roles: ["LEADER", "DEVICE_ADMIN"],
|
||||||
|
order: 2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/device-manage",
|
path: "/device-manage",
|
||||||
label: "设备管理",
|
label: "设备管理",
|
||||||
icon: ExperimentOutlined,
|
icon: ExperimentOutlined,
|
||||||
roles: ["DEVICE_ADMIN"],
|
roles: ["DEVICE_ADMIN"],
|
||||||
|
order: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/admin/user-manage",
|
path: "/admin/user-manage",
|
||||||
label: "用户管理",
|
label: "用户管理",
|
||||||
icon: UsergroupAddOutlined,
|
icon: UsergroupAddOutlined,
|
||||||
roles: ["ADMIN"],
|
roles: ["ADMIN"],
|
||||||
|
order: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "数据统计",
|
label: "数据统计",
|
||||||
icon: PieChartOutlined,
|
icon: PieChartOutlined,
|
||||||
|
order: 2,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "/admin/stats-device",
|
path: "/admin/stats-device",
|
||||||
label: "设备统计",
|
label: "设备统计",
|
||||||
|
order: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/admin/stats-reservation",
|
path: "/admin/stats-reservation",
|
||||||
label: "使用人统计",
|
label: "使用人统计",
|
||||||
|
order: 2,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
roles: ["ADMIN"],
|
roles: ["ADMIN"],
|
||||||
@ -67,12 +77,14 @@ const menuConfig = [
|
|||||||
icon: ApartmentOutlined,
|
icon: ApartmentOutlined,
|
||||||
label: "团队管理",
|
label: "团队管理",
|
||||||
roles: ["ADMIN"],
|
roles: ["ADMIN"],
|
||||||
|
order: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/userdetail",
|
path: "/userdetail",
|
||||||
label: "个人信息",
|
label: "个人信息",
|
||||||
icon: UserOutlined,
|
icon: UserOutlined,
|
||||||
roles: ["USER", "LEADER", "DEVICE_ADMIN", "ADMIN"],
|
roles: ["USER", "LEADER", "DEVICE_ADMIN", "ADMIN"],
|
||||||
|
order: 999,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -44,8 +44,18 @@ export default function CommonLayout() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 递归排序函数
|
||||||
|
const sortMenu = (menu) => {
|
||||||
|
return [...menu]
|
||||||
|
.sort((a, b) => (a.order ?? 999) - (b.order ?? 999))
|
||||||
|
.map((item) => ({
|
||||||
|
...item,
|
||||||
|
children: item.children ? sortMenu(item.children) : undefined,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
const buildMenuItems = (menu) =>
|
const buildMenuItems = (menu) =>
|
||||||
menu.map((item) => {
|
sortMenu(menu).map((item) => {
|
||||||
if (item.children) {
|
if (item.children) {
|
||||||
return {
|
return {
|
||||||
key: item.label, // 可改为 item.path 但注意不冲突
|
key: item.label, // 可改为 item.path 但注意不冲突
|
||||||
|
@ -22,7 +22,9 @@ export default function Login() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-screen h-screen flex justify-center items-center flex-col">
|
<div className="w-screen h-screen flex justify-center items-center flex-col">
|
||||||
<h1 className="m-6 text-gray-800 font-mono text-4xl">xxxx设备预约系统</h1>
|
<h1 className="m-6 text-gray-800 font-mono text-4xl">
|
||||||
|
电科院材料所实验室仪器设备预约系统
|
||||||
|
</h1>
|
||||||
<Form
|
<Form
|
||||||
name="login"
|
name="login"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
129
src/pages/admin/DeviceDetailStatsModal.jsx
Normal file
129
src/pages/admin/DeviceDetailStatsModal.jsx
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
import { message, Modal, Space, Spin, Table } from "antd";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import axiosInstance from "../../api/axios";
|
||||||
|
|
||||||
|
export default function DeviceDetailStatsModal({
|
||||||
|
visible,
|
||||||
|
record,
|
||||||
|
range,
|
||||||
|
onClose,
|
||||||
|
}) {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await axiosInstance.get("/device/detail-stats", {
|
||||||
|
params: {
|
||||||
|
deviceId: record.deviceId,
|
||||||
|
start: range[0].format("YYYY-MM-DD"),
|
||||||
|
end: range[1].format("YYYY-MM-DD"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
setData(res);
|
||||||
|
} catch (e) {
|
||||||
|
message.error("获取数据失败");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleExport = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await axiosInstance.get("/device/detail-stats/export", {
|
||||||
|
params: {
|
||||||
|
deviceId: record.deviceId,
|
||||||
|
start: range[0].format("YYYY-MM-DD"),
|
||||||
|
end: range[1].format("YYYY-MM-DD"),
|
||||||
|
},
|
||||||
|
responseType: "blob", // 二进制流
|
||||||
|
skipInterceptor: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 从响应头获取文件名,兼容后端返回
|
||||||
|
const fileName = `${record?.deviceName}_${range[0].format(
|
||||||
|
"YYYY.MM.DD"
|
||||||
|
)}-${range[1].format("YYYY.MM.DD")}_使用详情.xlsx`;
|
||||||
|
|
||||||
|
// 创建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(() => {
|
||||||
|
if (visible) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
}, [visible, record]);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: "使用人",
|
||||||
|
dataIndex: "applicantName",
|
||||||
|
key: "applicantName",
|
||||||
|
sorter: (a, b) => a.applicantName.localeCompare(b.applicantName),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "所属团队",
|
||||||
|
dataIndex: "applicantTeam",
|
||||||
|
key: "applicantTeam",
|
||||||
|
sorter: (a, b) => a.applicantTeam.localeCompare(b.applicantTeam),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "开始日期",
|
||||||
|
dataIndex: "startDay",
|
||||||
|
key: "startDay",
|
||||||
|
sorter: (a, b) => a.startDay.localeCompare(b.startDay),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "结束日期",
|
||||||
|
dataIndex: "endDay",
|
||||||
|
key: "endDay",
|
||||||
|
sorter: (a, b) => a.endDay.localeCompare(b.endDay),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Spin spinning={loading}>
|
||||||
|
<Modal
|
||||||
|
open={visible}
|
||||||
|
title={`${record?.deviceName}_${range[0].format(
|
||||||
|
"YYYY.MM.DD"
|
||||||
|
)}-${range[1].format("YYYY.MM.DD")}_使用详情`}
|
||||||
|
width={"80%"}
|
||||||
|
cancelText="返回"
|
||||||
|
onCancel={() => {
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
okText="导出Excel"
|
||||||
|
onOk={handleExport}
|
||||||
|
>
|
||||||
|
<Table
|
||||||
|
rowKey={(record) => record.deviceId}
|
||||||
|
dataSource={data}
|
||||||
|
columns={columns}
|
||||||
|
className="mt-4"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</Spin>
|
||||||
|
);
|
||||||
|
}
|
@ -3,6 +3,7 @@ import dayjs from "dayjs";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import axiosInstance from "../../api/axios";
|
import axiosInstance from "../../api/axios";
|
||||||
import { datePresets } from "../../config/datePresetsConfig";
|
import { datePresets } from "../../config/datePresetsConfig";
|
||||||
|
import DeviceDetailStatsModal from "./DeviceDetailStatsModal";
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
|
|
||||||
@ -15,6 +16,8 @@ export default function DeviceStats() {
|
|||||||
]);
|
]);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [visiable, setVisiable] = useState(false);
|
||||||
|
const [selectedRecord, setSelectedRecord] = useState(null);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -107,6 +110,23 @@ export default function DeviceStats() {
|
|||||||
key: "totalUsageDays",
|
key: "totalUsageDays",
|
||||||
sorter: (a, b) => a.totalUsageDays - b.totalUsageDays,
|
sorter: (a, b) => a.totalUsageDays - b.totalUsageDays,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "操作",
|
||||||
|
key: "action",
|
||||||
|
render: (_, record) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedRecord(record);
|
||||||
|
setVisiable(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
查看详情
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -135,6 +155,14 @@ export default function DeviceStats() {
|
|||||||
dataSource={filteredData}
|
dataSource={filteredData}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
/>
|
/>
|
||||||
|
<DeviceDetailStatsModal
|
||||||
|
visible={visiable}
|
||||||
|
record={selectedRecord}
|
||||||
|
onClose={() => {
|
||||||
|
setVisiable(false);
|
||||||
|
}}
|
||||||
|
range={range}
|
||||||
|
/>
|
||||||
</Spin>
|
</Spin>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
33
src/pages/admin/TeamDetailModal.jsx
Normal file
33
src/pages/admin/TeamDetailModal.jsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { List, Modal, Typography } from "antd";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import axiosInstance from "../../api/axios";
|
||||||
|
|
||||||
|
export default function TeamDetailModal({ open, team, onclose }) {
|
||||||
|
const [data, setData] = useState([]);
|
||||||
|
|
||||||
|
const fetchData = async () => {
|
||||||
|
const data = await axiosInstance.get(`/user-team/${team.id}`);
|
||||||
|
setData(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (open) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
}, [open, team]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onCancel={() => {
|
||||||
|
onclose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<List
|
||||||
|
header={<div>{team?.name}</div>}
|
||||||
|
dataSource={data}
|
||||||
|
renderItem={(item) => <List.Item>{item}</List.Item>}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
@ -3,6 +3,7 @@ import Column from "antd/es/table/Column";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import axiosInstance from "../../api/axios";
|
import axiosInstance from "../../api/axios";
|
||||||
import TeamDeleteButton from "../../components/TeamDeleteButton";
|
import TeamDeleteButton from "../../components/TeamDeleteButton";
|
||||||
|
import TeamDetailModal from "./TeamDetailModal";
|
||||||
|
|
||||||
export default function TeamManage() {
|
export default function TeamManage() {
|
||||||
const [teams, setTeams] = useState([]);
|
const [teams, setTeams] = useState([]);
|
||||||
@ -11,6 +12,8 @@ export default function TeamManage() {
|
|||||||
const [editingId, setEditingId] = useState();
|
const [editingId, setEditingId] = useState();
|
||||||
const [editingName, setEditingName] = useState("");
|
const [editingName, setEditingName] = useState("");
|
||||||
const [newTeamName, setNewTeamName] = useState();
|
const [newTeamName, setNewTeamName] = useState();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [selectedTeam, setSelectedTeam] = useState(null);
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const data = await axiosInstance.get("/teams");
|
const data = await axiosInstance.get("/teams");
|
||||||
@ -124,6 +127,15 @@ export default function TeamManage() {
|
|||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedTeam(record);
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
人员列表
|
||||||
|
</Button>
|
||||||
<TeamDeleteButton
|
<TeamDeleteButton
|
||||||
record={record}
|
record={record}
|
||||||
onConfirm={() => handleDelete(record)}
|
onConfirm={() => handleDelete(record)}
|
||||||
@ -133,6 +145,14 @@ export default function TeamManage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Table>
|
</Table>
|
||||||
|
<TeamDetailModal
|
||||||
|
open={open}
|
||||||
|
team={selectedTeam}
|
||||||
|
onclose={() => {
|
||||||
|
setOpen(false);
|
||||||
|
setSelectedTeam(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { Button, Form, Input, Table, Tag } from "antd";
|
import { Button, DatePicker, Form, Input, message, Table, Tag } from "antd";
|
||||||
import { useForm } from "antd/es/form/Form";
|
import { useForm } from "antd/es/form/Form";
|
||||||
import Column from "antd/es/table/Column";
|
import Column from "antd/es/table/Column";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import axiosInstance from "../../api/axios";
|
import axiosInstance from "../../api/axios";
|
||||||
import { selectUserId } from "../../features/auth/authSlice";
|
import { selectUserId, selectUserRole } from "../../features/auth/authSlice";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
export default function MyApproval() {
|
export default function MyApproval() {
|
||||||
const [approvals, setApprovals] = useState([]);
|
const [approvals, setApprovals] = useState([]);
|
||||||
@ -14,8 +15,11 @@ export default function MyApproval() {
|
|||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
|
const [editingRow, setEditingRow] = useState(null);
|
||||||
|
const [tempEndTime, setTempEndTime] = useState(null);
|
||||||
|
|
||||||
const userId = useSelector(selectUserId);
|
const userId = useSelector(selectUserId);
|
||||||
|
const userRole = useSelector(selectUserRole);
|
||||||
|
|
||||||
const fetchData = async (pagination, searchParam) => {
|
const fetchData = async (pagination, searchParam) => {
|
||||||
const data = await axiosInstance.get(`/approval/${userId}`, {
|
const data = await axiosInstance.get(`/approval/${userId}`, {
|
||||||
@ -53,6 +57,20 @@ export default function MyApproval() {
|
|||||||
await fetchData(newPagination, values);
|
await fetchData(newPagination, values);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (record) => {
|
||||||
|
try {
|
||||||
|
await axiosInstance.post(`/reservation/endTime/${record.reservationId}`, {
|
||||||
|
endTime: dayjs(tempEndTime).format("YYYY-MM-DD"),
|
||||||
|
});
|
||||||
|
const values = await form.validateFields();
|
||||||
|
await fetchData(pagination, values);
|
||||||
|
setEditingRow(null);
|
||||||
|
message.success("修改成功");
|
||||||
|
} catch (error) {
|
||||||
|
message.error("修改失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="p-2 pt-4">
|
<div className="p-2 pt-4">
|
||||||
<Form form={form} layout="inline" onFinish={handleSearch}>
|
<Form form={form} layout="inline" onFinish={handleSearch}>
|
||||||
@ -88,7 +106,57 @@ export default function MyApproval() {
|
|||||||
/>
|
/>
|
||||||
<Column title="预约设备" key="deviceName" dataIndex="deviceName" />
|
<Column title="预约设备" key="deviceName" dataIndex="deviceName" />
|
||||||
<Column title="开始时间" key="startTime" dataIndex="startTime" />
|
<Column title="开始时间" key="startTime" dataIndex="startTime" />
|
||||||
<Column title="结束时间" key="endTime" dataIndex="endTime" />
|
<Column
|
||||||
|
title="结束时间"
|
||||||
|
key="endTime"
|
||||||
|
render={(_, record) => {
|
||||||
|
const isEditable =
|
||||||
|
record.decision === 1 && userRole.includes("DEVICE_ADMIN");
|
||||||
|
|
||||||
|
if (editingRow === record.approvalId) {
|
||||||
|
return (
|
||||||
|
<div style={{ display: "flex", gap: 8 }}>
|
||||||
|
<DatePicker
|
||||||
|
defaultValue={dayjs(record.endTime)}
|
||||||
|
onChange={(date) => setTempEndTime(date)}
|
||||||
|
disabledDate={(current) =>
|
||||||
|
current &&
|
||||||
|
current.isBefore(dayjs(record.startTime), "day")
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleSubmit(record)}
|
||||||
|
>
|
||||||
|
提交
|
||||||
|
</Button>
|
||||||
|
<Button onClick={() => setEditingRow(null)} size="small">
|
||||||
|
取消
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isEditable ? (
|
||||||
|
<span>
|
||||||
|
<span className="pr-6">{record.endTime}</span>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setEditingRow(record.approvalId);
|
||||||
|
setTempEndTime(dayjs(record.endTime));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span>{record.endTime}</span>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Column
|
<Column
|
||||||
title="审批情况"
|
title="审批情况"
|
||||||
key="decision"
|
key="decision"
|
||||||
|
@ -60,20 +60,27 @@ export default function DeviceDetailModal({ visiable, device, onclose }) {
|
|||||||
}, [visiable, device?.deviceId]);
|
}, [visiable, device?.deviceId]);
|
||||||
|
|
||||||
const { RangePicker } = DatePicker;
|
const { RangePicker } = DatePicker;
|
||||||
const disabledDate = (current) => {
|
const disabledDate = (current, { from } = {}) => {
|
||||||
if (!current) return false;
|
if (!current) return false;
|
||||||
|
|
||||||
const today = dayjs().startOf("day");
|
const today = dayjs().startOf("day");
|
||||||
const currentDay = current.startOf("day");
|
const currentDay = current.startOf("day");
|
||||||
if (currentDay.isBefore(today)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
dayjs.extend(isBetween);
|
|
||||||
|
|
||||||
return unavailableTimes.some(({ startTime, endTime }) => {
|
// 禁用过去日期
|
||||||
|
const isPastDate = current.isBefore(today);
|
||||||
|
|
||||||
|
// 禁用不可预约时间段
|
||||||
|
dayjs.extend(isBetween);
|
||||||
|
const isUnavailable = unavailableTimes.some(({ startTime, endTime }) => {
|
||||||
const start = dayjs(startTime).startOf("day");
|
const start = dayjs(startTime).startOf("day");
|
||||||
const end = dayjs(endTime).endOf("day");
|
const end = dayjs(endTime).endOf("day");
|
||||||
return currentDay.isBetween(start, end, null, "[]");
|
return currentDay.isBetween(start, end, null, "[]");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 限制选择范围为 7 天内(从 from 开始算起)
|
||||||
|
const isExceedingRange = from && Math.abs(current.diff(from, "day")) >= 7;
|
||||||
|
|
||||||
|
return isPastDate || isUnavailable || isExceedingRange;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOK = async () => {
|
const handleOK = async () => {
|
||||||
|
@ -58,6 +58,16 @@ export default function MyReservation() {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Column
|
||||||
|
title="团队负责人"
|
||||||
|
key="deviceLeaderName"
|
||||||
|
dataIndex="deviceLeaderName"
|
||||||
|
/>
|
||||||
|
<Column
|
||||||
|
title="负责人联系方式"
|
||||||
|
key="deviceLeaderContact"
|
||||||
|
dataIndex="deviceLeaderContact"
|
||||||
|
/>
|
||||||
<Column
|
<Column
|
||||||
title="设备管理员"
|
title="设备管理员"
|
||||||
key="deviceAdminName"
|
key="deviceAdminName"
|
||||||
@ -68,8 +78,17 @@ export default function MyReservation() {
|
|||||||
key="deviceAdminContact"
|
key="deviceAdminContact"
|
||||||
dataIndex="deviceAdminContact"
|
dataIndex="deviceAdminContact"
|
||||||
/>
|
/>
|
||||||
<Column title="开始使用时间" key="startTime" dataIndex="startTime" />
|
<Column
|
||||||
<Column title="结束使用时间" key="endTime" dataIndex="endTime" />
|
title="使用时间"
|
||||||
|
key="usageTime"
|
||||||
|
render={(_, record) => {
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
{record.startTime} 至 {record.endTime}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Column
|
<Column
|
||||||
title="预约状态"
|
title="预约状态"
|
||||||
key="statusLabel"
|
key="statusLabel"
|
||||||
|
@ -28,7 +28,9 @@ const router = createBrowserRouter([
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "user",
|
path: "user",
|
||||||
element: <ProtectedRoute allowedRoles={["USER"]} />,
|
element: (
|
||||||
|
<ProtectedRoute allowedRoles={["USER", "LEADER", "DEVICE_ADMIN"]} />
|
||||||
|
),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: "reserve",
|
path: "reserve",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user