Files
LokiStack/frontend/package.json
T
opsx 53c7d64b35 feat(logs): 结果表虚拟滚动(@tanstack/react-virtual),>300 条时只渲染可视区
=== 一、为什么 ===
结果表原来是 entries.map 全量渲染真实 DOM。实测每条日志约 671B:
  1000 条页面 → 1000 个 DOM 节点,且大量消息带 whitespace-pre-wrap
  自动换行(Java 堆栈尤其长),浏览器需要排版全部行 → 滚动卡顿。
这是当前"每页条数"的真正瓶颈,比传 655KB 响应更影响体验。

=== 二、实现要点 ===
1. 新增 components/VirtualLogList.tsx
   - useVirtualizer(**独立滚动容器**,非 window 滚动)
     原因:window 虚拟滚动要把列表相对文档顶部偏移 scrollMargin 算准,
     上方工具条高度一变(快捷模板/域过滤条出现隐藏)就错位;
     独立容器内虚拟项位置只跟容器滚动量有关,不依赖页面布局。
   - 行高不一(消息会换行)→ 用 measureElement 实测每行高度
   - 容器 max-h-70vh,overscan 10
2. 抽公共行组件 LogRow,普通渲染与虚拟滚动**共用同一份 DOM 结构**,
   levelBadgeClass 逐字沿用原实现(含 info/warn/debug 配色),保证外观不变
3. 安全阀:VIRTUAL_THRESHOLD = 300
   - 默认每页 200 条仍走原来的普通渲染(行为已验证,零风险)
   - 用户主动把每页调到 500/1000 才启用虚拟滚动
4. 依赖:@tanstack/react-virtual ^3.14.23(package.json + lock 已更新,
   Docker 内 npm ci 可复现)

=== 三、验证 ===
- 本地 npx tsc -b --force 全量类型检查通过(rc=0)
- 产物 bundle 含 getVirtualItems / measureElement / getTotalSize
- 页面与主 JS 资源均 HTTP 200
- 平台健康 ok;hub latest 与 Pod digest 一致

镜像:lghost/lokistack:latest + v0.1 = sha256:25002cebc77c
2026-10-28 16:28:53 +00:00

83 lines
2.6 KiB
JSON

{
"name": "lokistack-frontend",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-accordion": "^1.2.12",
"@radix-ui/react-alert-dialog": "^1.1.15",
"@radix-ui/react-aspect-ratio": "^1.1.8",
"@radix-ui/react-avatar": "^1.1.11",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-hover-card": "^1.1.15",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-menubar": "^1.1.16",
"@radix-ui/react-navigation-menu": "^1.2.14",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-progress": "^1.1.8",
"@radix-ui/react-radio-group": "^1.3.8",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slider": "^1.3.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.2.6",
"@radix-ui/react-tabs": "^1.1.13",
"@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",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0",
"i18next": "^26.5.11",
"input-otp": "^1.4.2",
"lucide-react": "^0.562.0",
"next-themes": "^0.4.6",
"react": "^19.2.0",
"react-day-picker": "^9.13.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.70.0",
"react-i18next": "^17.1.9",
"react-resizable-panels": "^4.2.2",
"react-router": "^7.6.1",
"recharts": "^2.15.4",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"vaul": "^1.1.2",
"zod": "^4.3.5"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^5.1.1",
"autoprefixer": "^10.4.23",
"eslint": "^9.39.1",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.24",
"globals": "^16.5.0",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.19",
"tailwindcss-animate": "^1.0.7",
"tw-animate-css": "^1.4.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4"
}
}