141 lines
3.8 KiB
React
141 lines
3.8 KiB
React
|
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>
|
||
|
);
|
||
|
}
|