如何按列展示含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>
原有方案失效原因
- 重叠检测逻辑不完整:仅比较当前事件与遍历到的第一个不同事件,未覆盖所有重叠场景
- 布局方式错误:依赖margin的相对偏移而非绝对定位,易导致布局混乱
- 未动态分配列宽度:固定叠加70px的marginLeft,无法根据重叠列数调整宽度,最终挤在一行
内容的提问来源于stack exchange,提问作者togzi
相关产品推荐
相关产品推荐

