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

如何动态实现agendaDay的rowspan?如何让资源表头与事件宽度一致?

针对fullCalendar AgendaDay视图的两个问题解决方案

我来帮你搞定这两个fullCalendar的问题,下面是具体的解决方案,都是基于你提供的代码来调整的:


一、动态实现AgendaDay视图的rowspan效果

AgendaDay结合资源分组(groupByDateAndResource)时,rowspan主要分资源表头合并和事件单元格合并两种场景,分别给你对应的实现方式:

1. 资源表头的rowspan(同一组资源合并表头)

如果你的资源需要按组合并表头,先给资源数据添加group标识(比如{id: 'res1', title: 'Room 1', group: 'Class A'}),然后通过resourceRender钩子动态设置rowspan:

resourceRender: function(resourceObj, labelTds, bodyTds) {
  // 先清除之前的rowspan,避免重复渲染冲突
  labelTds.removeAttr('rowspan');
  
  // 筛选同一组的所有资源
  const sameGroupResources = $('#day-calendar').fullCalendar('getResources').filter(r => r.group === resourceObj.group);
  if (sameGroupResources.length > 1 && resourceObj === sameGroupResources[0]) {
    // 给组内第一个资源的表头设置rowspan
    labelTds.attr('rowspan', sameGroupResources.length);
    // 隐藏同组其他资源的表头
    sameGroupResources.slice(1).forEach(r => {
      $('#day-calendar .fc-resource[data-resource-id="' + r.id + '"] .fc-resource-label').hide();
    });
  }
}

2. 事件单元格的rowspan(同一资源下连续事件合并)

如果要把同一资源下的多个连续事件合并成一个单元格,在eventAfterAllRender钩子中计算事件时长并修改rowspan:

eventAfterAllRender: function(view) {
  // 按资源ID分组事件
  const resourceEventsMap = {};
  $('#day-calendar .fc-event').each(function() {
    const resourceId = $(this).data('resourceId');
    if (!resourceEventsMap[resourceId]) resourceEventsMap[resourceId] = [];
    resourceEventsMap[resourceId].push(this);
  });

  // 处理每组事件的合并
  Object.values(resourceEventsMap).forEach(events => {
    if (events.length <= 1) return;
    
    // 计算合并后的rowspan:总时长 / 单个时间槽时长
    const firstEventEl = $(events[0]);
    const event = firstEventEl.data('event');
    const totalMinutes = moment.duration(event.end.diff(event.start)).asMinutes();
    const slotMinutes = view.slotDuration.asMinutes();
    const rowspan = Math.ceil(totalMinutes / slotMinutes);

    // 设置第一个事件的rowspan,隐藏其他事件
    firstEventEl.attr('rowspan', rowspan);
    events.slice(1).forEach(el => $(el).hide());
  });
}

二、修改特定列表头样式 + 资源表头与事件宽度同步

1. 动态修改带标题的特定列表头样式

fullCalendar的资源表头有fc-resource-label类,你可以通过资源标题或资源ID精准定位,然后修改样式:

  • 静态CSS方式(直接写样式):
/* 匹配标题包含"Venue 1"的表头 */
.fc-resource-label:contains("Venue 1") {
  background: #f5f5f5;
  font-weight: bold;
}

/* 匹配特定ID的资源表头 */
.fc-resource[data-resource-id="res-2"] .fc-resource-label {
  color: #2f54eb;
}
  • 动态JS方式(事件加载后修改):
    在eventsRender钩子中,根据事件关联的资源动态调整:
eventsRender: function(events, view) {
  // 示例:修改第一个事件对应的资源表头样式
  if (events.length === 0) return;
  const targetResourceId = events[0].resourceId;
  $('#day-calendar .fc-resource[data-resource-id="' + targetResourceId + '"] .fc-resource-label')
    .css('background-color', '#e6f7ff')
    .addClass('custom-header-style');
}

2. 让资源表头与事件宽度保持一致

你已经通过eventAfterRender把事件设为200px,只需在eventAfterAllRender中同步资源表头和列的宽度即可:

eventAfterAllRender: function(view) {
  // 获取事件的实际宽度(如果是固定200px也可以直接写死)
  const eventWidth = $('#day-calendar .fc-event').first().width() || 200;
  // 同步资源表头和资源列的宽度
  $('#day-calendar .fc-resource-label, #day-calendar .fc-resource').css('width', `${eventWidth}px`);
}

如果需要动态适配容器宽度(比如资源数量变化时自动调整),可以先计算宽度再同步:

// 先计算动态宽度:容器宽度 / 资源数量
const containerWidth = $('#day-calendar').width();
const resourceCount = $('#day-calendar').fullCalendar('getResources').length;
const dynamicWidth = Math.floor(containerWidth / resourceCount);

// 在eventAfterRender中设置事件宽度
eventAfterRender: function(event, element, view) {
  $(element).css('width', `${dynamicWidth}px`);
},

// 在eventAfterAllRender中同步表头宽度
eventAfterAllRender: function(view) {
  $('#day-calendar .fc-resource-label, #day-calendar .fc-resource').css('width', `${dynamicWidth}px`);
}

整合后的完整代码

把上面的逻辑整合到你的initCalendar函数中:

function initCalendar(myresources) { 
  $('#day-calendar').fullCalendar({ 
    defaultView: 'agendaDay', 
    defaultDate: $scope.setDate, 
    aspectRatio: 1.8, 
    editable: true, 
    selectable: true, 
    eventLimit: true, 
    slotEventOverlap: true, 
    minTime: $scope.lessonStartTime, 
    maxTime: $scope.lessonEndTime, 
    contentHeight: 'auto', 
    slotLabelInterval: $scope.lessonTimeInterval, 
    slotLabelFormat: "HH:mm:A", 
    header: { 
      left: '', 
      center: 'Time', 
      right: '' 
    }, 
    views: { 
      agendaDay: { 
        groupByDateAndResource: true, 
      } 
    }, 
    selectHelper: true, 
    resourceLabelText: "Venue", 
    allDaySlot: false, 
    resources: myresources, 
    events: eventsData, 
    eventAfterRender: function(event, element, view) { 
      $(element).css('width', '200px'); 
    },
    // 资源表头rowspan逻辑
    resourceRender: function(resourceObj, labelTds, bodyTds) {
      labelTds.removeAttr('rowspan');
      const sameGroupResources = $('#day-calendar').fullCalendar('getResources').filter(r => r.group === resourceObj.group);
      if (sameGroupResources.length > 1 && resourceObj === sameGroupResources[0]) {
        labelTds.attr('rowspan', sameGroupResources.length);
        sameGroupResources.slice(1).forEach(r => {
          $('#day-calendar .fc-resource[data-resource-id="' + r.id + '"] .fc-resource-label').hide();
        });
      }
    },
    // 事件合并+宽度同步逻辑
    eventAfterAllRender: function(view) {
      // 同步资源表头与事件宽度
      const eventWidth = $('#day-calendar .fc-event').first().width() || 200;
      $('#day-calendar .fc-resource-label, #day-calendar .fc-resource').css('width', `${eventWidth}px`);

      // 事件单元格合并逻辑(可选)
      const resourceEventsMap = {};
      $('#day-calendar .fc-event').each(function() {
        const resourceId = $(this).data('resourceId');
        if (!resourceEventsMap[resourceId]) resourceEventsMap[resourceId] = [];
        resourceEventsMap[resourceId].push(this);
      });
      Object.values(resourceEventsMap).forEach(events => {
        if (events.length <= 1) return;
        const firstEventEl = $(events[0]);
        const event = firstEventEl.data('event');
        const totalMinutes = moment.duration(event.end.diff(event.start)).asMinutes();
        const slotMinutes = view.slotDuration.asMinutes();
        const rowspan = Math.ceil(totalMinutes / slotMinutes);
        firstEventEl.attr('rowspan', rowspan);
        events.slice(1).forEach(el => $(el).hide());
      });
    },
    // 动态修改特定表头样式
    eventsRender: function(events, view) {
      if (events.length === 0) return;
      const targetResourceId = events[0].resourceId;
      $('#day-calendar .fc-resource[data-resource-id="' + targetResourceId + '"] .fc-resource-label')
        .css('background-color', '#e6f7ff')
        .addClass('custom-header-style');
    }
  }) 
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:10:06