背景
有一些应用系统或应用功能,如日程管理、任务管理需要使用到日历组件。虽然Element Plus也提供了日历组件,但功能比较简单,用来做数据展现勉强可用。但如果需要进行复杂的数据展示,以及互动操作如通过点击添加事件,则需要做大量的二次开发。
FullCalendar是一款备受欢迎的开源日历组件,以其强大的功能而著称。其基础功能不仅免费且开源,为开发者提供了极大的便利,仅有少量高级功能需要收费。然而,尽管该组件功能卓越,其文档却相对简洁,导致在集成过程中需要开发者自行摸索与探索,这无疑增加了不少学习和验证的时间成本。
为此,本专栏通过日程管理系统的真实案例,手把手带你了解该组件的属性和功能,通过需求导向的方式,详细阐述FullCalendar组件的集成思路和实用解决方案。
在介绍过程中,我们将重点关注集成要点和注意事项,力求帮助开发者在集成过程中少走弯路,提供有效的避坑指南,从而提升开发效率,更好地利用这款优秀的日历组件。
官网:https://fullcalendar.io/
环境Vue3+Element Plus+FullCalendar 6.1.11。
使用
收集箱拖放到日历无刷新改造
前面我们增加了收集箱功能,用于存放待安排的任务,并实现了从收集箱拖放任务到日历的功能,如下图所示:
使用的是FullCalendar的drop事件,最后调用的是刷新操作,如下:
// 从收集箱拖放到日历
drop(arg) {
console.log(arg)
// 获取是否全天
const allDay = arg.allDay
// 获取开始时间
const start = arg.date
// 获取任务标识
const id = arg.draggedEl.dataset.id
let endTime = new Date(start)
if (allDay) {
//若为全天,结束时间为开始时间加1天
endTime = this.$dateFormatter.formatUTCTime(new Date(start.getTime() + 24 * 60 * 60 * 1000))
} else {
// 非全天,结束时间在开始时间基础上加半小时
endTime = this.$dateFormatter.formatUTCTime(new Date(start.getTime() + 30 * 60 * 1000))
}
const startTime = this.$dateFormatter.formatUTCTime(new Date(start.getTime()))
// 调用安排工作接口
this.$api.personaltask.task.assign(id, startTime, endTime).then((res) => {
this.refresh()
})
}
先重构为无刷新模式,把this.refresh()去除,日历中会显示,但是,如果再点击该任务时,会报错,没有获取到任务id……这就尴尬了
重新翻看api,尝试使用eventReceive,同样没有拿到事件的id,那就怀疑被拖放的数据源头,没有设置id了。
回到收集箱的数据加载操作,在eventData中尝试加入id属性,如下所示:
loadData() {
this.$api.personaltask.task.listWithStatus('PENDING').then((res) => {
if (res.data) {
this.taskList = res.data
let containerEl = document.getElementById('external-events')
new Draggable(containerEl, {
itemSelector: '.dragElement',
eventData: function (eventEl) {
return {
id: eventEl.getAttribute('data-id'),
title: eventEl.innerText,
duration: '00:30'
}
}
})
}
})
}
获取id的方式看上去比较奇怪,但没有更好的方式……
然后查看eventReceive事件收到的数据,event对象中的id有数据了。
drop事件收到的数据对象中没有event属性,draggedEl里还是需要通过dataset里才能拿到事件id
测试发现,在数据源中添加了id属性后,使用的依旧是原来的drop事件,拖放到非全天区域正常了。
但是,从收集箱拖放到全天区域,日历上并不会显示事件。
回看下drop的逻辑处理,实际主要还是构建起止时间传给后台做数据保存,前端的事件属性,比如是否全天,并没有设置,所以才会有问题。
原因定位了,要解决,就不能使用drop事件来处理了,而是更换为eventReceive,先查看参数情况。
拖放到非全天区域,如下图:
id能直接取到,不用像drop事件,需要从dataset里获取(const id = arg.draggedEl.dataset.id)
allDay属性正确,开始时间是拖放结束对应日历的位置,结束时间是自动加了半小时(收集箱中指定的)。
拖放到全天区域,如下图:
id、allDay和start都正常,end也是在start基础上加了半小时,这个不对,需要调整。
最终实现如下:
// 日历接收到外部元素拖放
eventReceive(arg) {
// console.log(arg)
let event = arg.event
// 获取任务标识
const id = event.id
// 获取是否全天
const allDay = event.allDay
// 获取开始时间
const start = event.start
// 全天情况下重新设置结束时间
if (allDay) {
//若为全天,结束时间为开始时间加1天
event.setEnd(new Date(start.getTime() + 24 * 60 * 60 * 1000))
}
// 刷新收集箱列表
// this.reloadCollectionBox()
// 转换起止时间格式
const startTime = this.$dateFormatter.formatUTCTime(event.start)
const endTime = this.$dateFormatter.formatUTCTime(event.end)
// 调用安排工作接口
this.$api.personaltask.task.assign(id, startTime, endTime)
}
可以看到,使用eventReceive事件,比原先使用的drop事件更方便,尤其了增加了调整事件属性的能力。
详细测试发现,从收集箱拖放到日历,有较大概率产生前端显示多个重复事件的严重问题,如下图所示:
且一旦某一次拖动发生了该问题,则下一次会100%发生,且最终产生的前端事件的个数是上次的两倍……
这问题很诡异,怀疑是不是拖动过程中,经过的单元格触发了多次,尝试简化代码,发现问题出在收集箱刷新操作上reloadCollectionBox。
在将收集箱里的任务拖动到日历,或者在日历上通过邮件菜单将某个任务退回到收集箱,需要调用刷新操作,来更新数据,如下:
loadData() {
this.$api.personaltask.task.listWithStatus('PENDING').then((res) => {
if (res.data) {
this.taskList = res.data
let containerEl = document.getElementById('external-events')
new Draggable(containerEl, {
itemSelector: '.dragElement',
eventData: function (eventEl) {
return {
id: eventEl.getAttribute('data-id'),
title: eventEl.innerText,
duration: '00:30'
}
}
})
}
})
}
在这个过程中,应该是产生了重复的事件绑定操作,从而导致拖动一次,会产生多个结果数据。
通过定义全局变量,在加载数据前先销毁后创建的方式来解决,调整如下:
loadData() {
// 为避免多次绑定产生的拖动一个任务产生前端多个任务显示,先销毁
if (this.draggable) {
this.draggable.destroy()
}
this.$api.personaltask.task.listWithStatus('PENDING').then((res) => {
if (res.data) {
this.taskList = res.data
let containerEl = document.getElementById('external-events')
this.draggable = new Draggable(containerEl, {
itemSelector: '.dragElement',
eventData: function (eventEl) {
return {
id: eventEl.getAttribute('data-id'),
title: eventEl.innerText,
duration: '00:30'
}
}
})
}
})
}
调整后测试正常,不会再出现拖动一次,产生多个前端事件的问题。
排查和解决该问题花了大量时间,主要原因是FullCalendar的文档太简要了,尤其是draggable内部封装了事件绑定比较隐蔽。
应用系统
名称:遇见
地址:https://meet.popsoft.tech
说明:基于一二三应用开发平台和FullCalendar日历组件实现的面向个人的时间管理、任务管理系统,1分钟注册,完整功能,欢迎使用~