---
description: "一屏看清「谁 · 做了什么 · 什么时候」。逆时序排列、按天成组，左侧一条竖线串起圆点节点，右侧卡片挂事件正文。审计日志、项目动态、工单回溯都是它"
---

# 时间线（活动流）

一屏看清「谁 · 做了什么 · 什么时候」。逆时序排列、按天成组，左侧一条竖线串起圆点节点，右侧卡片挂事件正文。审计日志、项目动态、工单回溯都是它。

<iframe src="/skills/dashboard/assets/demo-timeline.html"
        style="width:100%;height:700px;border:1px solid #8884;border-radius:10px"
        loading="lazy" title="形状 demo：时间线"></iframe>

试这几下：

- 顶部**类型筛选**取消勾选「评论」—— 相关事件消失，若某天全被筛掉，整组也跟着隐藏
- 时间显示分三档：今天是**相对时间**（X 分钟/小时前）、昨天带「昨天 HH:MM」、更早退到 MM-DD HH:MM
- 底部**加载更早** —— 出骨架屏后追加更旧记录 + toast；两批到底后按钮消失，出「已到起点」结束态
- 把所有类型全取消 —— 显式提示「请至少勾选一个类型」，不留空白页

## 规矩

- **恒逆时序 + 按天分组**：每组一个日期标题（今天 / 昨天 / MM-DD），组内也按时间从新到旧。demo 用固定 `NOW` 锚点计算分组；真实版按 `createdAt desc` 从服务端取，前端按天 bucket
- **圆点连线纯装饰，语义走列表**：分组标题下用 `<ol>/<li>`，圆点/图标带 `aria-hidden`，屏幕阅读器读到的是「N 项事件」而非「装饰图」
- **事件一个形状**：`{ id, type, actor, action, at, meta?, icon, tone }`；tone 用主题令牌（primary / success / muted / warn）映射颜色，不硬编码 hex
- **分页拉更早**：demo 是内存里两批模拟游标；真实版按 `add-infinite-list` 走服务端游标 —— `WHERE createdAt < :cursor ORDER BY createdAt DESC LIMIT N`，长历史再套虚拟滚动
- **相对时间要稳定**：demo 用简易 formatter + 固定 `NOW`；真实版建议 `Intl.RelativeTimeFormat` + `useEffect` 定时刷新分钟级差，并把「精确 HH:MM」放进 `<time title>` 或 hover tooltip

> 蓝本：`add-timeline.md`[^add-timeline]（open-dashboard @ `aa9815f`，MIT，Invariants 已消化进上面「规矩」）

??? abstract "demo 源码：`assets/demo-timeline.html`（自包含、未压缩）"

    ```html
    --8<-- "skills/dashboard/assets/demo-timeline.html"
    ```

[^add-timeline]: [`add-timeline.md`](https://github.com/ahpxex/open-dashboard/blob/aa9815f426e9520841c4aa15a341bfd9f866091f/.claude/skills/add-component/references/add-timeline.md)，2026-07-28 读。
