[fix]:[FL-160][对齐系统日志筛选交互]

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
chengkai3
2026-06-20 11:11:08 +08:00
parent 3d8013da57
commit 260e6598ef
2 changed files with 43 additions and 65 deletions
+24
View File
@@ -327,3 +327,27 @@
- 风险与关注点:
- 改动涉及 `GET /api/v1/tower-models` 列表分页契约,未改变响应字段、CRUD 字段、权限码或图片上传/预览接口。
## Follow-up - 系统日志筛选交互细节对齐(FL-160)
- 背景:
- 评审继续指出系统日志页仍保留“查询/重置筛选”按钮,和用户管理页的 500ms 防抖自动搜索模式不一致。
- 本次处理:
- 移除系统日志页桌面端和移动端的“查询”“重置筛选”按钮,筛选完全依赖输入框 500ms 防抖自动触发。
- 移动端保留多个筛选字段的 label,但移除按钮组,并将末尾表单项 `marginBottom` 对齐为 0。
- 表格 loading 从 `logsQuery.isFetching` 改为 `logsQuery.isLoading`,对齐用户管理页初次加载态口径。
- 表格分页补齐 `showSizeChanger: true``[10, 20, 50, 100]`,并让请求 `limit` 跟随分页 pageSize。
- 表格和移动卡片空态文案统一为“未找到符合筛选条件的日志记录。”。
- 验证:
- 基线:`npm --workspace web exec eslint src/app/admin/users/page.tsx src/app/admin/syslog/page.tsx` 通过,仅用户页存在 1 条既有 unused eslint-disable warning。
- 基线:`npm --workspace web exec tsc --noEmit --pretty false` 失败,失败点均在既有 `src/app/admin/elevation-records/page.tsx`,与系统日志页无关。
- 修改后:`npm --workspace web exec eslint src/app/admin/syslog/page.tsx --max-warnings=0` 通过。
- 修改后:`npm --workspace web exec eslint src/app/admin/users/page.tsx src/app/admin/syslog/page.tsx` 通过,仍仅用户页 1 条既有 warning。
- 修改后:`npm --workspace web exec tsc --noEmit --pretty false` 仍失败于既有 `src/app/admin/elevation-records/page.tsx`
- 修改后:`npm run build:web` 编译通过后在 TypeScript 阶段失败于同一既有 `src/app/admin/elevation-records/page.tsx:91`
- 风险与关注点:
- 改动仅影响 `/admin/syslog` 前端筛选交互、分页展示和空态文案,不改变 `/api/v1/admin/audit-logs` 字段或权限语义。
- 当前 dev 分支存在 unrelated `elevation-records` TypeScript 错误,会阻断全量 `tsc``next build`
+19 -65
View File
@@ -2,7 +2,7 @@
import Link from "next/link";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { Button, Card, Col, Empty, Form, Input, Row, Space, Spin, Table, Tag, Typography, type CardProps } from "antd";
import { Card, Col, Empty, Form, Input, Row, Space, Spin, Table, Tag, Typography, type CardProps } from "antd";
import type { ColumnsType } from "antd/es/table";
import type { CSSProperties, ComponentType, RefAttributes } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -44,6 +44,7 @@ export default function AdminSyslogPage() {
const isMobile = useMobileDetection();
const [offset, setOffset] = useState(0);
const [pageSize, setPageSize] = useState(PAGE_SIZE);
const [draftFilters, setDraftFilters] = useState<Filters>(EMPTY_FILTERS);
const [filters, setFilters] = useState<Filters>(EMPTY_FILTERS);
const [error, setError] = useState("");
@@ -103,7 +104,7 @@ export default function AdminSyslogPage() {
const logsPath = useMemo(() => {
const params = new URLSearchParams();
params.set("limit", String(PAGE_SIZE));
params.set("limit", String(pageSize));
params.set("offset", String(offset));
if (filters.action.trim()) {
params.set("action", filters.action.trim());
@@ -112,7 +113,7 @@ export default function AdminSyslogPage() {
params.set("user_id", filters.user_id.trim());
}
return `/api/v1/admin/audit-logs?${params.toString()}`;
}, [filters.action, filters.user_id, offset]);
}, [filters.action, filters.user_id, offset, pageSize]);
const loadLogs = useCallback(async () => {
const response = await fetchWithAuth(logsPath);
@@ -139,7 +140,7 @@ export default function AdminSyslogPage() {
const total = logsQuery.data?.total ?? 0;
const queryError = logsQuery.error instanceof Error ? logsQuery.error.message : "";
const anyError = error || queryError;
const currentPage = Math.floor(offset / PAGE_SIZE) + 1;
const currentPage = Math.floor(offset / pageSize) + 1;
useToastFeedback({
errorMessage: anyError,
@@ -198,14 +199,14 @@ export default function AdminSyslogPage() {
if (loadedCount < total) {
setIsLoadingMore(true);
setCardViewPage((prev) => prev + 1);
setOffset((prev) => prev + PAGE_SIZE);
setOffset((prev) => prev + pageSize);
}
}
};
cardBody.addEventListener("scroll", handleScroll);
return () => cardBody.removeEventListener("scroll", handleScroll);
}, [viewMode, isLoadingMore, logsQuery.isLoading, total, allLoadedLogs.length]);
}, [viewMode, isLoadingMore, logsQuery.isLoading, total, allLoadedLogs.length, pageSize]);
// Reset card view state when filters change
useEffect(() => {
@@ -414,7 +415,7 @@ export default function AdminSyslogPage() {
>
{viewMode === "card" ? (
<Form layout="vertical" style={{ marginBottom: 16 }}>
<Form.Item label="动作">
<Form.Item label="动作" style={{ marginBottom: 12 }}>
<Input
allowClear
placeholder="按动作筛选(如 auth.login"
@@ -422,7 +423,7 @@ export default function AdminSyslogPage() {
onChange={(event) => handleActionChange(event.target.value)}
/>
</Form.Item>
<Form.Item label="用户ID">
<Form.Item label="用户ID" style={{ marginBottom: 0 }}>
<Input
allowClear
placeholder="按用户ID筛选(如 openclaw"
@@ -430,31 +431,6 @@ export default function AdminSyslogPage() {
onChange={(event) => handleUserIdChange(event.target.value)}
/>
</Form.Item>
<Form.Item>
<Space size={8}>
<Button
type="primary"
onClick={() => {
setOffset(0);
setFilters({
action: draftFilters.action.trim(),
user_id: draftFilters.user_id.trim(),
});
}}
>
</Button>
<Button
onClick={() => {
setOffset(0);
setDraftFilters(EMPTY_FILTERS);
setFilters(EMPTY_FILTERS);
}}
>
</Button>
</Space>
</Form.Item>
</Form>
) : (
<Form layout="inline" style={{ rowGap: 12 }}>
@@ -474,31 +450,6 @@ export default function AdminSyslogPage() {
onChange={(event) => handleUserIdChange(event.target.value)}
/>
</Form.Item>
<Form.Item>
<Space size={8}>
<Button
type="primary"
onClick={() => {
setOffset(0);
setFilters({
action: draftFilters.action.trim(),
user_id: draftFilters.user_id.trim(),
});
}}
>
</Button>
<Button
onClick={() => {
setOffset(0);
setDraftFilters(EMPTY_FILTERS);
setFilters(EMPTY_FILTERS);
}}
>
</Button>
</Space>
</Form.Item>
</Form>
)}
@@ -512,21 +463,24 @@ export default function AdminSyslogPage() {
rowKey={(record) => String(record.id)}
columns={columns}
dataSource={logs}
loading={logsQuery.isFetching}
loading={logsQuery.isLoading}
tableLayout="fixed"
pagination={{
current: currentPage,
pageSize: PAGE_SIZE,
pageSize,
total: Math.max(total, 1),
onChange: (page) => setOffset((page - 1) * PAGE_SIZE),
showSizeChanger: false,
showQuickJumper: false,
onChange: (page, nextPageSize) => {
setPageSize(nextPageSize);
setOffset((page - 1) * nextPageSize);
},
showSizeChanger: true,
pageSizeOptions: [10, 20, 50, 100],
showTotal: () => `${total}`,
hideOnSinglePage: false,
style: { marginBottom: 0 },
}}
locale={{
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="暂无日志数据" />,
emptyText: <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description="未找到符合筛选条件的日志记录。" />,
}}
scroll={{ y: tableScrollY }}
/>
@@ -541,7 +495,7 @@ export default function AdminSyslogPage() {
<div className="admin-syslog-card-view-state">
<Empty
image={Empty.PRESENTED_IMAGE_SIMPLE}
description="暂无日志数据"
description="未找到符合筛选条件的日志记录。"
/>
</div>
) : (