FullCalendar日历组件集成系列10——拖放外部元素至FullCalendar组件内

拖放外部元素至FullCalendar组件内

业务场景

先来说下需求。
我们通过日历组件来安排日程,主要是安排那些有相对明确的时间的事项。但实际还存在一些事项,比如临时的,刚想起或刚产生的,不紧急,甚至需要完成一些前置依赖。安排到哪个时间点尚不确定。
这种场景下,需要一个收集箱来暂存这些任务,用于提醒,防止遗忘而遗漏。
如上所述,将任务状态标记为“待安排”,并将其起止时间值为空。
安排计划时再根据实际情况把这些事项放到具体的计划中。

功能设计

对于上述需求,我们在当前日历组件之外,增加收集箱的功能,用于存放和显示“待安排”的任务,如下图所示:
image.png
这样用户不需要切换到别的菜单来新建待安排的事项,并且在安排计划时候也可以将待安排的事项一并考虑。
虽然用户可以在收集箱中的任务清单中点击某项任务,设置起止时间,通过刷新日历组件来显示,但是操作较繁琐。为了便于操作,我们来实现“拖放”操作,即将左侧收集箱中的任务,直接拖放到日历组件中,根据放置的位置自动设置起止时间,这样做更方便与直观。
即我们需要将一个FullCalendar日历组件外的元素,拖放到日历组件内部。

尝试方案

官网说明文档有相关描述,但是过于简略,需要摸索。
首先,需要配置属性,将拖放功能droppable开关打开:

// 启用拖动外部元素放置到日历
droppable: true

然后,定义拖放结束的回调事件drop,注意不是前面用过的eventDrop,如下:

//外部元素拖放到日历中
drop: this.drop

接下来,最关键的操作就是外部元素拖拽到日历组件内部了。
官网描述的使用draggable,说的很含糊,这个draggable到底指啥并不明确。
一开始,尝试使用自己熟悉的vuedraggable组件来实现拖拽功能,然后测试失败,drop回调事件触发不了,怀疑被vuedraggable自己截获和处理了。
然后,使用html5自身的draggable,同样发现无法触发回调事件。

解决方案

回过头细看官方说明,原来导入的是FullCalendar自身的Draggable组件,但是写法看上去又很奇怪,是JQuery模式的操作,如下:

document.addEventListener('DOMContentLoaded', function() {
  var Calendar = FullCalendar.Calendar;
  var Draggable = FullCalendar.Draggable;

  var containerEl = document.getElementById('external-events');
  var calendarEl = document.getElementById('calendar');
  var checkbox = document.getElementById('drop-remove');

  // initialize the external events
  // -----------------------------------------------------------------

  new Draggable(containerEl, {
    itemSelector: '.fc-event'
  });

  // initialize the calendar
  // -----------------------------------------------------------------

  var calendar = new Calendar(calendarEl, {
    headerToolbar: {
      left: 'prev,next today',
      center: 'title',
      right: 'dayGridMonth,timeGridWeek,timeGridDay'
    },
    editable: true,
    droppable: true, // this allows things to be dropped onto the calendar
    drop: function(info) {
      console.log(info)
      // is the "remove after drop" checkbox checked?
      if (checkbox.checked) {
        // if so, remove the element from the "Draggable Events" list
        info.draggedEl.parentNode.removeChild(info.draggedEl);
      }
    }
  });

  calendar.render();
});

尝试将其转化为vue的写法。
引入draggable组件:

import { Draggable } from '@fullcalendar/interaction'

使用div包裹,指定id为external-events,任务列表通过el-row附加v-for,指定class为dragElement,如下:

 <div id="external-events">
    <el-row
      v-for="element in taskList"
      :key="element.id"
      class="dragElement"           
    >
      <el-tag
        closable
        @close="remove(element.id)"
        @click="modify(element.id)"
        :title="element.name"
      >
        {{ element.name }}</el-tag
      >
    </el-row>
  </div>

初始化加载数据的操作里,调用Draggable,这时候,需要通过 document.getElementById(‘external-events’)获取外层的div元素,并且通过itemSelector: '.dragElement’来获取可拖拽的元素,如下:

 // 初始化
init() {
  this.loadData()
},
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 {
            title: eventEl.innerText,
            duration: '00:30'
          }
        }
      })
    }
  })
}

完成上述操作后,可以实现任务从外部拖放到日历组件内部了,当然,仅限于前端。

接下来,就是调用后端服务,将被拖动的任务,根据放到日历组件中的位置,设置其起止时间,并变更其状态为“未开始”。

drop回调事件中,能拿到几个重要属性如下:
allDay:是否全天事件
date:拖放结束放置的位置所在的时间
draggedEl:被拖放的html元素
我们可以通过date获取开始时间,然后结合allDay来计算结束时间,如果是全天,开始时间+1天为结束时间;如果是非全天,开始时间+半小时(我们自行设置的默认持续时长)。
还有最关键的一点,是如何获取到被拖拽的任务的标识id,通过打印输出的方式,发现draggedEl元素里仅传递了任务标题,并没有传递id。
尝试了很多方式,最终是使用date-id这种定义属性,来放入任务标识:

 <div id="external-events">
  <el-row
    v-for="element in taskList"
    :key="element.id"
    class="dragElement"
    :data-id="element.id"
  >
    <el-tag
      closable
      @close="remove(element.id)"
      @click="modify(element.id)"
      :title="element.name"
    >
      {{ element.name }}</el-tag
    >
  </el-row>
</div>

然后在drop的回调方法中,通过arg.draggedEl.dataset.id的方式取出来,整个回调方法如下:

 drop(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(() => {
    this.refresh()
  })
}

最终,实现了将外部元素拖放到日历组件内部的功能。

重构为无刷新模式

前面我们增加了收集箱功能,用于存放待安排的任务,并实现了从收集箱拖放任务到日历的功能,如下图所示:image.png
使用的是FullCalendar的drop事件,最后调用的是刷新操作,如下:

退回收集

通过右键菜单,可以将某个暂不具备的执行条件的任务退回了收集箱,通过调用FullCalendar的删除任务的api,并调用收集箱的加载数据方法,来实现页面无刷新,如下:

// 设置待安排
setPending(id) {
  this.$api.personaltask.task.changeStatus(id, 'PENDING').then(() => {
    this.revmoveTask(id)
    this.reloadCollectionBox()
  })
}

// 删除任务
revmoveTask(taskId) {
  const fullCalendar = this.$refs.fullCalendar.calendar
  const event = fullCalendar.getEventById(taskId)
  event.remove()
}

// 刷新收集箱
reloadCollectionBox() {
  this.$refs.collectionBox.loadData()
}

应用系统

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

学海无涯,行者无疆

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

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

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

打赏作者

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

抵扣说明:

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

余额充值