PAYATHON 2026

Vis.js Timeline 按 payment code 垂直堆叠重叠 items

支付小周

结论

可以用 Timeline 原生的 subgroup 机制实现。把 paymentCodeId 映射到 item 的 subgroup,并同时开启普通堆叠和 subgroup 内堆叠:

const options = {
  stack: true,
  stackSubgroups: true
};

同一个 group 中的 items 会按 payment code 分到不同的 subgroup。同一 payment code 内,时间重叠的 items 会垂直堆叠;不同 payment codes 不会共用同一行。

paymentCodeId 是业务字段,Timeline 不会自动用它控制布局,需要显式赋值给 Vis.js 能识别的 subgroup 属性。

实现方式

创建 Timeline 前,先转换 items:

const items = [
  {
    id: 1,
    group: "section_1",
    subgroup: "payment_1",
    start: "2025-08-26 09:00",
    end: "2025-08-26 11:00",
    paymentCodeId: 1,
    content: "Item A"
  },
  {
    id: 2,
    group: "section_1",
    subgroup: "payment_1",
    start: "2025-08-26 10:30",
    end: "2025-08-26 12:00",
    paymentCodeId: 1,
    content: "Item B"
  },
  {
    id: 3,
    group: "section_1",
    subgroup: "payment_2",
    start: "2025-08-26 09:00",
    end: "2025-08-26 10:00",
    paymentCodeId: 2,
    content: "Item C"
  },
  {
    id: 4,
    group: "section_1",
    subgroup: "payment_2",
    start: "2025-08-26 09:30",
    end: "2025-08-26 10:30",
    paymentCodeId: 2,
    content: "Item D"
  }
];

const groups = [
  {
    id: "section_1",
    content: "Section 1"
  }
];

const options = {
  stack: true,
  stackSubgroups: true
};

new vis.Timeline(
  document.getElementById("timeline"),
  items,
  groups,
  options
);

也可以根据原始数据自动生成 subgroup:

const timelineItems = reservations.map((reservation) => ({
  ...reservation,
  subgroup: `payment_${reservation.paymentCodeId}`
}));

建议为值添加前缀,不要直接使用数字:

subgroup: `payment_${item.paymentCodeId}`

这样可以避免 1、"1" 或其他业务维度的值产生不易察觉的标识冲突。

为什么 stack: false 无法达到目标

stack: false 会关闭重叠 item 的自动垂直堆叠。即使设置了 subgroup,同一 subgroup 中互相重叠的 items 也无法按预期分层。

这两个配置的作用不同:

  • stack: true:允许重叠的 items 垂直堆叠。
  • stackSubgroups: true:在每个 subgroup 内分别计算堆叠。

因此,这个场景不能使用 stack: false。

控制 payment code 的垂直顺序

如果需要固定 subgroup 的上下顺序,可以在 group 上设置 subgroupOrder。它不是 Timeline 的顶层 option,也不应直接作为普通 item 的布局参数使用。

一种常见做法是让 items 携带排序字段,再在 group 中指定这个字段:

const items = reservations.map((reservation) => ({
  ...reservation,
  subgroup: `payment_${reservation.paymentCodeId}`,
  subgroupSortOrder: Number(reservation.paymentCodeId)
}));

const groups = [
  {
    id: "section_1",
    content: "Section 1",
    subgroupOrder: "subgroupSortOrder"
  }
];

完整配置仍需启用 subgroup stacking:

const options = {
  stack: true,
  stackSubgroups: true
};

不同 Vis.js/vis-timeline 版本支持的 subgroupOrder 形式可能不同。如果当前版本不接受字段名,可以按照该版本的 API 使用比较函数,例如:

const groups = [
  {
    id: "section_1",
    content: "Section 1",
    subgroupOrder(a, b) {
      return Number(a.paymentCodeId) - Number(b.paymentCodeId);
    }
  }
];

比较函数接收的具体对象结构,应以项目实际使用版本为准。

注意事项

  • group 和 subgroup 是两个层级。这里仍将 section_1 作为 group,将 payment code 作为 subgroup。
  • 同一 payment code 的所有 items 必须生成完全相同的 subgroup 值。
  • 不同 section 可以重复使用相同的 subgroup 标识,因为 subgroup 布局会在各自的 group 内计算。
  • subgroup 只负责划分布局区域。如果需要显示 “Payment Code 1” 之类的标签,还要通过 item 内容、CSS、背景 item 或额外的 group 结构实现。subgroup 本身不一定会自动显示标题。
  • 如果使用的旧版 vis.js 不支持 stackSubgroups,需要升级到支持该选项的 vis-timeline 版本,或者把 payment code 提升为真正的 group。不要只根据报错猜测参数位置,应先核对项目实际安装版本的 API。

备注:内容仅供参考。