import { useRef } from "react"; import { Button, Tag } from "../../../components/primitives"; import { Notice, VirtualList, VIRTUAL_THRESHOLD } from "../../components"; import { useTopAnchoredScroll } from "../../logScroll"; import { PLATFORM_LABEL } from "../channelFields"; import { platformOf } from "./channelUtils"; import { ChannelLoadError } from "./channelStatusView"; import { ChannelLogEmpty, ChannelLogRow, MessageLogMeta } from "./channelLogViews"; import { useChannelMessageLog } from "./useChannelMessageLog"; /** 一行消息的估算高度(px):标签行 + 正文数行,实测后会被真实值取代。 */ const MESSAGE_ROW_ESTIMATE = 76; /** 单适配器消息日志(design/17 §2「实时日志」)。数据面与 `ChannelLogList` 共用 * `useChannelMessageLog`(历史分页 + `channel_persisted` WS 追帧 + 兜底轮询, * **最新在上**),区别只在渲染:消息是本模块最长的列表,也是 WS 每来一帧就要整表 * 重渲染的面,因此走窗口化——只挂载视口内的行(裁决 16.4.3「按效率最高的做」)。 * * 滚动维持:窗口化列表的滚动容器在列表**内部**,因此把它显式交给 * `useTopAnchoredScroll`(新条目落在顶部时不顶跑用户正在读的位置)。加载 / 空 / * 失联三种状态照旧分开。 */ export function AdapterMessageLog({ adapter, limit = 200, }: { adapter: string; limit?: number; }): React.ReactNode { const { msgs, loading, error, reload, hasData } = useChannelMessageLog(adapter, limit); const scrollerRef = useRef(null); const anchorRef = useTopAnchoredScroll(msgs[0]?.id ?? null, msgs.length, scrollerRef); const platform = platformOf(adapter); return (
{PLATFORM_LABEL[platform] ?? platform} {adapter}

消息日志

GET /api/v1/admin/channels/messages
{loading ? ( 加载中… ) : error && !hasData && msgs.length === 0 ? ( ) : ( <> {msgs.length === 0 ? ( ) : ( <>
VIRTUAL_THRESHOLD ? ( 虚拟滚动 · 仅挂载视口内的行 ) : null } />
m.id} renderRow={(m) => } rowClassName="pb-2" rowHeight={MESSAGE_ROW_ESTIMATE} maxHeight="min(60vh, 640px)" containerRef={scrollerRef} /> )} )}
); }