在很多业务场景中,我们都需要把「时间维度上的事件」清晰地呈现在一个可交互的日历里,并和其它数据视图(图表、报表、分析面板等)联动。本文基于一个典型的「事件日历 + 外部报表联动」场景,总结一套在 Vue 项目中落地 FullCalendar 的通用实践。
概览
FullCalendar 是最受欢迎的js calendar组件
demos: fullcalendar.io/demos
vue demo: github.com/fullcalenda...
安装
使用 NPM 或 Yarn 安装软件包core以及您计划使用的任何插件(按需安装) 插件列表:fullcalendar.io/docs/plugin...
| 标题 | 描述 | 示意 |
|---|---|---|
| @fullcalendar/core | 必须 | |
| @fullcalendar/vue | vue2项目 | |
| @fullcalendar/vue3 | vue3项目 | |
| @fullcalendar/interaction | 支持点击、拖拽等事件 | |
| @fullcalendar/daygrid | DayGrid 视图 | ![]() |
| @fullcalendar/timegrid | timegrid视图 | ![]() |
| @fullcalendar/resource-timeline | 时间轴视图 | ![]() |
js
npm install \
@fullcalendar/core \
@fullcalendar/vue \
@fullcalendar/daygrid \
@fullcalendar/timegrid \
@fullcalendar/interaction \
如果是vue3项目用@fullcalendar/vue3
使用
引入组件
xml
<template>
<FullCalendar ref="myCalendar" :options="calendarOptions" />
</template>
<script>
// 引入已经安装好的,页面所需要的 FullCalendar 插件
import FullCalendar from '@fullcalendar/vue'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import interactionPlugin from '@fullcalendar/interaction'
// 日历参数配置
const calendarOptions = {}
export default {
name: "my-calendar",
components: {
FullCalendar
},
data () {
return {
calendarOptions
}
}
}
</script>
参数配置
dayGridMonth视图的简单配置参考:
js
calendarOptions = {
locale: 'zh-cn',
plugins: [
dayGridPlugin,
// interactionPlugin, // needed for dateClick
],
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth',
},
buttonText: { today: '今天', prev: '上个月', next: '下个月', dayGridMonth: '月' }, // 设置按钮文本内容
initialView: 'dayGridMonth',
initialEvents: [], // alternatively, use the `events` setting to fetch from a feed
firstDay: 1,
aspectRatio: 1.35, // 日历单元格宽高比 默认值:1.35
eventColor: '#3a79eb', // 日历中事件的默认背景色颜色,优先级低于添加事件时设置的背景色
dayMaxEvents: true,
editable: false,
selectable: false,
selectMirror: true,
dayMaxEvents: true,
weekends: true,
events: this.fetchEvents, // 获取事件
eventClick: this.handleEventClick, // 点击事件
eventsSet: this.handleEvents,
// you can update a remote database when these fire:
// eventChange: this.handleEventChange // eventAdd:
// eventRemove:
详细的配置可参考文章:blog.csdn.net/FlowGuanEr/...
slot模板
xml
<template>
<FullCalendar :options="calendarOptions">
<template v-slot:eventContent='arg'>
<b>{{ arg.event.title }}</b>
</template>
</FullCalendar>
</template>
Calendar API
ini
let calendarApi = this.$refs.myCalendar.getApi()
calendarApi.next()
事件
1. 事件对象
js
var calendar = new Calendar(calendarEl, {
timeZone: 'UTC',
events: [
{
id: 'a',
title: 'my event',
start: '2018-09-01',
end: '2018-09-01'
}
]
})
更多字段详解:fullcalendar.io/docs/event-...
2. 初始化事件
可以在initialEvents配置初始化事件列表
也可以用events中配置方法,调用接口去获取数据,将数据格式化成事件对象规范的格式,显示事件列表。
事件排序 :接口返回的顺序可能杂乱,建议在传给 successCallback 前对列表按 start(及可选的 end、title)排序,这样同一天内多事件在月视图中的展示顺序一致、可预期(FullCalendar 会按你传入的顺序在同一格内排列)。
js
fetchEvents (info, successCallback, failureCallback) {
// info.start / info.end 是当前视图的起止时间
requestAPI(api.fetchCalendarEvents, {
startTime: info.start.getTime(),
endTime: info.end.getTime()
}).then(data => {
if (data?.length) {
const list = data.map((item) => ({
id: item.eventId,
title: item.eventTitle,
start: moment(item.startTime).format('YYYY-MM-DD'),
end: moment(item.endTime + MS_PER_DAY).format('YYYY-MM-DD')
}))
// 按开始时间排序,同一天内按结束时间、再按标题排序,保证展示顺序稳定
list.sort((a, b) => {
const startDiff = new Date(a.start) - new Date(b.start)
if (startDiff !== 0) return startDiff
const endDiff = new Date(a.end) - new Date(b.end)
if (endDiff !== 0) return endDiff
return (a.title || '').localeCompare(b.title || '')
})
successCallback(list)
} else {
failureCallback()
}
}).catch(failureCallback)
}
3. 事件回调
实战:事件日历与外部报表的联动方案
下面是一个抽象化的实战场景:在某个运营看板页面,我们用 FullCalendar 搭建了一个「事件日历」,并和右侧的数据分析报表(通过 iframe 嵌入)联动,整体思路可以概括为三步:
- Calendar 只负责展示事件排期
- 通过
events: this.fetchEvents懒加载当前视图范围内的事件,避免一次性加载整年数据。 - 接口返回后在前端做一次格式化,转成 FullCalendar 认可的事件对象:
id: 使用eventId标识事件;title: 使用eventTitle作为日历上展示的文案;start/end: 用moment格式化为YYYY-MM-DD,结束时间额外 +1 天,避免跨天活动少算一天。
- 在调用
successCallback(list)前对list按start→end→title排序,保证同一天内多事件的展示顺序稳定(见上文「事件排序」)。
- 通过
js
fetchEvents (info, successCallback, failureCallback) {
requestAPI(api.fetchCalendarEvents, {
startTime: info.start.getTime(),
endTime: info.end.getTime()
}).then(data => {
if (data?.length) {
const list = data.map(item => ({
id: item.eventId,
title: item.eventTitle,
start: moment(item.startTime).format('YYYY-MM-DD'),
end: moment(item.endTime + MS_PER_DAY).format('YYYY-MM-DD')
}))
list.sort((a, b) => {
const startDiff = new Date(a.start) - new Date(b.start)
if (startDiff !== 0) return startDiff
const endDiff = new Date(a.end) - new Date(b.end)
if (endDiff !== 0) return endDiff
return (a.title || '').localeCompare(b.title || '')
})
successCallback(list)
} else {
failureCallback()
}
}).catch(failureCallback)
}
- 点击日历事件,高亮并联动外部报表
- 在
eventClick中拿到被点击的事件,做两件事:- 把上一次选中的事件颜色还原为默认蓝色;
- 把当前事件改成高亮色,并记录
currentEventId;
- 同时,基于事件编号
eventId拼接外部报表地址,赋值给pageUrl,iframe 会自动切到该事件对应的数据分析页面:
- 在
js
handleEventClick (clickInfo) {
if (clickInfo?.event?.id) {
this.currentEvents.forEach(event => {
if (event.id === this.currentEventId) {
event.setProp('color', '#3a79eb')
}
})
clickInfo.event.setProp('color', '#db3491')
this.currentEventId = clickInfo.event.id
this.pageUrl = `${REPORT_BASE_URL}?eventId=${clickInfo.event.id}`
}
}
- 通过插槽自定义事件渲染
- 使用
eventContent插槽可以灵活控制日历单元里的展示结构,比如在运营看板里我们希望同时显示「活动时间 + 活动名称」:
- 使用
vue
<FullCalendar class="calendar-app-calendar" :options="calendarOptions">
<template v-slot:eventContent="arg">
<b>{{ arg.timeText }}</b>
<i>{{ arg.event.title }}</i>
</template>
</FullCalendar>
综合以上三点,一个完整的「事件日历 + 数据分析联动」就搭建好了:
使用者只需要在日历上点选某个事件,对应的外部报表就会自动切换到该事件的分析视图,从「时间排期」自然跳转到「结果分析」,大大提升日常分析效率。


