You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何按列展示含timeStart与timeEnd的数组对象?解决布局重叠问题

问题描述

我有一个包含多个数组的对象,每个数组都包含timeStart和timeEnd属性,示例如下:

{
  0: ['timeStart' => '17:30', 'timeEnd' => '18:20'],
  1: ['timeStart' => '17:45', 'timeEnd' => '19:20'],
  2: ['timeStart' => '18:30', 'timeEnd' => '20:00']
  ...
}

我需要将这些内容按列展示成重叠事件自动分列的时间轴样式。尝试通过检测时间重叠给每个重叠项添加marginLeft,但最终布局变成了一行,无法实现预期效果。

更新:我认为当前的实现方案存在问题,以下是我编写的代码:

现有JavaScript方法

setEvent(event, index) {

  let prevDate
  let prevMargin
  let prevHeight
  if(index < this.installersWorkSchedule.events.length && index > -1) {
    if (index > 0) {
      let prevEvent = this.installersWorkSchedule.events[index - 1]
      prevDate = prevEvent.date

      prevMargin = moment.duration(prevEvent.timeStart).asMinutes() - 480
      prevHeight = moment.duration(prevEvent.timeEnd).asMinutes() - moment.duration(prevEvent.timeStart).asMinutes()

    }

    const margin = event.date === prevDate ?
        -(parseInt(prevMargin) + parseInt(prevHeight)) + (moment.duration(event.timeStart).asMinutes() - 480)
        :
        moment.duration(event.timeStart).asMinutes() - 480
    const height = moment.duration(event.timeEnd).asMinutes() - moment.duration(event.timeStart).asMinutes()
    let marginLeft = 0
    for (let ev of this.installersWorkSchedule.events) {
      if (ev !== event) {
        let currentMargin = moment.duration(ev.timeStart).asMinutes() - 480
        let currentHeight = moment.duration(ev.timeEnd).asMinutes() - moment.duration(ev.timeStart).asMinutes()
        let checkMargin = event.date === ev.date ?
            -(currentMargin + currentHeight) + (moment.duration(event.timeStart).asMinutes() - 480)
            :
            moment.duration(event.timeStart).asMinutes() - 480
        if (checkMargin < 0) {
          marginLeft += 70

        }
      } else {
        break
      }
    }

    return [margin + 'px', height + 'px', marginLeft + 'px']
  }

  return ['0', '0', '0']

},

现有Vue模板

<template v-for="(event, index) in installersWorkSchedule.events">

              <div
                  @click="goTo(event.orderId)"
                  :id="index"
                  class="rounded text-left text-sm  p-1 font-bold justify-start cursor-pointer transform transition duration-500 hover:scale-110 hover:z-50"
                  v-if="event.date === formattedDate(day)"
                  :style="{
                    marginTop: setEvent(event, index)[0],
                    height: setEvent(event, index)[1],
                    marginLeft:  setEvent(event, index)[2]

                  }"
              >
              </div>
            </template>

希望得到正确的实现方法,解决布局问题。


解决方案

核心思路

要实现重叠事件分列展示,需先按日期分组事件,再为每个日期内的事件计算列位置,最后通过绝对定位实现精准布局,而非依赖margin偏移。

步骤1:按日期分组并预处理事件

将事件按日期分组,同时把时间转换为分钟数,方便后续时间比较:

// 按日期分组事件,转换时间为分钟数便于计算
groupEventsByDate() {
  const grouped = {};
  this.installersWorkSchedule.events.forEach(event => {
    const date = event.date;
    if (!grouped[date]) {
      grouped[date] = [];
    }
    grouped[date].push({
      ...event,
      startMin: moment.duration(event.timeStart).asMinutes(),
      endMin: moment.duration(event.timeEnd).asMinutes()
    });
  });
  // 每个日期的事件按开始时间排序
  Object.values(grouped).forEach(events => {
    events.sort((a, b) => a.startMin - b.startMin);
  });
  return grouped;
}

步骤2:计算事件的布局信息

为每个事件分配列位置,同时计算top、height、left、width等样式属性:

// 计算每个事件的布局参数
calculateEventLayout(events) {
  const columns = []; // 存储各列的事件列表
  return events.map(event => {
    // 找到第一个当前事件不重叠的列
    let columnIndex = columns.findIndex(col => {
      const lastEvent = col[col.length - 1];
      return lastEvent.endMin <= event.startMin;
    });
    // 无可用列则新增一列
    if (columnIndex === -1) {
      columns.push([event]);
      columnIndex = columns.length - 1;
    } else {
      columns[columnIndex].push(event);
    }
    const columnCount = columns.length;
    // 垂直偏移:以8:00为起点(480分钟=8*60)
    const top = `${event.startMin - 480}px`;
    // 事件高度
    const height = `${event.endMin - event.startMin}px`;
    // 水平偏移和宽度:按列数均分容器宽度
    const left = `${(columnIndex / columnCount) * 100}%`;
    const width = `${100 / columnCount}%`;
    return { ...event, top, height, left, width };
  });
}

步骤3:在Vue组件中整合逻辑

通过计算属性处理数据,为每个事件添加布局信息:

computed: {
  processedEvents() {
    const grouped = this.groupEventsByDate();
    // 为每个日期的事件添加布局参数
    Object.keys(grouped).forEach(date => {
      grouped[date] = this.calculateEventLayout(grouped[date]);
    });
    return grouped;
  }
}

步骤4:修改模板渲染

使用相对定位的容器包裹事件,通过绝对定位实现布局:

<!-- 日期容器:相对定位,高度对应8:00到22:00的14小时(每小时60px) -->
<div 
  class="day-container" 
  style="position: relative; height: 840px; border-left: 1px solid #eee;"
>
  <div
    v-for="event in processedEvents[formattedDate(day)]"
    :key="event.orderId"
    @click="goTo(event.orderId)"
    class="event-item rounded text-left text-sm p-1 font-bold cursor-pointer transform transition duration-500 hover:scale-110 hover:z-50 bg-blue-500 text-white"
    :style="{
      position: 'absolute',
      top: event.top,
      height: event.height,
      left: event.left,
      width: event.width,
      boxSizing: 'border-box'
    }"
  >
    {{ event.timeStart }} - {{ event.timeEnd }}
  </div>
</div>

原有方案失效原因

  1. 重叠检测逻辑不完整:仅比较当前事件与遍历到的第一个不同事件,未覆盖所有重叠场景
  2. 布局方式错误:依赖margin的相对偏移而非绝对定位,易导致布局混乱
  3. 未动态分配列宽度:固定叠加70px的marginLeft,无法根据重叠列数调整宽度,最终挤在一行

内容的提问来源于stack exchange,提问作者togzi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 01:15:55