diff --git a/src/config/menuConfig.js b/src/config/menuConfig.js index e38af50..1ce51bc 100644 --- a/src/config/menuConfig.js +++ b/src/config/menuConfig.js @@ -55,7 +55,7 @@ const menuConfig = [ label: "设备统计", }, { - path: "/admin/stats-user", + path: "/admin/stats-reservation", label: "使用人统计", }, ], diff --git a/src/pages/admin/ReservationStats.jsx b/src/pages/admin/ReservationStats.jsx new file mode 100644 index 0000000..a83a5f3 --- /dev/null +++ b/src/pages/admin/ReservationStats.jsx @@ -0,0 +1,148 @@ +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 ReservationStats() { + 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("/reservation/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("/reservation/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: "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: "usageCount", + key: "usageCount", + sorter: (a, b) => a.usageCount - b.usageCount, + }, + ]; + + return ( + + + { + if (dates) setRange(dates); + }} + presets={datePresets} + /> + + + + + + `${record.deviceId}_${record.applicantName}_${record.applicantTeam}` + } + dataSource={filteredData} + columns={columns} + /> + + ); +} diff --git a/src/router/index.jsx b/src/router/index.jsx index e7f7c76..d27ee4e 100644 --- a/src/router/index.jsx +++ b/src/router/index.jsx @@ -10,6 +10,7 @@ import ProtectedRoute from "./ProtectedRoute"; import DeviceManage from "../pages/deviceAdmin/DeviceManage"; import UserManage from "../pages/admin/UserManage"; import DeviceStats from "../pages/admin/DeviceStats"; +import ReservationStats from "../pages/admin/ReservationStats"; const router = createBrowserRouter([ { @@ -73,6 +74,10 @@ const router = createBrowserRouter([ path: "stats-device", element: , }, + { + path: "stats-reservation", + element: , + }, ], }, {