fix(monitor_web): 消息按 timestamp 排序插入,不再按到达顺序倒序

## 痛点

用户截图显示消息列表完全乱序:
  18:13:54 (top)
  18:21:59
  18:19:46
  18:21:51 (bottom)
无法按时间阅读。

## 根因

addMsg 行 2216:
  M.insertBefore(d, M.firstChild);

每条新 msg 永远插最前面, **完全按到达顺序倒序**:
- SSE 推送的实时消息 (timestamp 大) 跟 hidden 补抓的旧消息 (timestamp 小)
  按到达顺序穿插
- /api/history 初始化时按 ASC 顺序 forEach addMsg 也是这种倒序行为
- 结果显示顺序跟 timestamp 没有任何关系

## 修复

按 timestamp 找正确插入位置 (降序: 大 ts 在顶, 符合"最新在上"日志流惯例):

  d.dataset.ts = m.timestamp || 0;
  // 遍历现有 children, 找第一个 ts 比新消息小的位置插入
  for(let i=0; i<kids.length; i++){
    const existingTs = +(kids[i].dataset.ts || 0);
    if(ts > existingTs){
      M.insertBefore(d, kids[i]);
      inserted = true;
      break;
    }
  }
  if(!inserted) M.appendChild(d);  // 比所有都早, 放最底

O(n) 但常见情况 (新消息最大 ts) O(1) 命中。200 条上限 fast enough。

## 实测

修复前:
  18:13:54 / 18:21:59 / 18:19:46 / 18:21:51  ← 乱

修复后预期 (降序):
  18:21:59 / 18:21:51 / 18:19:46 / 18:13:54  ← 最新在顶

测试 185/185 通过 (改动只动 addMsg 排序逻辑)。
This commit is contained in:
ylytdeng
2026-05-17 18:24:45 +08:00
parent a01d326f40
commit a1e79f764d

View File

@@ -2204,6 +2204,7 @@ function addMsg(m, animate){
let contentHtml = renderContent(m);
const dk=m.timestamp+'|'+(m.username||m.chat);
d.dataset.ts = m.timestamp || 0; // 用于按 timestamp 排序定位
d.innerHTML=`<div class="msg-header"><span class="msg-time">${m.time}</span><span class="${cc}">${esc(m.chat)}</span>${sn}<div class="msg-r"><span class="msg-type">${m.type_icon} ${m.type}</span>${ur}</div></div><div class="msg-content" data-key="${dk}">${contentHtml}</div>`;
// 通知匹配检查
@@ -2213,7 +2214,21 @@ function addMsg(m, animate){
setTimeout(()=>d.classList.remove('notify-hl'), 10000);
}
M.insertBefore(d, M.firstChild);
// 按 timestamp 找正确插入位置 (降序: 大 ts 在顶, 小 ts 在底)
// 之前的 bug: insertBefore(d, M.firstChild) 永远插最前面,
// 导致 SSE 实时消息 + hidden 路径补抓的旧消息混在一起时顺序乱。
const ts = +d.dataset.ts;
const kids = M.children;
let inserted = false;
for(let i=0; i<kids.length; i++){
const existingTs = +(kids[i].dataset.ts || 0);
if(ts > existingTs){
M.insertBefore(d, kids[i]);
inserted = true;
break;
}
}
if(!inserted) M.appendChild(d); // 比所有现有都早, 放最底
if(animate){
setTimeout(()=>d.classList.remove('hl'), 3000);