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

如何实现FullCalendar事件超出工作时间后自动顺延至下一个工作日?

问题描述

我用React结合FullCalendar开发任务计划应用,已实现带特定时长的可拖拽事件。当前问题:当事件超出当天工作时间范围时,希望自动顺延至下一个工作日继续,而非限制在当天结束或显示在非工作时间里导致时间逻辑混乱。

已设置slotMinTime、slotMaxTime和自定义businessHours,试过nextDayThreshold但没达到预期。期望事件全部时长都显示在日历可见工作时间内,不隐藏在非工作时段。

现有代码

可拖拽事件配置

const containerEl = document.querySelector("#events");

let draggable = new Draggable(containerEl, {
    itemSelector: ".event",
    eventData: (eventEl) => {
        return {
            id: eventEl.innerText,
            title: eventEl.innerText,
            duration: eventEl.getAttribute('eventduration'),
            backgroundColor: eventEl.getAttribute('eventcolor'),
            borderColor: eventEl.getAttribute('eventcolor'),
            textColor: eventEl.getAttribute('eventtextcolor')
        };
    }
});

事件列表(任务)

<ul id="events" className="d-flex flex-wrap">
    {
        data.map(item => {
            if(item.duration > 0) {
                let textClass = 'text-white';

                if(item.textColor === '#000') {
                    textClass = 'text-dark';
                }

                return (
                    <li className={"event badge bg-" + item.status + " " + textClass + " d-block mb-2 me-2"} eventtextcolor={item.textColor} eventcolor={item.backgroundColor} eventduration={new Date(item.duration * 1000).toISOString().substring(11, 16)}>{item.title}</li>
                )   
            }
        })
    }
</ul>

日历配置

<FullCalendar
    plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
    initialView="timeGridWeek"
    editable
    slotMinTime="06:00"
    slotMaxTime="19:00"
    eventDurationEditable={false}
    droppable
    businessHours={{
        daysOfWeek: [1, 2, 3, 4, 5, 6], // 周一到周六
        startTime: '08:00',
        endTime: '17:00',
    }}
    slotDuration={'00:10:00'}
    allDaySlot={false}
/>
解决方案

要实现事件自动顺延至工作日,需手动计算事件起止时间,跳过非工作时段。核心逻辑是在事件被拖拽到日历(eventReceive)或移动(eventDrop)时,重新计算结束时间,确保所有时长落在工作时间内。

步骤1:编写工作时间计算工具函数

实现工具函数处理时间转换、工作日判断及实际结束时间计算:

// 转换HH:mm格式为分钟数
const timeToMinutes = (timeStr) => {
    const [hours, minutes] = timeStr.split(':').map(Number);
    return hours * 60 + minutes;
};

// 判断日期是否为工作日(匹配businessHours的daysOfWeek)
const isWorkDay = (date) => {
    const dayOfWeek = date.getDay(); // 0=周日,1=周一...6=周六
    return [1,2,3,4,5,6].includes(dayOfWeek);
};

// 获取当天工作起止时间(分钟数)
const getDailyWorkBounds = () => {
    return {
        start: timeToMinutes('08:00'),
        end: timeToMinutes('17:00')
    };
};

// 计算实际结束时间:从startDate开始,经过指定工作时长后的时间
const calculateWorkingEndTime = (startDate, totalMinutes) => {
    const currentDate = new Date(startDate);
    let remainingMinutes = totalMinutes;
    const workBounds = getDailyWorkBounds();

    while (remainingMinutes > 0) {
        // 非工作日直接跳转到下一天
        if (!isWorkDay(currentDate)) {
            currentDate.setDate(currentDate.getDate() + 1);
            currentDate.setHours(8, 0, 0, 0);
            continue;
        }

        const currentMinutes = currentDate.getHours() * 60 + currentDate.getMinutes();
        let availableMinutes = 0;

        if (currentMinutes < workBounds.start) {
            // 未到工作时间,跳转到当日工作开始时间
            currentDate.setHours(8, 0, 0, 0);
            availableMinutes = workBounds.end - workBounds.start;
        } else if (currentMinutes < workBounds.end) {
            // 计算当日剩余工作时间
            availableMinutes = workBounds.end - currentMinutes;
        } else {
            // 当日工作时间已过,跳转到下一个工作日开始
            currentDate.setDate(currentDate.getDate() + 1);
            currentDate.setHours(8, 0, 0, 0);
            continue;
        }

        // 消耗当日可使用的时长
        const useMinutes = Math.min(remainingMinutes, availableMinutes);
        remainingMinutes -= useMinutes;
        currentDate.setMinutes(currentDate.getMinutes() + useMinutes);
    }

    return currentDate;
};

步骤2:修改日历配置,添加事件处理回调

在FullCalendar组件中添加eventReceive和eventDrop回调,更新事件结束时间:

<FullCalendar
    plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
    initialView="timeGridWeek"
    editable
    slotMinTime="06:00"
    slotMaxTime="19:00"
    eventDurationEditable={false}
    droppable
    businessHours={{
        daysOfWeek: [1, 2, 3, 4, 5, 6],
        startTime: '08:00',
        endTime: '17:00',
    }}
    slotDuration={'00:10:00'}
    allDaySlot={false}
    // 处理拖拽进来的事件
    eventReceive={(info) => {
        const durationMinutes = timeToMinutes(info.event.duration);
        const endTime = calculateWorkingEndTime(info.event.start, durationMinutes);
        info.event.setEnd(endTime);
    }}
    // 处理已存在事件的移动
    eventDrop={(info) => {
        const durationMinutes = timeToMinutes(info.event.duration);
        const endTime = calculateWorkingEndTime(info.event.start, durationMinutes);
        info.event.setEnd(endTime);
    }}
/>

步骤3:优化拖拽事件的duration传递

在Draggable的eventData中,将duration转换为分钟数存入扩展属性,方便后续计算:

let draggable = new Draggable(containerEl, {
    itemSelector: ".event",
    eventData: (eventEl) => {
        const durationStr = eventEl.getAttribute('eventduration');
        const durationMinutes = timeToMinutes(durationStr);
        return {
            id: eventEl.innerText,
            title: eventEl.innerText,
            duration: durationStr,
            extendedProps: {
                durationMinutes
            },
            backgroundColor: eventEl.getAttribute('eventcolor'),
            borderColor: eventEl.getAttribute('eventcolor'),
            textColor: eventEl.getAttribute('eventtextcolor')
        };
    }
});

修改完成后,拖拽事件到日历上时,会自动跳过非工作时间,顺延至下一个工作日,所有时长均显示在工作时段内。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 17:37:12