背景
有一些应用系统或应用功能,如日程管理、任务管理需要使用到日历组件。虽然Element Plus也提供了日历组件,但功能比较简单,用来做数据展现勉强可用。但如果需要进行复杂的数据展示,以及互动操作如通过点击添加事件,则需要做大量的二次开发。
FullCalendar是一款备受欢迎的开源日历组件,以其强大的功能而著称。其基础功能不仅免费且开源,为开发者提供了极大的便利,仅有少量高级功能需要收费。然而,尽管该组件功能卓越,其文档却相对简洁,导致在集成过程中需要开发者自行摸索与探索,这无疑增加了不少学习和验证的时间成本。
为此,本专栏通过日程管理系统的真实案例,手把手带你了解该组件的属性和功能,通过需求导向的方式,详细阐述FullCalendar组件的集成思路和实用解决方案。
在介绍过程中,我们将重点关注集成要点和注意事项,力求帮助开发者在集成过程中少走弯路,提供有效的避坑指南,从而提升开发效率,更好地利用这款优秀的日历组件。
官网:https://fullcalendar.io/
环境Vue3+Element Plus+FullCalendar 6.1.11。
使用
按需加载事件数据(三)
新的问题
上面我们实现了按需加载,详细测试时发现,自己添加的自定义按钮,控制隐藏或显示已完成的任务,不再有效……经分析和推测,问题很可能出在回调方法中的第二个参数successCallback,虽然我们做了缓存,但是高度怀疑这个方法只能使用一次,在已有数据的情况下,调用第二次,存在内部逻辑处理……
针对推测做了一个验证,源码如下:
//代码段1:传入未完成的任务
const filtedData = this.eventData.filter((item) => {
return (
item.status === 'IN_PROGRESS' ||
item.status === 'TO_DO' ||
item.status === 'EXPIRED' ||
item.status === 'PENDING' ||
item.status === 'PAUSED'
)
})
this.successCallback(filtedData)
//代码段2:传入所有任务
this.successCallback(this.eventData)
以上两段代码,次序对调,都是仅最前面的生效,并不是数据合并,实锤了回调方法successCallback只生效一次的推测。
把successCallback控制台打印了下,如下:
ƒ (res2) {
if (!isResolved) {
isResolved = true;
normalizedSuccessCallback(res2);
}
}
其实不是successCallback自身变了,而是内部处理逻辑,有个isResolved状态位,一旦调用过一次后,该状态位就变了,第二次执行时状态位判断失败直接跳过处理了。
这么个机制,即使把数据过滤功能挪到后端进行,也照样无法解决问题。
尝试方案
日历组件内部是个黑盒,根据当前的现象和推测,目前最好的方式,就是重新触发下回调。
验证过一下几种解决方式,最终都没走通。
方式1:通过调用api来切换视图,实测无效,events属性对应的回调方法不会触发。
// 变更显示范围
changeShowScope() {
this.showAllFlag = !this.showAllFlag
// 触发回调
const fullCalendar = this.$refs.fullCalendar.calendar
const view = fullCalendar.view
fullCalendar.changeView(view.type)
}
方式2:查阅文档发现有个做数据转换的方法,添加eventDataTransform事件回调,测试发现只触发一次。
方式3:官方还有个viewDidMount 回调方法,看文档说明也是在dom元素加载后触发一次,数据变化不会再次触发,也没戏。
以上方式统统无效。
解决方案
对于该问题,官方组件内置或扩展不太给力。考虑到我们自己添加的显示全部与未完成的数据过滤功能,实际使用过程中不会频繁操作,因此在点击切换按钮时,通过刷新当前tab页来实现数据刷新功能。
思路简单,实现起来有比较多的细节需要考虑。
当前用户的两个选择项,是显示全部任务还是仅显示未完成任务,以及浏览的视图类型,需要通过查询参数来缓存。
在页面mounted的时候,执行初始化,从query参数中获取和赋值,并调用日历组件的api来切换视图。
mounted() {
this.init()
},
methods: {
// 初始化
init() {
this.calendarApi = this.$refs.fullCalendar.getApi()
// 处理是否显示全部
if (this.$route.query.showAllFlag != undefined) {
//此处注意,query参数是字符串类型,直接赋值给showAllFlag会令其类型变化,使用非运算符!会一直为true
this.showAllFlag = this.$route.query.showAllFlag == 'true' ? true : false
}
// 默认设置为日视图
let viewType = this.calendarOptions.initialView
// query参数中取值
if (this.$route.query.viewType) {
viewType = this.$route.query.viewType
}
// 调用日历组件api实现视图切换
const fullCalendar = this.$refs.fullCalendar.calendar
fullCalendar.changeView(viewType)
},
配合前面说过的,将日历组件的事件源设置为函数方法,在该方法中监听视图变化获取数据范围变化。
calendarOptions:
……
// 加载事件数据
events: this.loadEvent
……
}
// 加载事件数据
loadEvent(fetchInfo, successCallback, failureCallback) {
this.startTime = this.$dateFormatter.formatUTCTime(fetchInfo.start)
this.endTime = this.$dateFormatter.formatUTCTime(fetchInfo.end)
this.successCallback = successCallback
this.loadData()
}
变更显示范围时,调用刷新,这里的刷新是调用了前端框架的tab页刷新,传入了query参数
// 变更显示范围
changeShowScope() {
this.showAllFlag = !this.showAllFlag
this.refresh()
},
// 刷新
refresh() {
const fullCalendar = this.$refs.fullCalendar.calendar
let query = this.$route.query
query = Object.assign(query, {
viewType: fullCalendar.view.type,
showAllFlag: this.showAllFlag
})
refreshSelectedTagWithQuery(query)
}
进行功能测试,切换视图和变更数据显示全部还是未完成,前端显示正常。
查看后端情况情况,发现还有个小瑕疵,就是页面加载的时候,实际会触发两次调用后端服务请求,推测一次是来源于组件自身初始化加载,另一次是我们手工调用的api方法来切换视图。只有在切换显示范围时才会触发,影响很小,先搁置下,看看后面是否有更好的优化方式。