From a3482ed06edcdeabc63b0d14c6762a5e9ff2373b Mon Sep 17 00:00:00 2001 From: AskaEth Date: Mon, 5 Oct 2026 15:37:04 +0800 Subject: [PATCH] =?UTF-8?q?fix(admin):=20=E6=97=A5=E5=BF=97=E7=B1=BB?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=BB=9F=E4=B8=80=E3=80=8C=E6=9C=80=E6=96=B0?= =?UTF-8?q?=E5=9C=A8=E4=B8=8A=E3=80=8D+=20=E9=A1=B6=E9=83=A8=E5=89=8D?= =?UTF-8?q?=E6=8F=92=E4=B8=8D=E9=A1=B6=E8=B7=91=E9=98=85=E8=AF=BB=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 审计 15 个日志/事件面后统一口径:改 3 个(渠道总日志 / 每账号消息日志弹窗 / 适配器专属页实时日志),7 个本已合规未动,聊天页**刻意保持正序**(本质是读对话,正序+自动滚底才自然) - 排序口径唯一出处 channelLogOrder.ts(历史+WS 追帧按 id 去重、时间倒序)+ 展示层不倒序(与 channelEventViews 同分工);三面共用 useChannelMessageLog 取数 - 新增 logScroll.ts:贴顶跟着最新走、已下读按新增高度补偿(**新条目不把正在看的内容顶跑**);窗口化容器的滚动锚定一并接上 - 后端无需改(三处消息日志读取本就是 created_at DESC,已核) - 诚实边界:滚动锚定的 DOM 那半只有纯函数有自动化证据(项目无 jsdom/Playwright,渲染测试跑 react-dom/server、effect 不执行)⇒ 已如实标注 - 变异实测:排序改回旧→新 / 取消补偿 / 渲染层再倒一次 / 改坏共享文案 —— 四处均打红 - ⚠️ 两处未改(在别的切片文件面内,已转交):flow/NodeDetail 节点实时日志 → 归 F2;Agents 任务日志 → 归 H1 --- frontend/web/src/admin/components.tsx | 15 +- frontend/web/src/admin/hooks.ts | 7 +- frontend/web/src/admin/logScroll.test.ts | 99 +++++++++++++ frontend/web/src/admin/logScroll.ts | 131 ++++++++++++++++++ .../modules/channels/AdapterMessageLog.tsx | 67 ++++----- .../admin/modules/channels/channelLogList.tsx | 72 ++-------- .../modules/channels/channelLogOrder.test.ts | 114 +++++++++++++++ .../admin/modules/channels/channelLogOrder.ts | 44 ++++++ .../channels/channelLogViews.render.test.ts | 99 +++++++++++++ .../modules/channels/channelLogViews.tsx | 68 +++++++++ .../modules/channels/useChannelMessageLog.ts | 36 +++++ 11 files changed, 653 insertions(+), 99 deletions(-) create mode 100644 frontend/web/src/admin/logScroll.test.ts create mode 100644 frontend/web/src/admin/logScroll.ts create mode 100644 frontend/web/src/admin/modules/channels/channelLogOrder.test.ts create mode 100644 frontend/web/src/admin/modules/channels/channelLogOrder.ts create mode 100644 frontend/web/src/admin/modules/channels/channelLogViews.render.test.ts create mode 100644 frontend/web/src/admin/modules/channels/channelLogViews.tsx create mode 100644 frontend/web/src/admin/modules/channels/useChannelMessageLog.ts diff --git a/frontend/web/src/admin/components.tsx b/frontend/web/src/admin/components.tsx index 002057d..700dbe3 100644 --- a/frontend/web/src/admin/components.tsx +++ b/frontend/web/src/admin/components.tsx @@ -1,4 +1,4 @@ -import { useMemo, type ReactNode } from "react"; +import { useMemo, type ReactNode, type RefObject } from "react"; import { EmptyState, Tag, type TagTone } from "../components/primitives"; import { Icon } from "../components/Icon"; import { useVirtualRows, VIRTUAL_ROW_ESTIMATE } from "./hooks"; @@ -198,6 +198,9 @@ export interface VirtualListProps { /** 少于该行数时全量渲染(与 `AdminTable` 同口径)。 */ threshold?: number; className?: string; + /** 外部滚动容器 ref:窗口化时调用方要在同一容器上做滚动锚定(「顶部前插不顶跑 + * 阅读位置」)时传进来;非窗口化时不生效(那种渲染没有滚动容器)。 */ + containerRef?: RefObject; } /** 窗口化的长列表:日志 / 消息流这类「条数多、还会被 WS 持续追帧」的面用它, @@ -212,10 +215,16 @@ export function VirtualList({ rowClassName = "", threshold = VIRTUAL_THRESHOLD, className = "", + containerRef, }: VirtualListProps): ReactNode { const count = items.length; const windowed = count > threshold; - const { range, containerRef, rowAttrs } = useVirtualRows({ count, enabled: windowed, rowHeight }); + const { range, containerRef: innerRef, rowAttrs } = useVirtualRows({ + count, + enabled: windowed, + rowHeight, + containerRef, + }); if (!windowed) { return ( @@ -231,7 +240,7 @@ export function VirtualList({ const from = range.start; return ( -
+