feat: 实现管理员使用人统计页面
This commit is contained in:
parent
8a5f231ae2
commit
886a969477
@ -55,7 +55,7 @@ const menuConfig = [
|
||||
label: "设备统计",
|
||||
},
|
||||
{
|
||||
path: "/admin/stats-user",
|
||||
path: "/admin/stats-reservation",
|
||||
label: "使用人统计",
|
||||
},
|
||||
],
|
||||
|
148
src/pages/admin/ReservationStats.jsx
Normal file
148
src/pages/admin/ReservationStats.jsx
Normal file
@ -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 (
|
||||
<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}_${record.applicantName}_${record.applicantTeam}`
|
||||
}
|
||||
dataSource={filteredData}
|
||||
columns={columns}
|
||||
/>
|
||||
</Spin>
|
||||
);
|
||||
}
|
@ -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: <DeviceStats />,
|
||||
},
|
||||
{
|
||||
path: "stats-reservation",
|
||||
element: <ReservationStats />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user