FullCalendar日历组件集成实战(16)

背景

有一些应用系统或应用功能,如日程管理、任务管理需要使用到日历组件。虽然Element Plus也提供了日历组件,但功能比较简单,用来做数据展现勉强可用。但如果需要进行复杂的数据展示,以及互动操作如通过点击添加事件,则需要做大量的二次开发。
FullCalendar是一款备受欢迎的开源日历组件,以其强大的功能而著称。其基础功能不仅免费且开源,为开发者提供了极大的便利,仅有少量高级功能需要收费。然而,尽管该组件功能卓越,其文档却相对简洁,导致在集成过程中需要开发者自行摸索与探索,这无疑增加了不少学习和验证的时间成本。
为此,本专栏通过日程管理系统的真实案例,手把手带你了解该组件的属性和功能,通过需求导向的方式,详细阐述FullCalendar组件的集成思路和实用解决方案。
在介绍过程中,我们将重点关注集成要点和注意事项,力求帮助开发者在集成过程中少走弯路,提供有效的避坑指南,从而提升开发效率,更好地利用这款优秀的日历组件。

官网:https://fullcalendar.io/
image.png
环境Vue3+Element Plus+FullCalendar 6.1.11。

使用

保持当前视图范围不变

当我们在月底使用日历组件制定下个月计划和日程,或者安排下周的工作,新增或修改事件后,后端数据持久化后通过刷新页面的方式来让日历组件上的数据更新。这样存在的问题在于一旦刷新页面,则日历组件会“跳”回到当天日期。
例如,制定6月份的计划,刚添加了1条数据,然后保存,视图又跳回了5月份,还需要手工再切换,明显不合理。

FullCalendar组件的切换视图的方法changeView,可以传入第二个参数,单个时间或者起止范围。

在前面的实现方式中,我们通过query参数保持当前的视图类型以及自定义的显示范围(全部任务/进行中任务),采用该方式,把当期视图的显示时间范围也通过query参数来传递,在刷新方法中处理:

    // 刷新
    refresh() {
      const fullCalendar = this.$refs.fullCalendar.calendar
      // console.log(fullCalendar.view)
      let query = this.$route.query
      query = Object.assign(query, {
        viewType: fullCalendar.view.type,
        showAllFlag: this.showAllFlag,
        start: this.$dateFormatter.formatUTCDate(fullCalendar.view.activeStart),
        end: this.$dateFormatter.formatUTCDate(fullCalendar.view.activeEnd)
      })
      refreshSelectedTagWithQuery(query)
    }

然后在页面初始化时读取query参数,调用切换视图方法:

    // 初始化
    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
      }
      const fullCalendar = this.$refs.fullCalendar.calendar
      fullCalendar.changeView(viewType, {
        start: this.$route.query.start,
        end: this.$route.query.end
      })
    }

然后测试发现无效,依然显示的是当前时间。

然后手工写死测试,视图类型为日视图,传入单天,有效。

fullCalendar.changeView('timeGridDay', '2024-06-10')

视图类型为周,传入时间范围,无效!

 fullCalendar.changeView('timeGridWeek', { start: '2024-06-10', end: '2024-06-17' })

这就很无语了……
翻看了FullCalendar源码,看上去也没有问题:

changeView(viewType, dateOrRange) {
    this.batchRendering(() => {
        this.unselect();
        if (dateOrRange) {
            if (dateOrRange.start && dateOrRange.end) { // a range
                this.dispatch({
                    type: 'CHANGE_VIEW_TYPE',
                    viewType,
                });
                this.dispatch({
                    type: 'SET_OPTION',
                    optionName: 'visibleRange',
                    rawOptionValue: dateOrRange,
                });
            }
            else {
                let { dateEnv } = this.getCurrentData();
                this.dispatch({
                    type: 'CHANGE_VIEW_TYPE',
                    viewType,
                    dateMarker: dateEnv.createMarker(dateOrRange),
                });
            }
        }
        else {
            this.dispatch({
                type: 'CHANGE_VIEW_TYPE',
                viewType,
            });
        }
    });
}

停下来思考,推测日历展示的范围,不仅仅跟时间范围有关系,还跟目标时间有关系。
尝试调用内置方法gotoDate,源码调整如下:

  // 初始化
  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
    }
    const fullCalendar = this.$refs.fullCalendar.calendar
    if (this.$route.query.start && this.$route.query.end) {
      fullCalendar.changeView(viewType, {
        start: this.$route.query.start,
        end: this.$route.query.end
      })
      // 取起止范围相差天数
      const dayCount = this.getDaysDifference(this.$route.query.start, this.$route.query.end)
      // 开始时间加上时间范围差值的一半,即取时间中间位置
      const targetDay = new Date(
        new Date(this.$route.query.start).getTime() + (dayCount / 2) * 24 * 60 * 60 * 1000
      )
      // 导航到指定日期
      fullCalendar.gotoDate(this.$dateFormatter.formatUTCDate(targetDay))
    } else {
      fullCalendar.changeView(viewType)
    }
  }

实现的关键逻辑就是获取到页面刷新前视图显示的起止时间范围,然后取中间的时间值,调用api跳转到该时间。
对于日视图和周视图,跳转时间直接取开始时间也可以正常运行,但是月视图不行,因为默认显示范围大于一个月,当前月以及上个月底的几天和下个月的前几天都会显示。如果也直接取开始时间作为跳转时间,则还是会出现在月底制定下个月计划时,页面刷新跳回到本月的情况。

通过以上方法,前端功能正常了,监控后端服务,调用gotoDate时,又触发了一次加载数据,也就是刷新页面,需要加载三次数据……组件自己初始化触发一次,调用切换视图触发一次,调用跳转到时间再触发一次。
这么做确实不优雅,相当于曲线救国,不清楚是组件自身问题或限制,只能这么干,还是有更好的实现方案,后续解决了再更新下本文。

应用系统

名称:遇见
地址:https://meet.popsoft.tech
说明:基于一二三应用开发平台和FullCalendar日历组件实现的面向个人的时间管理、任务管理系统,1分钟注册,完整功能,欢迎使用~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行者无疆1982

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值