消息时间分组需前后端逻辑对齐,优先由后端返回带group_key的结构;前端兜底时须用date对象计算、避免字符串截取;dom需用语义化section包裹,标题支持i18n动态生成。

消息按时间分组不是加个 <h3></h3> 标题就完事,关键在「分组逻辑必须与后端对齐」。前端自己切分容易漏掉跨时区、夏令时、服务端聚合策略(比如“今天”从凌晨4点算起)等细节,导致同一批消息在不同设备上分组不一致。
后端返回带分组标识的结构最省心
让接口直接返回已分组的数据,比前端遍历重排更可靠。常见做法是:后端按 group_key 字段归类,例如 "today"、"yesterday"、"this_week"、"older",再把每组消息数组挂在其下:
[
{ "group": "today", "messages": [...] },
{ "group": "yesterday", "messages": [...] }
]
前端只需遍历渲染,不用判断日期差、不用处理 getDay() 周几偏移、也不用担心 UTC 和本地时间混用。如果后端做不到,才轮到前端兜底。
前端分组必须用 Date 对象做比较,别用字符串截取
常见错误是直接 msg.time.slice(0, 10) 拿日期字符串分组——这会崩在时区切换、ISO 格式不统一("2026-04-20" vs "2026/04/20")、或后端返回 UNIX 时间戳时。
- 统一转成
new Date(timestamp)实例,再取.toDateString()作为分组 key(它返回形如"Mon Apr 20 2026"的稳定字符串) - 避免用
.getFullYear() + '-' + .getMonth()拼接,月份是 0 起始,且没补零 - 如果需按“今天/昨天/本周”语义分组,用
Math.floor((now - msgDate) / (1000 * 60 * 60 * 24))算天数差,而不是比对getDate()
DOM 结构里每个分组必须用独立的语义容器
别把所有消息塞进一个 <div> 然后靠 CSS 隔开。正确结构是:<pre class="brush:php;toolbar:false;"><section aria-labelledby="group-today"><h3 id="group-today">今天</h3>
<article>...</article><article>...</article></section></pre>
<p>这样屏幕阅读器能正确识别分组边界,滚动锚点、键盘导航、CSS <code>:has() 选择器也才能生效。若用 <div> 堆砌,<code>aria-live 区域更新时可能跳过整个分组。
时间分组标题要支持本地化,且不能只靠 JS 渲染
“今天”“昨天”这类词不能硬编码在 HTML 里,得由 JS 动态生成并注入,否则 SSR 或爬虫看到的是空或英文。但更关键的是:这些标题必须可被翻译系统提取。
- 用
data-i18n-key="group_today"标记,交由 i18n 工具扫描 - 避免写死
document.querySelector('h3').textContent = '今天',改用模板函数如t('group_today') - 如果项目没 i18n,至少用
Intl.DateTimeFormat构造器动态生成,例如:new Intl.DateTimeFormat('zh-CN', { dateStyle: 'medium' }).format(new Date())
分组本身不难,难的是分组后的状态同步——比如用户标记某条“昨天”的消息为已读,该分组下只剩一条消息时,要不要自动收起?这个交互逻辑常被忽略,但它直接影响列表密度和操作预期。











