filesend/backend/app/uploads/c97369b916634e8aa1d5f2ebcc69c000_OperationLog.js

193 lines
6.1 KiB
JavaScript
Raw Normal View History

2025-10-10 17:25:29 +08:00
import React, { useState, useEffect, useCallback } from 'react';
import { Container, Row, Col, Table, Spinner, Alert, Form, Button } from 'react-bootstrap';
import { adminAPI } from '../../services/api';
import { toast } from 'react-toastify';
import { debounce } from '../../utils/debounce';
const OperationLog = () => {
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
const [selectedUserId, setSelectedUserId] = useState('');
const [startDate, setStartDate] = useState('');
const [endDate, setEndDate] = useState('');
const [users, setUsers] = useState([]); // 用于用户筛选下拉框
const fetchLogs = async (pageNumber = page, search = searchQuery, userId = selectedUserId, start = startDate, end = endDate) => {
try {
setLoading(true);
const params = {
page: pageNumber,
search: search,
user_id: userId || undefined,
start_date: start || undefined,
end_date: end || undefined,
};
const res = await adminAPI.getOperationLogs(params);
if (pageNumber === 1) {
setLogs(res.data.logs);
} else {
setLogs((prevLogs) => [...prevLogs, ...res.data.logs]);
}
setHasMore(res.data.has_more);
setPage(pageNumber);
} catch (err) {
setError('获取操作日志失败');
toast.error('获取操作日志失败');
console.error(err);
} finally {
setLoading(false);
}
};
const fetchUsersForFilter = async () => {
try {
const res = await adminAPI.getUsers({ page: 1, per_page: 9999 }); // 获取所有用户用于筛选
setUsers(res.data.users);
} catch (err) {
console.error('获取用户列表失败', err);
}
};
const loadMore = () => {
if (hasMore && !loading) {
fetchLogs(page + 1);
}
};
useEffect(() => {
fetchUsersForFilter();
}, []);
// 防抖搜索函数
const debouncedSearch = useCallback(
debounce((search, userId, start, end) => {
fetchLogs(1, search, userId, start, end);
}, 500),
[]
);
useEffect(() => {
debouncedSearch(searchQuery, selectedUserId, startDate, endDate);
}, [searchQuery, selectedUserId, startDate, endDate, debouncedSearch]);
return (
<Container fluid className="p-4">
<Row className="mb-4">
<Col>
<h2 className="text-center">操作日志</h2>
</Col>
</Row>
<Row className="mb-4">
<Col>
<Form className="d-flex flex-wrap align-items-center">
<Form.Group controlId="searchQuery" className="me-2 mb-2">
<Form.Control
type="text"
placeholder="搜索操作类型或详情..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</Form.Group>
<Form.Group controlId="userFilter" className="me-2 mb-2">
<Form.Select
value={selectedUserId}
onChange={(e) => setSelectedUserId(e.target.value)}
>
<option value="">所有用户</option>
{users.map((user) => (
<option key={user.id} value={user.id}>
{user.username}
</option>
))}
</Form.Select>
</Form.Group>
<Form.Group controlId="startDate" className="me-2 mb-2">
<Form.Control
type="date"
value={startDate}
onChange={(e) => setStartDate(e.target.value)}
/>
</Form.Group>
<Form.Group controlId="endDate" className="me-2 mb-2">
<Form.Control
type="date"
value={endDate}
onChange={(e) => setEndDate(e.target.value)}
/>
</Form.Group>
</Form>
</Col>
</Row>
{/* 统一处理加载状态 */}
{loading && (
<Row className="justify-content-center">
<Col xs="auto">
<Spinner animation="border" role="status">
<span className="visually-hidden">加载中...</span>
</Spinner>
</Col>
</Row>
)}
{error && <Alert variant="danger">{error}</Alert>}
{/* 无数据状态(仅在非加载且无错误时显示) */}
{!loading && !error && logs.length === 0 && (
<Alert variant="info">没有找到操作日志</Alert>
)}
{/* 日志表格(仅在非加载、无错误且有数据时显示) */}
{!loading && !error && logs.length > 0 && (
<Row>
<Col>
<Table striped bordered hover responsive className="shadow-sm">
<thead>
<tr>
<th>ID</th>
<th>用户</th>
<th>操作类型</th>
<th>文件ID</th>
<th>文件名</th>
<th>时间</th>
<th>详情</th>
</tr>
</thead>
<tbody>
{logs.map((log) => (
<tr key={log.id}>
<td>{log.id}</td>
<td>{log.username}</td>
<td>{log.operation_type}</td>
<td>{log.file_id || 'N/A'}</td>
<td>{log.original_filename || 'N/A'}</td>
<td>{new Date(log.timestamp).toLocaleString()}</td>
<td>{log.details}</td>
</tr>
))}
</tbody>
</Table>
{hasMore && (
<div className="d-flex justify-content-center mt-4">
<Button
variant="primary"
onClick={loadMore}
disabled={loading}
>
{loading ? '加载中...' : '加载更多'}
</Button>
</div>
)}
</Col>
</Row>
)}
</Container>
);
};
export default OperationLog;