From 65683350ea1d637c0abb7a69973c9176abf170c8 Mon Sep 17 00:00:00 2001 From: yml2213 Date: Tue, 23 Jun 2026 09:45:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E6=97=B6=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E9=9D=A2=E6=9D=BF=E5=A2=9E=E5=8A=A0=E6=97=A5=E5=BF=97=E7=AD=89?= =?UTF-8?q?=E7=BA=A7=E9=80=89=E6=8B=A9=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 4级过滤:错误/警告+/成功+/全部 - inline模式和card模式均支持 - 点击Select时不触发折叠事件(stopPropagation) - 条数统计仍显示全部日志数(不过滤) --- .../src/components/RealtimeLogPanel.tsx | 53 +++++++++++++++++-- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/web/frontend/src/components/RealtimeLogPanel.tsx b/web/frontend/src/components/RealtimeLogPanel.tsx index 9e17c37..89a5363 100644 --- a/web/frontend/src/components/RealtimeLogPanel.tsx +++ b/web/frontend/src/components/RealtimeLogPanel.tsx @@ -1,8 +1,25 @@ -import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react'; -import { Card, Spin, Tag, theme } from 'antd'; +import { useEffect, useMemo, useRef, useState, type CSSProperties, type ReactNode } from 'react'; +import { Card, Select, Spin, Tag, theme } from 'antd'; import { DownOutlined, UpOutlined } from '@ant-design/icons'; import type { RealtimeLog } from '../hooks/useWebSocketLogs'; +/** 日志等级定义:value 越小越严格(只显示该等级及以上) */ +const LOG_LEVELS = [ + { value: 0, label: '错误', key: 'error' }, + { value: 1, label: '警告+', key: 'warning' }, + { value: 2, label: '成功+', key: 'success' }, + { value: 3, label: '全部', key: 'info' }, +] as const; + +/** 等级权重映射 */ +const LEVEL_WEIGHT: Record = { + error: 0, + warning: 1, + success: 2, + info: 3, + debug: 4, +}; + interface RealtimeLogPanelProps { logs: RealtimeLog[]; connected?: boolean; @@ -32,13 +49,20 @@ export default function RealtimeLogPanel({ }: RealtimeLogPanelProps) { const { token } = theme.useToken(); const [visible, setVisible] = useState(defaultVisible); + const [levelFilter, setLevelFilter] = useState(3); // 默认:全部 const endRef = useRef(null); + // 按等级过滤日志 + const filteredLogs = useMemo(() => { + if (levelFilter >= 3) return logs; + return logs.filter((log) => (LEVEL_WEIGHT[log.level] ?? 3) <= levelFilter); + }, [logs, levelFilter]); + useEffect(() => { if (visible) { endRef.current?.scrollIntoView({ behavior: 'smooth' }); } - }, [logs, visible]); + }, [filteredLogs, visible]); const logColors: Record = { error: token.colorError, @@ -60,14 +84,14 @@ export default function RealtimeLogPanel({ ...bodyStyle, }} > - {logs.length === 0 ? ( + {filteredLogs.length === 0 ? ( spinWhenEmpty && connected ? ( ) : ( {emptyText} ) ) : ( - logs.map((log, index) => ( + filteredLogs.map((log, index) => (
({ value: l.value, label: l.label }))} + style={{ width: 80 }} + popupMatchSelectWidth={false} + /> + } style={{ flex: 1, overflow: 'hidden', ...style }} styles={{ body: { height: '100%', overflow: 'hidden', padding: 0 } }} > @@ -100,6 +134,15 @@ export default function RealtimeLogPanel({ onClick={() => collapsible && setVisible((value) => !value)} > {title} +