使用Fullcalendar管理日程事件(增删改查拖放)

新版Fullcalendar(v4)拥有丰富的选项方法事件配置以及插件应用,对开发者非常友好,开发者可以轻松的利用Fullcalendar定制一个完美的日程安排应用,本文将讲解最实际的日程事件管理前后端交互实例,包括事件的增删改查以及拖放应用的实现。

查看演示 下载源码

准备

本实例将要实现的功能:打开日程安排月视图,默认加载当前月视图的所有事件;点击视图中的任意日期,会弹出新增事件表单,输入事件相关信息后,保存即可;点击视图中的事件,会弹出修改事件表单,可对事件进行修改,也删除事件;我们也可以对视图中的事件进行拖放,拖放完毕也就改变了事件的时间。

注意本文提到的“事件”是指Fullcalendar日程安排事件内容。

本文涉及到的web技术有:

Vue + FullCalendar + Axios + Element-ui + PHP。

本文篇幅较长,建议最好边阅读边实际操作。

我们使用Axios作为Ajax请求模块,具体使用教程可以参考:《Vue项目中使用Axios封装http请求》,我们还用了Element-ui的Dialog、表单、日期时间拾取器等组件。

我们在上一节文章《在Vue框架下使用Fullcalendar》的基础上,新建Event.vue文件:

<template>
    <FullCalendar defaultView="dayGridMonth" 
            locale="zh-cn" 
            timeZone="UTC" 
            firstDay="1" 
            weekNumberCalculation="ISO" 
            editable="true"
            droppable="true"
            displayEventEnd="true"
            :eventTimeFormat="evnetTime"
            :header="header"
            :plugins="calendarPlugins"
            :events="calendarEvents"
            @dateClick="handleDateClick" 
            @eventClick="handleEventClick"
            @eventDrop="calendarEventDrop"
            @datesRender="handleDatesRender"
             />
</template>

<script>
import FullCalendar from '@fullcalendar/vue'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import interactionPlugin, { Draggable } from '@fullcalendar/interaction';
import '@fullcalendar/core/main.css';
import '@fullcalendar/daygrid/main.css';
import '@fullcalendar/timegrid/main.css';

export default {
    components: {
        FullCalendar
    },
    data() {
        return {
            calendarPlugins: [ 
                dayGridPlugin,
                timeGridPlugin,
                interactionPlugin
            ],
            header: {
                left: 'prev,next today',
                center: 'title',
                right: 'dayGridMonth,timeGridWeek,timeGridDay'
            },
            evnetTime: {
                hour: 'numeric',
                minute: '2-digit',
                hour12: false
            },

            calendarEvents: [],
            calendarEventDrop: info => {
                this.dropEvent(info);
            },
            handleDatesRender: arg => {
                this.getEventsList(arg.view)
            },

            dialogFormVisible: false,
            form: {
                title: null,
                start: null,
                end: null
            },
            optTitle: '添加事件',
        }
    },
    created() {
        //
    },
    methods: {
        获取事件列表
        getEventsList(info) {
            
        },
        handleDateClick(arg) {
            this.dialogFormVisible = true;
            this.optTitle = '新增事件';
            this.form.title = '',
            this.form.id = '',
            this.form.start = arg.date;
            this.form.end = arg.date;
        },
        handleEventClick(info) {
            info.el.style.borderColor = 'red';
            this.dialogFormVisible = true;
            this.optTitle = '修改事件';
            this.form = {
                id: info.event.id,
                title: info.event.title,
                start: info.event.start,
                end: info.event.end,
            };
        },
        //保存事件
        saveEvent() {
            
        },
        //删除事件
        delEvent() {
            
        },
        //拖动事件
        dropEvent(info) {
            
        }
    }
}
</script>
知识兔

读取事件

我们希望每次载入Fullcalendar,以及切换日期的时候,会读取视图中的日期范围内的事件列表。Fullcalendar提供了事件源属性events,支持json,数组,回调函数等方式获取日程数据,但是如果要对数据进行修改会新增的时候处理起来比较麻烦了。而我们采用Fullcalendar的另一个方法函数datesRender,它的意思是当视图中的日期渲染时,回调函数。

我们在data中,回调getEventsList()

handleDatesRender: arg => {
                this.getEventsList(arg.view)
            },
知识兔

methods中的getEventsList()代码如下:

getEventsList(info) {
            let params = {
                start: info.activeStart,
                end: info.activeEnd
            };
            this.$get('events.php', params)
            .then((res) => {
                this.calendarEvents = res;
            });
        },
知识兔

大家一看就明白,我们使用了Axios发送get请求,参数就是当前视图中的开始事件和结束时间,获取events.php返回的数据,并将数据赋给events

新增事件

当我们单击视图中的某一天时,触发日期点击事件:@dateClick="handleDateClick",我们在handleDateClick()中,弹出表单框,定义表单元素默认值。注意参数arg是一个内置对象,可以获取当前点击的日期等数据。

handleDateClick(arg) {
            this.dialogFormVisible = true;
            this.optTitle = '新增事件';
            this.form.title = '',
            this.form.id = '',
            this.form.start = arg.date;
            this.form.end = arg.date;
        },
知识兔

在弹出的dialog表单中,有日程事件的名称,起始和结束时间。当新增和编辑的时候我们共用这一个表单,在编辑表单时会显示删除按钮。

<el-dialog :title="optTitle" :visible.sync="dialogFormVisible">
  <el-form :model="form">
    <el-form-item label="事件名称" label-width="80px">
      <el-input v-model="form.title" auto-complete="off" placeholder="请输入事件名称"></el-input>
    </el-form-item>
    <el-form-item label="开始时间" label-width="80px">
        <el-date-picker
          v-model="form.start"
          type="datetime"
          placeholder="选择日期时间">
        </el-date-picker>
    </el-form-item>
    <el-form-item label="结束时间" label-width="80px">
        <el-date-picker
          v-model="form.end"
          type="datetime"
          placeholder="选择日期时间">
        </el-date-picker>
    </el-form-item>
  </el-form>
  <div slot="footer" class="dialog-footer">
    <el-button type="warning" @click="delEvent" v-if="form.id" style="float: left;">删 除</el-button>
    <el-button @click="dialogFormVisible = false">取 消</el-button>
    <el-button type="primary" @click="saveEvent">确 定</el-button>
  </div>
</el-dialog>
知识兔

当点击表单中的“确定”按钮时,调用saveEvent

saveEvent() {
            this.$post('events.php?action=save', this.form)
            .then((res) => {
                if (res.code === 0) {
                    if (this.form.id === undefined || this.form.id == '') { //新增
                        this.form.id = res.id;
                        this.calendarEvents.push(this.form);
                        this.$message({
                            message: '新增成功!',
                            type: 'success'
                        });
                    } else { //修改
                        this.calendarEvents.forEach((item, index, arr) => {
                            if (item.id == this.form.id) {
                                arr[index].title = this.form.title
                                arr[index].start = this.form.start
                                arr[index].end = this.form.end
                            }
                        });
                        this.$message({
                            message: '修改成功!',
                            type: 'success'
                        });
                    }
                    
                    this.dialogFormVisible = false;
                } else {
                    this.$message({
                        message: res.message,
                        type: 'error'
                    });
                }
            });
        },
知识兔

使用Axios发送了一个post请求,根据返回数据和本地form对象中是否有id参数,如果没有则是新增事件,直接this.calendarEvents.push(this.form);,就是往事件数组追加当前表单的数据,然后关闭Dialog,这时对应的日期内就会显示刚刚添加的事件。

修改事件

当点击视图中的某一个日程事件,会弹出Dialog,这时会触发@eventClick="handleEventClick"

handleEventClick(info) {
            info.el.style.borderColor = 'red';
            this.dialogFormVisible = true;
            this.optTitle = '修改事件';
            this.form = {
                id: info.event.id,
                title: info.event.title,
                start: info.event.start,
                end: info.event.end,
            };
        },
知识兔

handleEventClick自带info参数,可以根据该参数获取当前要修改事件的标题名称、起始和结束日期等数据。将这些数据赋值给form,并弹出Dialog。和新增事件一样,保存表单的时候也执行了saveEvent。因为修改事件时已知了事件的id,这个时候修改好的事件怎么替换原有的事件呢?我们使用forEach方法遍历事件数组,比对如果事件id与当前表单id相当时则修改事件名称和日期时间等,详情请看上面修改事件部分。

删除事件

点击修改事件弹出框Dialog时,左下角会出现“删除”按钮,点击该按钮,调用delEvent()

delEvent() {
            this.$post('events.php?action=del', {id: this.form.id})
            .then((res) => {
                if (res.code === 0) {
                    this.calendarEvents.forEach((item, index, arr) => {
                        if(item.id == this.form.id) {
                            arr.splice(index, 1);
                        }
                    });
                    this.dialogFormVisible = false;
                    this.$message({
                        message: '删除成功!',
                        type: 'success'
                    });
                } else {
                    this.$message({
                        message: res.message,
                        type: 'error'
                    });
                }
            });
        },
知识兔

删除当前事件后,同样的我们用forEach遍历事件数组,使用splice将当前删除的事件从事件数组中剔除。

拖放事件

当按住视图中的某一个日程事件,拖动至另一个日期中,触发@eventDrop="calendarEventDrop"

calendarEventDrop: info => {
                this.dropEvent(info);
            },
知识兔

eventDrop传递info参数给this.dropEvent(info)

dropEvent(info) {
            this.form = {
                id: info.event.id,
                title: info.event.title,
                start: info.event.start,
                end: info.event.end
            };
            this.saveEvent();
        }
知识兔

在拖动后,我们将data中的form对象的值改变,并调用saveEvent()保存拖动后的数据。

后端PHP

后端提供了与前端交互的API接口,PHP接收请求,并作出响应,Mysql提供数据存储查询功能。首先创建数据表:

CREATE TABLE `fullcalendar` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(128) NOT NULL,
  `start_time` int(10) NOT NULL DEFAULT '0',
  `end_time` int(10) NOT NULL DEFAULT '0',
  `created_at` datetime NOT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
知识兔

events.php接收获取事件列表、新增、修改、删除事件的接口,具体代码如下:

require_once('conn.php');

$action = isset($_GET['action']) ? $_GET['action'] : '';

if ($action == 'save') { //添加|修改
    $res['code'] = -1;
    $data = file_get_contents('php://input');
    $post = json_decode($data, true);
    $title = htmlentities($post['title']);
    $start = htmlentities($post['start']);
    $end = htmlentities($post['end']);
    if (empty($end)) {
        $end = $start;
    }
    if (empty($title)) {
        $res['message'] = '名称不能为空!';
        echo json_encode($res);
        exit;
    }
    $id = isset($post['id']) ? (int)$post['id'] : '0';
    if ($id == 0) { //添加
        $sql = "INSERT INTO `fullcalendar` (title,start_time,end_time,created_at) VALUES (:title,:start_time,:end_time,:created_at)";
        $stmt = $db->prepare($sql);
        $stmt->execute([
            ':title' => $title,
            ':start_time' => strtotime($start),
            ':end_time' => strtotime($end),
            ':created_at' => date('Y-m-d H:i:s')
        ]);
        $lastid = $db->lastInsertId();
        if ($lastid > 0) {
            $res['id'] = $lastid;
            $res['code'] = 0;
        } 
    } else { //修改
        $sql = "UPDATE `fullcalendar` SET title=:title,start_time=:start_time,end_time=:end_time,updated_at=:updated_at WHERE id=:id";
        $stmt = $db->prepare($sql);
        $stmt->execute([
            ':title' => $title,
            ':start_time' => strtotime($start),
            ':end_time' => strtotime($end),
            ':updated_at' => date('Y-m-d H:i:s'),
            ':id' => $id
        ]);
        $res['code'] = 0;
    }
    
    echo json_encode($res);

} elseif ($action == 'del') { //删除
    $res['code'] = -1;
    $data = file_get_contents('php://input');
    $post = json_decode($data, true);
    $id = isset($post['id']) ? (int)$post['id'] : '0';
    if ($id == 0) {
        $res['message'] = '非法参数';
        echo json_encode($res);
        exit;
    }

    $sql = "DELETE FROM `fullcalendar` WHERE id=:id";
    $stmt = $db->prepare($sql);
    $rs = $stmt->execute([
        ':id' => $id
    ]);
    if ($rs) {
        $res['code'] = 0;
    } else {
        $res['message'] = '删除失败';
    }
    echo json_encode($res);

} else {
    $start = isset($_GET['start']) ? (int)$_GET['start']/1000 : 0;
    $end = isset($_GET['end']) ? (int)$_GET['end']/1000 : 0;

    $start = isset($_GET['start']) ? strtotime($_GET['start']) : 0;
    $end = isset($_GET['end']) ? strtotime($_GET['end']) : 0;

    $sql = "SELECT id,title,start_time,end_time FROM `fullcalendar` WHERE start_time>=:startTime AND end_time<:endTime ORDER BY id desc";
    $stmt = $db->prepare($sql);
    $stmt->execute([
        ':startTime' => $start,
        ':endTime' => $end
    ]);
    $list = $stmt->fetchAll(PDO::FETCH_ASSOC);
    foreach ($list as $key => &$val) {
        $val['start'] = date('Y-m-d H:i:s', $val['start_time']);
        $val['end'] = date('Y-m-d H:i:s', $val['end_time']);
        $val['description'] = 'aaa';
    }
    echo json_encode($list);
}
知识兔

完整的实例代码请下载源码查看。

计算机