·  阅读

最近公司刚好有一个日历的需求,选择了Fullcalendar这个插件进行开发,开发过程中,发现网上关于fullcalendar插件在vue中的使用分享并不多,于是就顺便总结了一篇关于vue中使用fullcalendar的文章,希望对小伙伴有帮助。

不想看文字的,可以直接去看源代码,项目地址: github.com/littlePig-z…

这个是Fullcalendar的例子,开发过程中一个很好参考的demo,不局限于vue写法、jQ的写法,有需要的小伙伴可以去看看。

codesandbox.io/search?refi…

那我们现在开始~

一、首先安装fullcalendar的依赖包, fullcalendar.io/docs/plugin… 这个地址是插件合集。

红框部分是比较常用的插件,可自行去查看插件对应的视图,方便使用。

@fullcalendar/interaction 这个插件主要是用户事件的拖拽交互。 @fullcalendar/list 通常日历是栅格化的日历格子显示,使用这个插件就是把日历用列表的形式显示。视图显示如下图:

yarn add --save @fullcalendar/daygrid  // 日历格子显示
yarn add --save @fullcalendar/list  // 列表显示视图用
yarn add --save @fullcalendar/timegrid // 日历时间线视图
yarn add --save @fullcalendar/interaction // 拖拽插件

二、安装完依赖插件, 下面是编写自定义头部、引入fullcalendar插件

需要使用自定义头部的场景:如果需要在头部增加一些自定义下拉选择框,输入框,查询框的情况。官网提供的头部Toolbar中只有一个customButtons属性可自定义,但只能自定义按钮的文本,无法插入html元素。

官网默认的头部,这边就不再重复说了,官网随便找一个例子,都可以找到哈~

这个是Fullcalendar的api合集,方便使用、查找。

下面贴代码:

<template>
        <div class="fc-toolbar">
            <div class="fc-left">
                <el-button-group>
                    <el-button @click="month">
                    </el-button>
                    <el-button @click="week">
                    </el-button>
                    <el-button @click="today">
                    </el-button>
                </el-button-group>
            <div class="fc-center">
                <el-button @click="prev">
                </el-button>
                    {{ title }}
                <el-button @click="next">
                </el-button>
            <el-button ref="next"
                       class="search"
                       type="button"
                       @click="search">
            </el-button>
        <FullCalendar
            id="calendar"
            ref="fullCalendar"
            class="demo-app-calendar"
            :options="calendarOptions">
            <template v-slot:eventContent="arg">
                <el-popover
                    placement="top-start"
                    title="标题"
                    width="200"
                    :visible-arrow="false"
                    trigger="hover">
                    <i class="title">{{ arg.event.title }}</i>
                    <el-button @click="more">
                    </el-button>
                    <div slot="reference"
                         class="popper-content">
                        <span>{{ arg.timeText }}</span>
                        <i>{{ arg.event.title }}</i>
                        <i>{{ arg.event.title }}</i>
                </el-popover>
            </template>
        </FullCalendar>
</template>

写完了html的部分,下面写对应的methods方法,主要是通过getApi()获取到Fullcalendar的实例,调用changeView方法实现视图的切换。

可选的值: 除了dayGridMonth、dayGridWeek、timeGridWeek、resourceTimeline 等等,还可以自定义views
<script>
import FullCalendar from '@fullcalendar/vue';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import listPlugin from '@fullcalendar/list';
import interactionPlugin from '@fullcalendar/interaction';
export default {
    components: {
        FullCalendar, // make the <FullCalendar> tag available
    data() {
        return {
            title: '',
            calendarOptions: {
                locale: 'zh',
                plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
                initialView: 'dayGridMonth',
                editable: true,
                dayMaxEvents: true, // allow "more" link when too many events
                eventDurationEditable: true, // 可以调整事件的时间
                selectable: true, // 日历格子可选择
                nowIndicator: true, // 当前的时间线显示
                eventDisplay: 'block', // 争对全天的情况下,以块状显示
                headerToolbar: false, // 隐藏头部的导航栏
                selectMirror: false,
                displayEventEnd: true, // like 08:00 - 13:00
                eventTimeFormat: { // like '14:30:00'
                    hour: '2-digit',
                    minute: '2-digit',
                    meridiem: false,
                    hour12: false, // 设置时间为24小时
                slotLabelFormat: { // 左侧时间格式
                    hour: '2-digit',
                    minute: '2-digit',
                    meridiem: 'lowercase',
                    hour12: false, // false设置时间为24小时
                events: [
                        id: 1,
                        title: 'event 1',
                        start: '2021-10-21',
                        color: 'purple',
                        id: 2, title: 'event 2', start: '2021-10-22', color: 'purple',
                    { title: 'event 3', start: '2021-10-23' },
                    { title: 'event 4', start: '2021-10-24' },
                    { title: 'event 5', start: '2021-10-21' },
                    { title: 'event 6', start: '2021-10-21', color: 'purple' },
                    { title: 'event 7', start: '2021-10-21' },
                        id: 3,
                        title: 'event 6',
                        start: '2021-11-21',
                        end: '2021-11-23',
                        color: 'purple',
                        extendedProps: {
                            description: 'asdasdasdasdasdasdasdasds',
                        allDay: false,
                        id: 4,
                        title: 'event 7',
                        start: '2021-11-21',
                        extendedProps: {
                            description: '444444444444',
                eventColor: '#378006', // 事件的颜色
                allDayText: '全天',
                droppable: true, // this allows things to be dropped onto the calendar
            calendarApi: null,
            monthEvent: [
                    id: 'number_1',
                    resourceId: 'number_1',
                    title: 'event 1',
                    start: '2021-10-21',
                    color: 'purple',
                    resourceId: 'number_2',
                    id: 'number_2',  title: 'event 2', start: '2021-10-22', color: 'purple',
                { title: 'event 3', start: '2021-10-23' },
                { title: 'event 4', start: '2021-10-24' },
                { title: 'event 5', start: '2021-10-21' },
                { title: 'event 6', start: '2021-10-21', color: 'purple' },
                { title: 'event 7', start: '2021-10-21' },
                    id: 'number_3',
                    resourceId: 'number_3',
                    title: 'event 6',
                    start: '2021-11-21',
                    end: '2021-11-21',
                    color: 'purple',
                    extendedProps: {
                        description: 'asdasdasdasdasdasdasdasds',
                    id: 4,
                    title: 'event 7',
                    start: '2021-11-22',
                    extendedProps: {
                        description: '444444444444',
            weekEvent: [
                    id: 'number_1',
                    resourceId: 'number_1',
                    title: 'week_event',
                    start: '2021-11-11',
                    color: 'purple',
    mounted() {
        this.calendarApi = this.$refs.fullCalendar.getApi();
        this.title = this.calendarApi.view?.title;
        // 模拟动态获取数据
        this.getDtata();
    watch: {
        // 切换视图显示不同的事件
        'calendarApi.view.type'() {
            this.getDtata()
    methods: {
        getDtata() {
            setTimeout(() => {
                this.calendarOptions.events = this.calendarApi.view?.type === 'dayGridMonth' ?  this.monthEvent : this.weekEvent
            }, 200);
        prev() {
            this.calendarApi.prev();
            this.title = this.calendarApi.view?.title;
        next() {
            this.calendarApi.next();
            this.title = this.calendarApi.view?.title;
        today() {
            this.calendarApi.today();
            this.title = this.calendarApi.view?.title;
        month() {
            this.calendarApi.changeView('dayGridMonth');
            this.calendarApi.today();
            this.title = this.calendarApi.view?.title;
        week() {
            this.calendarApi.changeView('timeGridWeek');
            this.calendarApi.today();
            this.title = this.calendarApi.view?.title;
        day() {
            this.calendarApi.today();
            this.title = this.calendarApi.view?.title;
        search() {
            this.calendarApi.changeView('dayGrid', {
                start: '2021-10-21',
                end: '2021-10-23',
</script>

三、功能点实例

自定义头部大概已经实现了,那么接下来开始写咱们的功能啦。

功能点一: 日历的单击、双击功能、

直接在calendarOptions对象中使用方法属性dateClick通过定时器来判断是否双击还是单击事件。

let clickCount = 0; let prev = ''; // 上一次点击的dom节点 data() { return { calendarOptions: { dateClick: this.handleEventClick, methods: { ...... handleEventClick() { if (e.dateStr !== prev) { clickCount = 0; clickCount += 1; prev = e.dateStr; setTimeout(() => { if (clickCount === 2) { console.log('db click'); } else if (clickCount === 1) { console.log('one click'); clickCount = 0; }, 300)

功能点二:移到事件上显示弹框并操作

官网有提供两个方法,一个是tooltip(图一), 一个是popover(图二)。

官网提供的方法以原生js为主的,这边主要以vue的方式来实现。就是通过官网提供的slot:eventContent。当然也可以自行写一个。这边提供两种思路。

更推荐思路一:通过插槽slot的方式去写

结合element-ui组件,方便实现popover功能。

<template>
    <!-- 自定义头部的代码省略 -->
     <FullCalendar
        id="calendar"
        ref="fullCalendar"
        class="demo-app-calendar"
        :options="calendarOptions">
        <template v-slot:eventContent="arg">
            <el-popover
                placement="top-start"
                title="标题"
                width="200"
                :visible-arrow="false"
                trigger="hover">
                <i class="click">{{ arg.event.title }}</i>
                <el-button @click="handleMore">
                </el-button>
                <div slot="reference"
                     class="popper-content">
                    <span>{{ arg.timeText }}</span>
                    <i>{{ arg.event.title }}</i>
                    <i>{{ arg.event.title }}</i>
                </div>
            </el-popover>
        </template>
    </FullCalendar>
</template>
<script>
//此处只显示新增的部分
export default {
	methods: {
         handleMore() {},
</script>

思路二:自己写,监听鼠标事件,通过定位的方式实现。这个仅供参考思路哈~

<template>
<!-- 自定义头部的代码省略 -->
<!-- popper弹窗 -->
<div id="popper"
     ref="popper"
     class="popper"
     @mouseout="handleMouseOut"
     @mouseover="handleMousover">
    <p>{{ currentView && currentView.title }}</p>
    <button @click="handleMore">
    </button>
</div>
 <FullCalendar
    id="calendar"
    ref="fullCalendar"
    class="demo-app-calendar"
    :options="calendarOptions">
    <template v-slot:eventContent="arg">
        <div class="popper-content">
            <span>{{ arg.timeText }}</span>
            <i>{{ arg.event.title }}</i>
        </div>
    </template>
</FullCalendar>
</template>
<script>
let timer = null;
export default {
	data() {
		return {
		    currentView: {},
		    calendarOptions: {
                eventMouseEnter: this.eventMouseEnter,
                eventMouseLeave: this.handleMouseOut,
	methods: {
		// 事件的鼠标hover事件
        eventMouseEnter(e) {
            const pos = e?.jsEvent;
            const { popper } = this.$refs;
            popper.style.display = 'block';
            popper.style.left = `${pos.x - popper.offsetWidth / 2}px`;
            popper.style.top = `${pos.y - popper.offsetHeight}px`;
            this.currentView = {
                title: e.event.title,
                id: e.event.id,
        // 弹窗的鼠标hover事件
        handleMousover(e) {
            clearTimeout(timer);
            const { popper } = this.$refs;
            popper.style.display = 'block';
         // 弹窗的鼠标out事件
        handleMouseOut() {
            timer = setTimeout(() => {
                const { popper } = this.$refs;
                popper.style.display = 'none';
                this.currentView = {};
            }, 0);
        // 更多配置操作
        handleMore() {
            console.log(this.currentView.id, '===================');
</script>
<style>
.popper {
    position: fixed;
    width: 200px;
    height: 140px;
    padding: 20px;
    border: 1px solid #eee;
    text-align: center;
    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
    z-index: 1000;
    display: none;
    background-color: rgb(188, 236, 250);
</style>

功能点3:新增事件

通过点击按钮,实现在日历中动态增加事件。

两种方法(看个人喜好选择): 一、 使用官方提供的方法 addEvent 二、利用vue的双向数据绑定,直接改变calendarOptions里面的event属性

<template>
<!-- 在自定义头部的地方、加上新增事件的按钮 -->
<div class="fc-toolbar">
        <div class="fc-left">
            <el-button-group>
                <el-button @click="month">
                </el-button>
                <el-button @click="week">
                </el-button>
                <el-button @click="today">
                </el-button>
            </el-button-group>
        </div>
        <div class="fc-center">
            <el-button icon="el-icon-arrow-left"
                       @click="prev" />
                {{ title }}
            <el-button icon="el-icon-arrow-right"
                       @click="next" />
        </div>
            <el-button class="search"
                       style="marginRight: 20px"
                       type="button"
                       @click="addEvent">
            </el-button>
            <el-button class="search"
                       type="button"
                       @click="search">
            </el-button>
        </div>
    </div>
</template>
<script>
	methods: {
		// 在日历中增加事件,大伙可以根据需求去增加自定义时间的事件,我这边是写死一个日期的事件用来示范。
        addEvent() {
        	// 第一种思路: 使用官方提供的方法
            // this.calendarApi.addEvent({
            //    title: 'event 11',
            //    start: '2021-11-28',
            //第二种:通过vue的双向绑定
            this.calendarOptions.events.push({
                title: 'event 11',
                start: '2021-11-28',
</script>

功能点四:修改周视图上面的日期

默认显示的格式是 星期一 11/19, 需要自定义的话,可以在views属性中自定义周试图来实现不同格式的时间。类似月视图也是类似的方法。

views: {
    customeGridWeek: {
        type: 'timeGridWeek',
        dayHeaderFormat: {
            weekday: 'long',
//当然也可以直接在calendarOptions属性设置 
dayHeaderFormat: {
    weekday: 'long',
但这个就是争对全部视图了,如果只是争对周试图进行修改的话,建议自定义修改周视图

视图如下:

下面顺便分享一下 resourceTimeLine 的时间线展示,我刚开始搞的时候,一直显示不出来对应的日历。报没有安装相应的插件的错误,但其实已经引入了对应的插件。去网上搜也没有搜到对应的解决方法。

然后对比网上可以正常使用同学的依赖包版本,我就直接把我的版本绑定到对应的 5.5.0。然后重新安装依赖,神奇般的就可以正常显示了,所以不清楚最新版是不是做了某些改动,需要额外配置什么东西。目前不知道为啥最新版无法正常显示。知道的小伙伴可以评论告知我一下哈~

resourceTimeLine

我们要在自定义头部的地方增加一个下拉选择,手动切换对应的 resourceTimeLine 视图。

主要是分享三个点: 1、修改视图上面的时间线格式(自定义views,然后自由结合这些属性 slotLabelFormatdurationdayHeaderFormatslotDuration) 2、左侧的resource自定义显示,这个属性resourceLabelContent 3、动态增加resourceTimeline事件。

争对配置日历时间线格式的,最好不要全局设置,除非你想全部视图的时间线显示是一样的。最好的方式是通过自定义views, 分别设置时间线格式。

<template>
		<div class="fc-toolbar">
            <div class="fc-left">
                <el-button-group>
                    <el-button @click="month">
                    </el-button>
                    <el-button @click="week">
                    </el-button>
                    <el-button @click="today">
                    </el-button>
                </el-button-group>
            </div>
            <div class="fc-center">
                <el-button icon="el-icon-arrow-left"
                           @click="prev" />
                    {{ title }}
                <el-button icon="el-icon-arrow-right"
                           @click="next" />
            </div>
                <el-select v-model="type"
                           style="marginRight: 20px"
                           @change="handleType">
                    <el-option
                        v-for="item in options"
                        :key="item.value"
                        :label="item.label"
                        :value="item.value" />
                </el-select>
                <el-button class="search"
                           style="marginRight: 20px"
                           type="button"
                           @click="addEvent">
                </el-button>
                <el-button class="search"
                           type="button"
                           @click="search">
                </el-button>
            </div>
        </div>
	</div>
	<!-- 下面的不变,沿用上面的 -->
<template>
<script>
//需增加的插件
import resourceTimelinePlugin from '@fullcalendar/resource-timeline';
export default {
    components: {
        FullCalendar, // make the <FullCalendar> tag available
    data() {
    	title: '',
        currentView: {},
        options: [
            { value: 'timeline', label: 'resource-timeline' },
            { value: 'dategrid', label: 'agenda' },
        type: 'dategrid',
        calendarOptions: {
            locale: 'zh',
            timeZone: "UTC",
            plugins: [dayGridPlugin, timeGridPlugin, listPlugin, resourceTimelinePlugin, interactionPlugin],
            initialView: 'dayGridMonth',
            resourceAreaWidth: 200,
            contentHeight: 600,
            slotMinWidth: 70,
            resourceOrder: 'number',
            editable: true,
            dayMaxEvents: true, // allow "more" link when too many events
            eventDurationEditable: true, // 可以调整事件的时间
            selectable: true, // 日历格子可选择
            nowIndicator: true, // 现在的时间线显示
            eventDisplay: 'block', // 争对全天的情况下,以块状显示
            headerToolbar: false, // 隐藏头部的导航栏
            selectMirror: false,
            displayEventEnd: true, // like 08:00 - 13:00
            eventTimeFormat: { // like '14:30:00'
                hour: '2-digit',
                minute: '2-digit',
                meridiem: false,
                hour12: false, // 设置时间为24小时
            slotLabelFormat: { // 左侧时间格式
                hour: '2-digit',
                minute: '2-digit',
                meridiem: 'lowercase',
                hour12: false, // false设置时间为24小时
            events: [],
            eventColor: '#378006',
            allDayText: '全天',
            dateClick: this.handleDateClick,
            resourceAreaHeaderContent: "Rooms",  // 左侧resource的header显示文本,默认是resources
            resources: [  // 对应左侧resource的数据,几条就代表几行
                    id: 'number_1',
                    title: 'asas',
                    number: 1
                    id: 'number_2',
                    title: 'asas',
                    number: 2
                    id: 'number_3',
                    title: 'asas',
                    number: 3
            schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source', // 目前我也不清楚为啥需要这个凭证,值是从官网粘贴过来的
            resourceLabelContent(arg) {  // 自定义左侧resource值的显示
                return {
                    html: `<div>id: ${arg.resource.id}</div><div>title: ${arg.resource.title}</div>`
            views: {  // 自定义 resourceTimeLine的 时间显示
                  customTimeLineWeek: {
                        type: 'resourceTimeline',
                        duration: { weeks: 1 },
                        slotDuration: { days: 1 },
                        buttonText: 'Custom Week',
                        slotLabelFormat: {
                            weekday: 'long',
                            // month: 'numeric',
                            // day: 'numeric',
                            omitCommas: true,
                    customTimeLineMonth: {
                        type: 'resourceTimeline',
                        duration: { month: 1 },
                        slotLabelFormat: {
                            // month: 'numeric',
                            day: 'numeric',
                            // omitCommas: true,
                    customGridWeek: {
                        type: 'timeGridWeek',
                        dayHeaderFormat: {
                            weekday: 'long',
                        slotLabelFormat: {
                            // 左侧时间格式
                            hour: '2-digit',
                            minute: '2-digit',
                            meridiem: 'lowercase',
                            hour12: false, // false设置时间为24小时
            // 切换视图调用的方法
            datesSet(arg) {},
        calendarApi: null,
        monthEvent: [
                    id: 'number_1',
                    resourceId: 'number_1',
                    title: 'event 1',
                    start: '2021-10-21',
                    color: 'purple',
                    resourceId: 'number_2',
                    id: 'number_2',
                    title: 'event 2',
                    start: '2021-10-22',
                    color: 'purple',
                { title: 'event 3', start: '2021-10-23' },
                { title: 'event 4', start: '2021-10-24' },
                { title: 'event 5', start: '2021-10-21' },
                { title: 'event 6', start: '2021-10-21', color: 'purple' },
                { title: 'event 7', start: '2021-10-21' },
                    id: 'number_3',
                    resourceId: 'number_3',
                    title: 'event 6',
                    start: '20211120',
                    end: '20211122',
                    color: 'purple',
                    extendedProps: {
                        description: 'asdasdasdasdasdasdasdasds',
                    id: 4,
                    title: 'event 7',
                    start: '2021-11-22',
                    extendedProps: {
                        description: '444444444444',
        weekEvent: [
                id: 'number_1',
                resourceId: 'number_1',
                title: 'week_event',
                start: '2021-11-11',
                color: 'purple',
    methods: {
    	 getDtata() {
            setTimeout(() => {
                this.calendarOptions.events =
                    this.calendarApi.view?.type === 'dayGridMonth' ? this.monthEvent : this.weekEvent;
            }, 200);
        more() {},
        // 增加事件
        addEvent() {
            this.calendarOptions.events.push({
                id: 'number_4',
                resourceId: 'number_3', // 如果是resourceTimeLine视图的话,增加事件时,这个resourceId一定要加上,对应的值为 你要在哪个resource里面加事件,就是用哪个resource的对应id. 通过这个resourceId 去将当前事件绑定到上面。不加这个属性的话,是不会显示出来的。
                title: 'event 11',
                start: '2021-11-28',
        // 单击事件
        handleDateClick(e) {
            if (e.dateStr !== prev) {
                clickCount = 0;
            clickCount += 1;
            prev = e.dateStr;
            setTimeout(() => {
                if (clickCount === 2) {
                    console.log('db click');
                } else if (clickCount === 1) {
                    console.log('one click');
                clickCount = 0;
            }, 300);
        prev() {
            this.calendarApi.prev();
            this.title = this.calendarApi.view?.title;
        next() {
            this.calendarApi.next();
            this.title = this.calendarApi.view?.title;
        today() {
            this.calendarApi.today();
            this.title = this.calendarApi.view?.title;
        month() {
            if (this.type === 'timeline') {
                this.calendarApi.changeView('customTimeLineMonth');
            }else {
                this.calendarApi.changeView('dayGridMonth');
            this.calendarApi.today();
            this.title = this.calendarApi.view?.title;
        week() {
            if (this.type === 'timeline') {
                this.calendarApi.changeView('customTimeLineMonth');
            }else {
                this.calendarApi.changeView('customGridWeek');
            this.calendarApi.today();
            this.title = this.calendarApi.view?.title;
        day() {
            this.calendarApi.today();
            this.title = this.calendarApi.view?.title;
        search() {
            this.calendarApi.changeView('dayGrid', {
                start: '2021-10-21',
                end: '2021-10-23',
        // 下拉框选择的change事件
        handleType() {
            if(this.type ==='timeline') {
                this.calendarApi.changeView('customTimeLineMonth')
                this.calendarOptions.slotLabelFormat = null // 设置null 是因为我们全局设置了 slotLabelFormat的属性,是针对dayGrid视图的,不适用于timeline视图。
            } else {
                this.calendarApi.changeView('dayGridMonth')
<script>

需要注意的点:

为啥增加了事件没有正常显示在视图上?

在resourceTimeLine的视图下,定义事件、增加事件的时候,事件对象务必要增加一个属性 resourceId,对应的值为 你要在哪个resource里面加事件,就是用哪个resource的对应id. 通过这个resourceId 去将当前事件绑定到上面。

分享的内容就这么多啦,希望可以帮助到有需要的小伙伴。如有不正确的地方,不懂的地方欢迎评论。

分类:
前端
标签: