diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 3516175..3b1a2e1 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -35,6 +35,7 @@
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
+ "@tanstack/react-virtual": "^3.14.23",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
@@ -2916,6 +2917,33 @@
"version": "0.3.0",
"license": "MIT"
},
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.14.23",
+ "resolved": "https://registry.npmmirror.com/@tanstack/react-virtual/-/react-virtual-3.14.23.tgz",
+ "integrity": "sha512-Oyy58qQwdaQieA+A4WUDtnh9D1rv5FZvGdw8og9Xeyh88V6HH94vMSyV5i9ATIbYNe9t5Ix0WXRo9NOTpHDkYw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.19.8"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.19.8",
+ "resolved": "https://registry.npmmirror.com/@tanstack/virtual-core/-/virtual-core-3.19.8.tgz",
+ "integrity": "sha512-Oz/SYBo/aVPyQkwtOw7Q23myITUgFIof2glQqydYJAPL6mH2SkJDkvDKZTklB+7gv5jGLZQhyTcd7SO1A1EODQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"dev": true,
diff --git a/frontend/package.json b/frontend/package.json
index fef75ad..8f9a30e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -37,6 +37,7 @@
"@radix-ui/react-toggle": "^1.1.10",
"@radix-ui/react-toggle-group": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.8",
+ "@tanstack/react-virtual": "^3.14.23",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "^1.1.1",
diff --git a/frontend/src/pages/logs/components/LogResultsPanel.tsx b/frontend/src/pages/logs/components/LogResultsPanel.tsx
index 98ab653..e0acecd 100644
--- a/frontend/src/pages/logs/components/LogResultsPanel.tsx
+++ b/frontend/src/pages/logs/components/LogResultsPanel.tsx
@@ -5,20 +5,15 @@ import { Button } from '@/components/ui/button'
import { SearchableValueSelect } from '@/components/SearchableValueSelect'
import type { LogEntry } from '@/api/logs'
import { parseIngressAccessLog } from '@/lib/ingress-access-log'
-import { applyLabelFilter } from '../query-utils'
-import { HighlightedLogLine } from './HighlightedLogLine'
import { QueryEmptyState } from './QueryEmptyState'
+import { LogRow, VirtualLogList } from './VirtualLogList'
-function levelBadgeClass(level: string) {
- const value = (level || '').toLowerCase()
- if (value.includes('error') || value === 'err' || value === 'fatal' || value === 'critical') {
- return 'rounded bg-destructive/15 px-1 text-destructive'
- }
- if (value.includes('warn')) return 'rounded bg-warning/15 px-1 text-warning'
- if (value.includes('info')) return 'rounded bg-info/15 px-1 text-info'
- if (value.includes('debug') || value.includes('trace')) return 'rounded bg-muted px-1 text-muted-foreground'
- return 'rounded bg-muted px-1 text-muted-foreground'
-}
+/**
+ * 超过这个条数才启用虚拟滚动。
+ * 默认每页 200 条走普通渲染(已验证的外观/行为不变),
+ * 只有用户主动把每页调到 500/1000 时才走虚拟滚动 —— 降低改动风险。
+ */
+const VIRTUAL_THRESHOLD = 300
// 只保留原始日志视图;表格/折线/流图/饼图等可视化已迁至「日志分析」页(LogDetailSection)
@@ -67,15 +62,25 @@ export function LogResultsPanel({ entries, searched, hasSource, pageIndex, hasNe
{t('results.domainHint', { shown: visibleEntries.length, total: entries.length })}
}
- {entries.length === 0 ?
{t('results.domainNoMatch')}
:{t('results.domainNoMatch')}
: visibleEntries.length > VIRTUAL_THRESHOLD ? ( +