fullCalendar日历插件高级用法与源码修改实战
本文深入解析fullCalendar插件在Vue项目中的高级应用。涵盖从基础安装到复杂场景的配置,包括解决Ant Design日历灵活性不足的问题。重点讲解如何修改源码以支持双击事件、自定义删除标签及农历显示,并详细列出event钩子函数的使用方法,帮助开发者避开常见坑点,实现高度定制的日历功能。
fullCalendar日历插件玩法解析
fullCalendar日历
在引入该插件前,有必要说明选择fullCalendar的原因。此前项目使用的是Ant Design of Vue框架自带的日历组件,但其功能较为单一,缺乏对日历日程灵活操作的支持。相比之下,fullCalendar提供了更强大的交互能力,能够更灵活地处理日历相关的业务需求。
初次接触fullCalendar时,可能会遇到一些配置陷阱。目前npm仓库中存在的几个相关包,如vue-full-calendar、vue-fullcalendar以及calendar,需谨慎选择。虽然vue-fullcalendar可以使用,但它无法直接调用fullCalendar核心库中的其他高级插件包。因此,建议直接安装fullCalendar的核心依赖,以确保功能的完整性。
Fullcalendar安装
安装所需的npm包
npm install --save @fullcalendar/vue
下面包是日历的周视图、日视图等插件包:
npm install --save @fullcalendar/core @fullcalendar/daygrid @fullcalendar/interaction @fullcalendar/list @fullcalendar/timegrid
安装后的fullcalendar源码和其它插件都会在@fullcalendar导入HTML结构
<template>
<div>
<FullCalendar ref="myCalendar" :options="calendarOptions"/>
</div>
</template>导入JS模块
import FullCalendar from '@fullcalendar/vue'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import interactionPlugin from '@fullcalendar/interaction'
import listPlugin from '@fullcalendar/list'export default {
name: 'MaintenanceCalendarview',
components: {
FullCalendar
},
data () {
return {
calendarOptions: {
// 引入的插件,比如fullcalendar/daygrid,fullcalendar/timegrid引入后才可显示月,周,日
plugins: [ dayGridPlugin, interactionPlugin, timeGridPlugin, listPlugin ],
initialView: 'dayGridMonth', // 默认为那个视图(月:dayGridMonth,周:timeGridWeek,日:timeGridDay)
firstDay: 1, // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推
locale: 'zh-cn', // 切换语言,当前为中文
eventColor: '#3BB2E3', // 全部日历日程背景色
themeSystem: 'bootstrap', // 主题色(本地测试未能生效)
initialDate: moment().format('YYYY-MM-DD'), // 自定义设置背景颜色时一定要初始化日期时间
timeGridEventMinHeight: '20', // 设置事件的最小高度
aspectRatio: 1.65, // 设置日历单元格宽度与高度的比例。
// displayEventTime: false, // 是否显示时间
// allDaySlot: false, // 周,日视图时,all-day 不显示
eventLimit: true, // 设置月日程,与all-day slot的最大显示数量,超过的通过弹窗显示
headerToolbar: { // 日历头部按钮位置
left: '',
center: 'prevYear,prev title next,nextYear',
right: 'today dayGridMonth,timeGridWeek,timeGridDay'
},
buttonText: {
today: '今天',
month: '月',
week: '周',
day: '日'
},
slotLabelFormat: {
hour: '2-digit',
minute: '2-digit',
meridiem: false,
hour12: false // 设置时间为24小时
},
eventLimitNum: { // 事件显示数量限制(本地测试未能生效)
dayGrid: {
eventLimit: 5
},
timeGrid: {
eventLimit: 2 // adjust to 6 only for timeGridWeek/timeGridDay
}
},
// 事件
// eventClick: this.handleEventClick, // 点击日历日程事件
eventDblClick: this.handleEventDblClick, // 双击日历日程事件 (这部分是在源码中添加的)
eventClickDelete: this.eventClickDelete, // 点击删除标签事件 (这部分是在源码中添加的)
eventDrop: this.eventDrop, // 拖动日历日程事件
eventResize: this.eventResize, // 修改日历日程大小事件
select: this.handleDateSelect, // 选中日历格事件
eventDidMount: this.eventDidMount, // 安装提示事件
// loading: this.loadingEvent, // 视图数据加载中、加载完成触发(用于配合显示/隐藏加载指示器。)
// selectAllow: this.selectAllow, //编程控制用户可以选择的地方,返回true则表示可选择,false表示不可选择
eventMouseEnter: this.eventMouseEnter, // 鼠标滑过
allowContextMenu: false,
editable: true, // 是否可以进行(拖动、缩放)修改
eventStartEditable: true, // Event日程开始时间可以改变,默认true,如果是false其实就是指日程块不能随意拖动,只能上下拉伸改变他的endTime
eventDurationEditable: true, // Event日程的开始结束时间距离是否可以改变,默认true,如果是false则表示开始结束时间范围不能拉伸,只能拖拽
selectable: true, // 是否可以选中日历格
selectMirror: true,
selectMinDistance: 0, // 选中日历格的最小距离
dayMaxEvents: true,
weekends: true,
navLinks: true, // 天链接
selectHelper: false,
slotEventOverlap: false // 相同时间段的多个日程视觉上是否允许重叠,默认true允许
}
}
},
methods: {
eventMouseEnter (event, jsEvent, view) { // 鼠标划过的事件
if (event.event.classNames.length) {
// console.log(event)
}
},
eventDrop (event, dayDelta, minuteDelta, allDay, revertFunc, jsEvent, ui, view) {
console.log(event)
}
}
}上述代码中包含了日历事件及event事件的配置,这些是我根据实际开发经验筛选出的常用功能。
event数组的配置决定了事件是否可操作。这主要取决于开始日期和结束日期的格式。即使设置了editable属性,时间格式仍会影响操作权限。具体有四种情况:当日期时间跨度从00:00到23:59时,该事件被视为全天事件。
events: [
{
title : '可以拖动但不能缩放',
start : '2019-07-01 12:30',
end : '2019-07-01 13:30', editable: true
},//可以拖动但不能缩放,但在周、日视图中是可以进行缩放的
{
title : '可以拖动、缩放',
start : '2019-07-02 00:00',
end : '2019-07-02 23:59',
color:'red', editable: true
}, //可以拖动、缩放
{
title : 'event 2',
start : '2019-07-02',
end : '2019-07-02',
color:'red', editable: true
},
{ title: 'event 1', date: '2020-06-01', classNames:['cal'], editable: true
},
{
start: '2020-07-24',
end: '2020-07-28',
overlap: false,
display: 'background',
color: '#ff9f89'
},//背景色 (添加相同时间的背景色时颜色会重叠) 一点要初始化日期时间 initialDate: '2020-07-10',
],可以通过添加约束来限制日程的拖动和缩放范围。例如,设置groupId为'availableForMonthStart',即可限定日程仅能在该分组内进行拖动和缩放操作。
{
id: '添加约束',
title: '添加约束',
start: '2020-07-11 00:00',
end: '2020-07-11 12:00',
classNames: ['continuousClass'],
color: '#75a7c8',
editable: true,
constraint: 'availableForMonthStart'
},
{
id: 'constraintDom',
groupId: 'availableForMonthStart',
start: '2020-07-11 00:00',
end: '2020-07-11 23:59',
display: 'background',
color: '#ff9f89'
}自定义事件方法(dblClick)
在标准的日历事件方法中,部分功能可能缺失。
为此,我参考日历源码中的点击方法,复制并修改了一个双击事件处理逻辑。


具体做法是在原有的点击事件基础上,添加dblclick监听即可实现。
// 添加双击事件
var EventDblClicking = /** @class */ (function (_super) {
__extends(EventDblClicking, _super);
function EventDblClicking(settings) {
var _this = _super.call(this, settings) || this;
_this.handleSegClick = function (ev, segEl) {
var component = _this.component;
var context = component.context;
var seg = getElSeg(segEl);
if (seg && // might be the <div> surrounding the more link
component.isValidSegDownEl(ev.target)) {
// our way to simulate a link dblclick for elements that can't be <a> tags
// grab before trigger fired in case trigger trashes DOM thru rerendering
var hasUrlContainer = elementClosest(ev.target, '.fc-event-forced-url');
var url = hasUrlContainer ? hasUrlContainer.querySelector('a[href]').href : '';
context.emitter.trigger('eventDblClick', {
el: segEl,
event: new EventApi(component.context, seg.eventRange.def, seg.eventRange.instance),
jsEvent: ev,
view: context.viewApi
});
if (url && !ev.defaultPrevented) {
window.location.href = url;
}
}
};
_this.destroy = listenBySelector(settings.el, 'dblclick', '.fc-event', // on both fg and bg events
_this.handleSegClick);
return _this;
}
return EventDblClicking;
}(Interaction));
自定义方法的添加主要涉及这三个地方。
自定义删除标签
由于日历日程是通过遍历生成的,如果在遍历过程中动态添加标签,实现起来较为繁琐。
因此,这里采用的方法是直接修改源码内部逻辑,在生成日程时添加删除标签。
添加后唯一的问题是删除标签的样式显示。因为在拖动或缩放日程时,删除按钮也会随之移动,具体的样式处理较为复杂,此处不再赘述,因为涉及的具体样式修改细节较多。
修改第一处(月视图)

var StandardEvent = /** @class */ (function (_super) {
__extends(StandardEvent, _super);
function StandardEvent() {
return _super !== null && _super.apply(this, arguments) || this;
}
StandardEvent.prototype.render = function () {
var _a = this, props = _a.props, context = _a.context;
var seg = props.seg;
var timeFormat = context.options.eventTimeFormat || props.defaultTimeFormat;
var timeText = buildSegTimeText(seg, timeFormat, context, props.defaultDisplayEventTime, props.defaultDisplayEventEnd);
return (createElement(EventRoot, { seg: seg, timeText: timeText, disableDragging: props.disableDragging, disableResizing: props.disableResizing, defaultContent: props.defaultContent || renderInnerContent, isDragging: props.isDragging, isResizing: props.isResizing, isDateSelecting: props.isDateSelecting, isSelected: props.isSelected, isPast: props.isPast, isFuture: props.isFuture, isToday: props.isToday }, function (rootElRef, classNames, innerElRef, innerContent, hookProps) { return (createElement("a", __assign({ className: props.extraClassNames.concat(classNames).join(' '), style: {
borderColor: hookProps.borderColor,
backgroundColor: hookProps.backgroundColor
}, ref: rootElRef }, getSegAnchorAttrs(seg)),
// 手动添加了删除标签
createElement("div", { className: 'fc-eventTooltip' }),
createElement("a", { className: 'fc-eventDeleteClick' }, '删除'),
createElement("div", { className: 'fc-event-main', ref: innerElRef, style: { color: hookProps.textColor } }, innerContent,),
hookProps.isStartResizable &&
createElement("div", { className: 'fc-event-resizer fc-event-resizer-start' }),
hookProps.isEndResizable &&
createElement("div", { className: 'fc-event-resizer fc-event-resizer-end' },))); }));
};
return StandardEvent;
}(BaseComponent));找到对应的代码段,然后进行替换。
修改第二处(这部分涉及周、日视图中的删除逻辑。原本计划将删除标签放置在日程内容下方,但周视图中的样式表现不佳,容易被撑开。具体放置位置可根据实际需求自行调整)。

// 手动添加了删除标签
function renderInnerContent(innerProps) {
return (createElement(Fragment, null,
// createElement("div", { className: 'fc-daygrid-event-dot', style: { borderColor: innerProps.borderColor || innerProps.backgroundColor } }),
innerProps.timeText &&
createElement("div", { className: 'fc-eventTooltip' }),
createElement("a", { className: 'fc-eventDeleteClick' }, '删除'),
createElement("div", { className: 'fc-event-title' }, innerProps.event.title || createElement(Fragment, null, "\u00A0")),
createElement("div", { className: 'fc-event-time' }, innerProps.timeText)));
}替换代码后,默认是没有点击事件的。与上方自定义点击事件类似,需要自行定义点击逻辑。需要注意的是,并非所有标签添加自定义事件都能成功生效。
效果图

这里推荐几种修改日程样式的方法,这些方法都可以操作日程中的样式细节。
eventDidMount: this.eventDidMount,// 渲染时触发
eventDragStart: this.eventDragStart, // 日程开始拖拽时触发
eventDragStop: this.eventDragStop, // 日程拖拽结束时触发
eventResizeStart: this.eventResizeStart, // 日程大小调整开始时触发
eventResizeStop: this.eventResizeStop, // 日程大小调整结束时触发
日历选中方法(这里添加了一个选中日历格后再次点击日历格后新增<双击日历格>)
select: this.handleDateSelect // 选中日历格快速新增
handleDateSelect (selectInfo) {
console.log(selectInfo)
console.log(selectInfo.jsEvent.timeStamp)
// 当点击两次同日历格时判断(要第一次点击选中之后在点击才会生效)
if ((moment(selectInfo.endStr).valueOf() - moment(selectInfo.startStr).valueOf() === 86400000 &&
moment(selectInfo.startStr).valueOf() >= moment(moment().format('YYYY-MM-DD')).valueOf() &&
selectInfo.view.type === 'dayGridMonth') ||
((selectInfo.view.type === 'timeGridWeek' || selectInfo.view.type === 'timeGridDay') &&
moment(selectInfo.startStr).valueOf() >= moment(moment().format('YYYY-MM-DD')).valueOf() &&
(selectInfo.allDay ? (moment(selectInfo.endStr).valueOf() - moment(selectInfo.startStr).valueOf() === 86400000)
: (moment(selectInfo.end).valueOf() - moment(selectInfo.start).valueOf() > 1800000))
)) {
let temporaryTime = {}
temporaryTime = {
timeStamp: selectInfo.jsEvent.timeStamp,
timeString: moment(selectInfo.start).format('YYYY-MM-DD')
}
// 第一次点击时获取选中
if (this.selectTimeStamp.length === 0) {
this.selectTimeStamp.push({
timeStamp: selectInfo.jsEvent.timeStamp,
timeString: moment(selectInfo.start).format('YYYY-MM-DD')
})
} else {
// 第二次点击时判断是否和第一次一样,不一样就删除第一次的,添加第二次的
this.selectTimeStamp.forEach(item => {
if (item.timeString === temporaryTime.timeString) {
//内容
} else {
this.selectTimeStamp.splice(this.selectTimeStamp.findIndex(event => event.timeString === item.timeString), 1)
this.selectTimeStamp.push({
timeStamp: selectInfo.jsEvent.timeStamp,
timeString: moment(selectInfo.start).format('YYYY-MM-DD')
})
}
})
}
}
// 判断过去时间不能新增
if ((moment(selectInfo.endStr).valueOf() - moment(selectInfo.startStr).valueOf() > 86400000 &&
moment(selectInfo.startStr).valueOf() < moment(moment().format('YYYY-MM-DD')).valueOf() &&
selectInfo.view.type === 'dayGridMonth') ||
((selectInfo.view.type === 'timeGridWeek' || selectInfo.view.type === 'timeGridDay') &&
moment(selectInfo.startStr).valueOf() < moment(moment().format('YYYY-MM-DD')).valueOf() &&
(selectInfo.allDay ? (moment(selectInfo.endStr).valueOf() - moment(selectInfo.startStr).valueOf() > 86400000)
: (moment(selectInfo.end).valueOf() - moment(selectInfo.start).valueOf() > 1800000))
)) {
this.$confirm({
title: '提示',
content: '过去时间不能进行新增!',
okText: '确定',
cancelText: '取消',
onOk () {
}
})
}
// 判断获取的时间大于当前、以及两天以上
if ((moment(selectInfo.endStr).valueOf() - moment(selectInfo.startStr).valueOf() > 86400000 &&
moment(selectInfo.startStr).valueOf() >= moment(moment().format('YYYY-MM-DD')).valueOf() &&
selectInfo.view.type === 'dayGridMonth') ||
((selectInfo.view.type === 'timeGridWeek' || selectInfo.view.type === 'timeGridDay') &&
moment(selectInfo.startStr).valueOf() >= moment(moment().format('YYYY-MM-DD')).valueOf() &&
(selectInfo.allDay ? (moment(selectInfo.endStr).valueOf() - moment(selectInfo.startStr).valueOf() > 86400000)
: (moment(selectInfo.end).valueOf() - moment(selectInfo.start).valueOf() > 1800000))
)) {
//内容
}
},效果图

这里提供一个文档参考event事件钩子
Events and Hooks
- event-selected(event, jsEvent, view) - Triggered on eventClick()
- event-mouseover(event, jsEvent, view) - Triggered on eventMouseover()
- event-mouseout(event, jsEvent, view) - Triggered on eventMouseout()
- event-drop(event) - Triggered on eventDrop()
- event-resize(event) - Triggered on eventResize()
- event-created(event) - Triggered on select()
- event-receive(event) - Triggered on eventReceive()
- event-render(event) - Triggered on eventRender()
- view-render(view, element) - Triggered on viewRender()
- day-click(date, jsEvent, view) - Triggered on dayClick()
扩展--------------------------------------------
1、月视图增加农历
在calendarOptions中增加views数据;
通过dayCellContent来设置日期里显示的数据;
引入calendar.js计算农历 https://github.com/jjonline/calendar.js;
import calenderFormate from "../utils/calendar" //农历计算方法
import {formatDate} from "../utils/index" //将日期对象转换成 2020-01-01 格式的日期
//data中calendarOptions修改:
calendarOptions: {
views:{
//对应月视图
dayGridMonth:{
displayEventTime:false,//是否显示时间
dayCellContent(item){
let _date=formatDate(item.date).split('-')
let _dateF=calenderFormate.solar2lunar(_date[0],_date[1],_date[2])
return {html:`<p><label>${_dateF.cDay}</label><span>${_dateF.IDayCn}</span></p>`}
},
}
},
}2、周视图增加农历
在前面的基础上修改views;
在dayHeaderContent中设置显示数据;
views: {
//对应周视图调整
timeGridWeek:{
slotMinTime:"09:00",//周视图开始时间
slotMaxTime:"20:00",//周视图结束时间
displayEventTime:false,//是否显示时间
dayHeaderContent(item){
let _date=formatDate(item.date).split('-')
let _dateF=calenderFormate.solar2lunar(_date[0],_date[1],_date[2])
return {html:`<h3>${_dateF.ncWeek.slice(2)}</h3><p><label>${_dateF.cDay}</label><span>${_dateF.IDayCn}</span></p>`}
}
}
}效果图

推荐参考的文档
https://fullcalendar.io/
https://www.npmjs.com/package/vue-full-calendar
https://blog.csdn.net/supingemail/article/details/48371927
这里提一下,一般在git提交代码的时候,修改的源码是不能直接提交上去的,需要手动打包过去。
这边我暂时就添加了我个人觉得够用的,只是部分没详细解释,我想起来的话会更新的,如果有什么问题可以在下面留言。
到此这篇关于fullCalendar日历插件玩法解析的文章就介绍到这了,更多相关fullCalendar日历插件内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
Vue.js 高阶组件封装与自定义 Hook 实战技巧
2026-11-05 17:15:22
怎么在Vue3项目中集成轻量级JS文本编辑器
2026-11-03 12:21:35
Vue.js官网怎么切换Vue 2与Vue 3版本文档?避免看错版本指南
2026-11-03 08:08:08
ElementUI el-switch 开关组件使用详解与高级应用
2026-10-31 15:21:18
Element UI 表格行按钮点击加载与状态管理实现
2026-10-31 14:12:32
