ChatMessageList (消息列表)
会话的消息流:同一发送人连续消息合并成组、日期分隔条吸顶、「以下为未读消息」分隔线并从那里打开;向上加载更早消息不跳、在底部时新消息自动贴底、不在底部时回到底部按钮累计未读;scrollToSeq 跳转并高亮;右键 / 长按菜单与左滑回复。
消息直接吃 chat API 的 JSON(KunChatMessage,id 是字符串)。滚动容器是 flex-direction: column-reverse,滚动原点在底部:服务端渲染的 HTML 就停在最新消息, 上方的任何增长(更早的一页、渲染进来的行、解码完成的图片)都不改变视图,也就不写一次 scrollTop—— iOS 的惯性滚动不会被打断。只有视口下方的变化才需要手动锚定。
私聊:从未读处打开
last-read-seq 取打开会话时的已读位置,之后保持不变——分隔线就停在那里,列表从它开始显示; @read 报告真正看到的位置,由站点去标记已读。右键或长按消息弹出菜单,触屏上左滑回复。
已读到 seq 16
Direct.vue
群聊、服务消息与跳转
Group.vue
双向翻页与窗口
向上滚到顶附近发出 load-older,更早的一页出现在上方而视图不动; 跳到没载入的消息时发出 jump,站点按 around_seq 取一个窗口再调 scrollToSeq;窗口不含最新消息时 has-newer 为真,回到底部按钮发出 latest。
已载入 seq 0–0,共 0 条
Paging.vue
几千条消息
不引入虚拟滚动库:屏幕外的行用 content-visibility: auto 跳过布局与绘制, contain-intrinsic-size: auto 记住已渲染过的高度,回滚不跳。
Thousands.vue
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| currentUserId * | string | — | The viewer's id. Their messages sit on the right, and a pending one they send (`status: 'sending'`) scrolls the list to the bottom. |
| messages * | KunChatMessage[] | — | Messages in display order, oldest first. Pending ones go last, carrying `client_message_id` and `status`. |
| actions | ((message: KunChatMessage, own: boolean) => KunChatMessageAction[]) | — | Which actions the menu offers for a message — permissions are the site's call. `quote` shows only while text of the message is selected, `copy` only for a message with text. Default `['reply', 'copy']`. |
| ariaLabel | string | locale chat.messages | Accessible name of the scroll region. |
| groupWindow | number | 600 | Longest gap, in seconds, inside one run of a sender's messages. |
| hasNewer | boolean | false | The list is a window that does not reach the newest message (after a jump): scrolling near the bottom emits `load-newer`, and the scroll-down button emits `latest`. |
| hasOlder | boolean | false | More history exists above: scrolling near the top emits `load-older`. |
| kind | KunChatKind | "direct" | `group` shows names and avatars on others' messages; `direct` does not. |
| lastReadSeq | number | null | null | The viewer's read cursor as it was when the conversation opened. The unread divider goes above the first later message someone else sent, and the list opens there. Keep it fixed while the conversation is open, or the divider walks down as messages get read. |
| loadingNewer | boolean | false | The same for `load-newer`, at the bottom. |
| loadingOlder | boolean | false | A `load-older` request is in flight: a spinner shows on top and no second request goes out. Without it, the list waits for the first message to change before asking again. |
| peerReadSeq | number | null | null | The other side's read cursor: own messages at or below it show the double tick, unless they carry their own `status`. |
| reactionOptions | KunChatReactionOption[] | [] | The reaction vocabulary: the menu's quick row, and the art on reaction chips. |
| resolveMediaUrl | KunChatMediaUrlResolver | — | Turns a photo's hash into a URL. Required to show photos. |
| swipeToReply | boolean | true | Swipe a bubble left to reply, on touch screens. |
| timeZone | string | — | IANA zone for times and day boundaries. Pass it when server-rendering, or the server's zone and the reader's disagree and hydration mismatches. |
| unreadCount | number | — | Unread count on the scroll-down button. Defaults to the messages of others below the read position that the list has seen. |
| users | KunChatUser[] | [] | The `users` of the chat responses: senders, reply targets, actors. |
事件
| 事件 | 回调参数 | 说明 |
|---|---|---|
| action | action: string, message: KunChatMessage, detail: { quote?: KunChatReplyQuote; } | A menu action, or a swipe (`reply`). `detail.quote` is set for `quote`: the selected part of the message. `copy` has already been done. |
| jump | seq: number | A reply target that is not loaded was clicked: load around it (`around_seq`), then call `scrollToSeq(seq)`. |
| latest | — | The scroll-down button was pressed while `hasNewer`: reload the newest page, then call `scrollToBottom()`. |
| link | url: string, event: MouseEvent | A link in a message was clicked. |
| load-newer | — | Scrolled near the bottom while `hasNewer`: fetch the page after. |
| load-older | — | Scrolled near the top while `hasOlder`: fetch the page before. |
| mention | userId: string, event: MouseEvent | A mention in a message was clicked. |
| react | message: KunChatMessage, reaction: string | null | Set the viewer's reaction on a message, or remove it (null). |
| read | seq: number | Others' messages up to this seq have been on screen while the page was visible: mark them read. Only ever increases. |
| retry | message: KunChatMessage | Resend a failed message. |
| user-click | userId: string, event: MouseEvent | A sender's avatar or name was clicked. |
插槽
| 插槽 | 作用域 | 说明 |
|---|---|---|
| #empty | any | Shown when there are no messages. |
| #footer | any | Below the last message, e.g. a typing bubble. |
| #start | any | Above the first message once there is no older history, e.g. "this is the start of your conversation". |


