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

背景

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

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

使用

收集箱拖放到日历无刷新改造

前面我们增加了收集箱功能,用于存放待安排的任务,并实现了从收集箱拖放任务到日历的功能,如下图所示:image.png
使用的是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有数据了。
image.png
drop事件收到的数据对象中没有event属性,draggedEl里还是需要通过dataset里才能拿到事件idimage.png

测试发现,在数据源中添加了id属性后,使用的依旧是原来的drop事件,拖放到非全天区域正常了。

但是,从收集箱拖放到全天区域,日历上并不会显示事件。
回看下drop的逻辑处理,实际主要还是构建起止时间传给后台做数据保存,前端的事件属性,比如是否全天,并没有设置,所以才会有问题。

原因定位了,要解决,就不能使用drop事件来处理了,而是更换为eventReceive,先查看参数情况。

拖放到非全天区域,如下图:
image.png
id能直接取到,不用像drop事件,需要从dataset里获取(const id = arg.draggedEl.dataset.id)
allDay属性正确,开始时间是拖放结束对应日历的位置,结束时间是自动加了半小时(收集箱中指定的)。

拖放到全天区域,如下图:
image.png
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事件更方便,尤其了增加了调整事件属性的能力。

详细测试发现,从收集箱拖放到日历,有较大概率产生前端显示多个重复事件的严重问题,如下图所示:
image.png
且一旦某一次拖动发生了该问题,则下一次会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分钟注册,完整功能,欢迎使用~

相关推荐

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-19 02:52:05       51 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-19 02:52:05       54 阅读
  3. 在Django里面运行非项目文件

    2024-07-19 02:52:05       44 阅读
  4. Python语言-面向对象

    2024-07-19 02:52:05       55 阅读

热门阅读

  1. 代码随想录学习 54day 图论 Bellman_ford 算法精讲

    2024-07-19 02:52:05       17 阅读
  2. 锁升级过程中的两次自旋 面试重点

    2024-07-19 02:52:05       19 阅读
  3. electron 应用的生命周期

    2024-07-19 02:52:05       19 阅读
  4. SQL基础

    2024-07-19 02:52:05       18 阅读
  5. 【Unity C#优化】业务逻辑代码方面的优化

    2024-07-19 02:52:05       18 阅读
  6. 【Linux】微基准测试

    2024-07-19 02:52:05       14 阅读
  7. AI发展下的伦理挑战,应当如何应对?

    2024-07-19 02:52:05       18 阅读