ChatMessageList (消息列表)

会话的消息流:同一发送人连续消息合并成组、日期分隔条吸顶、「以下为未读消息」分隔线并从那里打开;向上加载更早消息不跳、在底部时新消息自动贴底、不在底部时回到底部按钮累计未读;scrollToSeq 跳转并高亮;右键 / 长按菜单与左滑回复。

消息直接吃 chat API 的 JSON(KunChatMessage,id 是字符串)。滚动容器是 flex-direction: column-reverse,滚动原点在底部:服务端渲染的 HTML 就停在最新消息, 上方的任何增长(更早的一页、渲染进来的行、解码完成的图片)都不改变视图,也就不写一次 scrollTop—— iOS 的惯性滚动不会被打断。只有视口下方的变化才需要手动锚定。

私聊:从未读处打开

last-read-seq 取打开会话时的已读位置,之后保持不变——分隔线就停在那里,列表从它开始显示; @read 报告真正看到的位置,由站点去标记已读。右键或长按消息弹出菜单,触屏上左滑回复。

2025年12月29日
雪之下小春:
在吗在吗
雪之下小春:
你之前说的那个补丁我装上了,但是一进游戏就乱码
你:
十有八九是区域设置的问题。用 Locale Emulator 转区再开试试,或者直接这样启动:
你:
shell
LANG=ja_JP.UTF-8 wine "Game.exe"
雪之下小春:
好了!!转区之后正常了
雪之下小春:www.moyu.moe《星空鉄道とシロの旅》汉化补丁 v0.9
顺便问一下,这个补丁的汉化是完整的吗?moyu 上写的是 0.9 版
你:
主线是完整的,只有两个番外还没翻。作者说下个月会补上
昨天
雪之下小春:
通关了!!!给你看我最喜欢的几张 CG
雪之下小春:
海边那段真的哭死我了
雪之下小春:
最后那个反转你猜到了吗?原来列车长就是白本人
你:
猜到一半,第三章那封信就有暗示了
你:
推荐你接着玩同社的前作,世界观是连着的。论坛有人写过详细的考据:https://www.kungal.com/topic/1024
已编辑
你:
不过前作的系统有点老,存档记得一个位多开几个位
今天
雪之下小春:
早上好~
以下为未读消息
雪之下小春:
昨天你说的前作我下好了,片头曲好好听
雪之下小春:
对了,@鲲 周末的线下聚会你去吗?

已读到 seq 16

Direct.vue

群聊、服务消息与跳转

昨天
你 创建了群组「Galgame 汉化交流」
你 邀请了 雪之下小春、Ayase和樱小路露娜
你:
欢迎各位!群规就两条:聊剧情请把关键内容用剧透遮起来,比如凶手是管家;求资源去论坛发帖。
你 置顶了「欢迎各位!群规就两条:聊剧情请把关键内容用剧透遮起来,比如⠿…」
来了来了
Ayase
Ayase:
先问个问题:这周的机翻组进度怎么样了
第二章校对完了,第三章还在润色
樱小路露娜
樱小路露娜:
有兴趣帮忙校对的私聊我
今天
雪之下小春
我可以帮忙!日语 N2 水平够吗
樱小路露娜
够的,欢迎~
樱小路露娜 将群名改为「Galgame 汉化交流 · 校对组」
Group.vue

双向翻页与窗口

向上滚到顶附近发出 load-older,更早的一页出现在上方而视图不动; 跳到没载入的消息时发出 jump,站点按 around_seq 取一个窗口再调 scrollToSeq;窗口不含最新消息时 has-newer 为真,回到底部按钮发出 latest。

已载入 seq 0–0,共 0 条
还没有消息
Paging.vue

几千条消息

不引入虚拟滚动库:屏幕外的行用 content-visibility: auto 跳过布局与绘制, contain-intrinsic-size: auto 记住已渲染过的高度,回滚不跳。

点上面的按钮载入 3000 条消息
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']`.
ariaLabelstringlocale chat.messagesAccessible name of the scroll region.
groupWindownumber600Longest gap, in seconds, inside one run of a sender's messages.
hasNewerbooleanfalseThe 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`.
hasOlderbooleanfalseMore history exists above: scrolling near the top emits `load-older`.
kindKunChatKind"direct"`group` shows names and avatars on others' messages; `direct` does not.
lastReadSeqnumber | nullnullThe 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.
loadingNewerbooleanfalseThe same for `load-newer`, at the bottom.
loadingOlderbooleanfalseA `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.
peerReadSeqnumber | nullnullThe other side's read cursor: own messages at or below it show the double tick, unless they carry their own `status`.
reactionOptionsKunChatReactionOption[][]The reaction vocabulary: the menu's quick row, and the art on reaction chips.
resolveMediaUrlKunChatMediaUrlResolver—Turns a photo's hash into a URL. Required to show photos.
swipeToReplybooleantrueSwipe a bubble left to reply, on touch screens.
timeZonestring—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.
unreadCountnumber—Unread count on the scroll-down button. Defaults to the messages of others below the read position that the list has seen.
usersKunChatUser[][]The `users` of the chat responses: senders, reply targets, actors.

事件

事件回调参数说明
actionaction: 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.
jumpseq: numberA 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()`.
linkurl: string, event: MouseEventA 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.
mentionuserId: string, event: MouseEventA mention in a message was clicked.
reactmessage: KunChatMessage, reaction: string | nullSet the viewer's reaction on a message, or remove it (null).
readseq: numberOthers' messages up to this seq have been on screen while the page was visible: mark them read. Only ever increases.
retrymessage: KunChatMessageResend a failed message.
user-clickuserId: string, event: MouseEventA sender's avatar or name was clicked.

插槽

插槽作用域说明
#emptyanyShown when there are no messages.
#footeranyBelow the last message, e.g. a typing bubble.
#startanyAbove the first message once there is no older history, e.g. "this is the start of your conversation".