From dc0451529efec8ebb6d8f5e6df4e1c28d670f53e Mon Sep 17 00:00:00 2001
From: BenjaminNH <1249376374@qq.com>
Date: Tue, 1 Jul 2025 11:24:55 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E8=AE=BE=E5=A4=87?=
=?UTF-8?q?=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/config/DeviceStatusConfig.js | 5 +
src/config/menuConfig.js | 7 +
src/pages/deviceAdmin/DeviceDetailModal.jsx | 109 ++++++++++++++++
src/pages/deviceAdmin/DeviceManage.jsx | 136 ++++++++++++++++++++
src/pages/shared/MyApproval.jsx | 13 +-
src/pages/shared/UserDetail.jsx | 2 +-
src/pages/user/DeviceDetailModal.jsx | 7 +-
src/pages/user/Reserve.jsx | 2 +-
src/router/index.jsx | 23 ++--
9 files changed, 283 insertions(+), 21 deletions(-)
create mode 100644 src/config/DeviceStatusConfig.js
create mode 100644 src/pages/deviceAdmin/DeviceDetailModal.jsx
create mode 100644 src/pages/deviceAdmin/DeviceManage.jsx
diff --git a/src/config/DeviceStatusConfig.js b/src/config/DeviceStatusConfig.js
new file mode 100644
index 0000000..3aeda2e
--- /dev/null
+++ b/src/config/DeviceStatusConfig.js
@@ -0,0 +1,5 @@
+export const deviceStatusOptions = [
+ { label: "可用", value: "AVAILABLE" },
+ { label: "停用", value: "DISABLED" },
+ // { label: "维修中", value: "MAINTENANCE" },
+];
diff --git a/src/config/menuConfig.js b/src/config/menuConfig.js
index 2db2373..70e6eb7 100644
--- a/src/config/menuConfig.js
+++ b/src/config/menuConfig.js
@@ -1,5 +1,6 @@
import {
DesktopOutlined,
+ ExperimentOutlined,
FileDoneOutlined,
UnorderedListOutlined,
UserOutlined,
@@ -31,6 +32,12 @@ const menuConfig = [
icon: UnorderedListOutlined,
roles: ["LEADER", "DEVICE_ADMIN"],
},
+ {
+ path: "/device-manage",
+ label: "设备管理",
+ icon: ExperimentOutlined,
+ roles: ["DEVICE_ADMIN"],
+ },
{
path: "/userdetail",
label: "个人信息",
diff --git a/src/pages/deviceAdmin/DeviceDetailModal.jsx b/src/pages/deviceAdmin/DeviceDetailModal.jsx
new file mode 100644
index 0000000..1cff3af
--- /dev/null
+++ b/src/pages/deviceAdmin/DeviceDetailModal.jsx
@@ -0,0 +1,109 @@
+import { UploadOutlined } from "@ant-design/icons";
+import { Button, Form, Image, Input, Modal, Select, Upload } from "antd";
+import TextArea from "antd/es/input/TextArea";
+import { useEffect, useState } from "react";
+import axiosInstance, { baseURL } from "../../api/axios";
+import { deviceStatusOptions } from "../../config/DeviceStatusConfig";
+
+export default function DeviceDetailModal({
+ visiable,
+ device,
+ onclose,
+ onSuccess,
+}) {
+ const [form] = Form.useForm();
+ const [imageFile, setImageFile] = useState(null);
+ const [initialValues, setInitialValues] = useState({});
+ const [fileList, setFileList] = useState();
+ useEffect(() => {
+ if (device) {
+ const values = {
+ name: device.name,
+ location: device.location,
+ usageRequirement: device.usageRequirement,
+ status: device.status,
+ };
+ form.setFieldsValue(values);
+ setInitialValues(values);
+ }
+ }, [device]);
+
+ const handleEdit = async () => {
+ const values = await form.validateFields();
+ const updates = {};
+ Object.keys(initialValues).forEach((key) => {
+ if (values[key] !== initialValues[key]) {
+ updates[key] = values[key];
+ }
+ });
+
+ if (Object.keys(updates).length > 0) {
+ await axiosInstance.put(`/device/${device.deviceId}`, updates);
+ }
+
+ if (imageFile) {
+ const formData = new FormData();
+ formData.append("image", imageFile);
+ await axiosInstance.post(`device/${device.deviceId}/image`, formData, {
+ headers: {
+ "Content-Type": "multipart/form-data",
+ },
+ });
+ }
+ onSuccess();
+ setFileList([]);
+ form.resetFields();
+ onclose();
+ };
+
+ return (
+ {
+ setFileList([]);
+ form.resetFields();
+ onclose();
+ }}
+ onOk={handleEdit}
+ okText="保存"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {device?.imagePath && (
+
+ )}
+
+ {
+ setImageFile(file);
+ return false; // 阻止自动上传
+ }}
+ showUploadList={{ showRemoveIcon: true }}
+ onChange={({ fileList }) => setFileList(fileList)}
+ >
+ }>点击上传
+
+
+
+
+ );
+}
diff --git a/src/pages/deviceAdmin/DeviceManage.jsx b/src/pages/deviceAdmin/DeviceManage.jsx
new file mode 100644
index 0000000..16b3404
--- /dev/null
+++ b/src/pages/deviceAdmin/DeviceManage.jsx
@@ -0,0 +1,136 @@
+import { Button, Input, message, Space, Table, Tag } from "antd";
+import Column from "antd/es/table/Column";
+import { useEffect, useState } from "react";
+import { useSelector } from "react-redux";
+import axiosInstance from "../../api/axios";
+import { deviceStatusOptions } from "../../config/DeviceStatusConfig";
+import { selectUserId } from "../../features/auth/authSlice";
+import DeviceDetailModal from "./DeviceDetailModal";
+
+export default function DeviceManage() {
+ const [devices, setDevices] = useState([]);
+ const [selectedDevice, setSelectedDevice] = useState(null);
+ const [searchName, setSearchName] = useState(null);
+ const [pagination, setPagination] = useState({
+ current: 1,
+ pageSize: 10,
+ total: 0,
+ });
+
+ const userId = useSelector(selectUserId);
+ const fetchData = async (pagination, name = searchName) => {
+ const data = await axiosInstance.get(`/device/${userId}`, {
+ params: {
+ page: pagination.current,
+ size: pagination.pageSize,
+ name,
+ },
+ });
+
+ setDevices(data.records);
+ setPagination({
+ ...pagination,
+ total: data.total,
+ });
+ };
+
+ useEffect(() => {
+ fetchData(pagination);
+ }, []);
+
+ const handlePageChange = async (pagination) => {
+ await fetchData(pagination);
+ };
+
+ const handleDelete = async (deviceId) => {
+ await axiosInstance.delete(`/device/${deviceId}`);
+ const newPagination = {
+ ...pagination,
+ current: 1,
+ };
+ setPagination(newPagination);
+ await fetchData(newPagination);
+ };
+
+ const handleSearch = async (value) => {
+ setSearchName(value);
+ const newPagination = {
+ ...pagination,
+ current: 1,
+ };
+ setPagination(newPagination);
+ await fetchData(newPagination, value);
+ };
+
+ // 名称、使用要求、位置、状态
+ return (
+ <>
+
+
+
+
+
+ {
+ const color = status === "AVAILABLE" ? "green" : "grey";
+ const option = deviceStatusOptions.find(
+ (item) => item.value === status
+ );
+ return (
+
+ {option ? option.label : status}
+
+ );
+ }}
+ />
+ {
+ return (
+
+
+
+
+ );
+ }}
+ />
+
+ setSelectedDevice(null)}
+ onSuccess={async () => {
+ message.success("编辑成功");
+ await fetchData(pagination);
+ }}
+ />
+ >
+ );
+}
diff --git a/src/pages/shared/MyApproval.jsx b/src/pages/shared/MyApproval.jsx
index 3cce9e5..6ae629b 100644
--- a/src/pages/shared/MyApproval.jsx
+++ b/src/pages/shared/MyApproval.jsx
@@ -45,13 +45,12 @@ export default function MyApproval() {
const handleSearch = async () => {
const values = await form.validateFields();
- fetchData(
- {
- current: 1,
- pageSize: pagination.pageSize,
- },
- values
- );
+ const newPagination = {
+ ...pagination,
+ current: 1,
+ };
+ setPagination(newPagination);
+ await fetchData(newPagination, values);
};
return (
diff --git a/src/pages/shared/UserDetail.jsx b/src/pages/shared/UserDetail.jsx
index 24c63f2..a1ba4bc 100644
--- a/src/pages/shared/UserDetail.jsx
+++ b/src/pages/shared/UserDetail.jsx
@@ -6,7 +6,7 @@ import axiosInstance from "../../api/axios";
import { selectUserId } from "../../features/auth/authSlice";
export default function UserDetail() {
- const [form] = useForm();
+ const [form] = Form.useForm();
const [showPassword, setShowPassword] = useState(false);
const [user, setUser] = useState({
username: "",
diff --git a/src/pages/user/DeviceDetailModal.jsx b/src/pages/user/DeviceDetailModal.jsx
index 73e394c..15da59a 100644
--- a/src/pages/user/DeviceDetailModal.jsx
+++ b/src/pages/user/DeviceDetailModal.jsx
@@ -8,11 +8,10 @@ import {
Space,
} from "antd";
import dayjs from "dayjs";
-import axiosInstance, { baseURL } from "../../api/axios";
-import { useEffect, useState } from "react";
import isBetween from "dayjs/plugin/isBetween";
-import { store } from "../../store";
-import { useSelector, useStore } from "react-redux";
+import { useEffect, useState } from "react";
+import { useSelector } from "react-redux";
+import axiosInstance, { baseURL } from "../../api/axios";
export default function DeviceDetailModal({ visiable, device, onclose }) {
const [unavailableTimes, setUnavailableTims] = useState([]);
diff --git a/src/pages/user/Reserve.jsx b/src/pages/user/Reserve.jsx
index d31fbe1..0217f13 100644
--- a/src/pages/user/Reserve.jsx
+++ b/src/pages/user/Reserve.jsx
@@ -46,7 +46,7 @@ export default function Reserve() {
const [selectedDevice, setSelectedDevice] = useState(null);
const handleSearch = (value) => {
- setName(name);
+ setName(value);
const newPagination = {
...pagination,
current: 1,
diff --git a/src/router/index.jsx b/src/router/index.jsx
index 60967ab..5415b36 100644
--- a/src/router/index.jsx
+++ b/src/router/index.jsx
@@ -1,15 +1,13 @@
import { createBrowserRouter, Navigate } from "react-router-dom";
import CommonLayout from "../layouts/CommonLayout";
-import DeviceAdminApproval from "../pages/deviceAdmin/Approval";
-import LeaderApproval from "../pages/leader/Approval";
-import LeaderMyApproval from "../pages/leader/MyApproval";
import Login from "../pages/Login";
-import MyReservation from "../pages/user/MyReservation";
-import Reserve from "../pages/user/Reserve";
-import UserDetail from "../pages/shared/UserDetail";
-import ProtectedRoute from "./ProtectedRoute";
import Approval from "../pages/shared/Approval";
import MyApproval from "../pages/shared/MyApproval";
+import UserDetail from "../pages/shared/UserDetail";
+import MyReservation from "../pages/user/MyReservation";
+import Reserve from "../pages/user/Reserve";
+import ProtectedRoute from "./ProtectedRoute";
+import DeviceManage from "../pages/deviceAdmin/DeviceManage";
const router = createBrowserRouter([
{
@@ -51,7 +49,16 @@ const router = createBrowserRouter([
},
],
},
-
+ {
+ path: "device-manage",
+ element: ,
+ children: [
+ {
+ path: "",
+ element: ,
+ },
+ ],
+ },
{
path: "userdetail",
element: ,