FullCalendar 是一个功能强大的 JavaScript 日历插件,它可以用来创建和管理事件日历。它支持多种视图(月视图、周视图、日视图等),并且可以轻松地与其他库(如 Vue.js)集成。下面我们将详细介绍如何使用 FullCalendar 实现事件日历管理。
实现效果大致如下:

安装和引入 FullCalendar
首先,你需要安装 FullCalendar 及其相关插件。可以通过 npm 来安装:
npm install @fullcalendar/core @fullcalendar/daygrid @fullcalendar/timegrid @fullcalendar/list @fullcalendar/interaction
安装完成后,你需要在你的项目中引入 FullCalendar 及其插件:
import { Calendar } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import listPlugin from '@fullcalendar/list';
import interactionPlugin from '@fullcalendar/interaction';
初始化 FullCalendar
在 Vue 组件中,你可以在 mounted 钩子中初始化 FullCalendar。首先,你需要在模板中定义一个容器元素,然后在 mounted 钩子中创建一个新的 Calendar 实例,并将其挂载到 DOM 上。
const fullcalendar = ref(null);
onMounted(() => {
const calendar = new Calendar(fullcalendar.value, {
plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
// 其他配置...
});
calendar.render();
});
配置 FullCalendar
FullCalendar 提供了许多配置选项,你可以根据需要进行自定义。例如,你可以设置日历的视图类型、语言、事件数据等。以下是一些常见的配置选项:
const calendar = new Calendar(fullcalendar.value, {
plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
initialView: 'dayGridMonth', // 初始视图
locale: 'zh-cn', // 语言设置
editable: true, // 允许编辑事件
selectable: true, // 允许选择日期
events: [
// 事件数据
{ title: '事件1', start: '2023-04-01' },
{ title: '事件2', start: '2023-04-02', end: '2023-04-03' },
],
// 更多配置...
});
事件处理
FullCalendar 允许你为不同的用户交互定义处理函数,例如点击事件、选择日期等。你可以通过配置选项来添加这些处理函数:
const calendar = new Calendar(fullcalendar.value, {
// ...
eventClick: function(info) {
// 处理事件点击
alert('点击了事件: ' + info.event.title);
},
select: function(info) {
// 处理日期选择
alert('选择了日期范围: ' + info.startStr + ' 到 ' + info.endStr);
},
// 更多事件处理...
});
更新事件数据
当你的事件数据发生变化时,你可能需要更新日历上的事件。FullCalendar 提供了方法来添加、删除和更新事件:
// 添加事件
calendar.addEvent({ title: '新事件', start: '2023-04-10' });
// 删除事件
let event = calendar.getEventById('event-id');
if (event) {
event.remove();
}
// 更新事件
event.setProp('title', '更新后的事件标题');
集成到 Vue 组件中
在 Vue 组件中使用 FullCalendar 时,你可以将日历实例和事件数据存储在组件的状态中,并在状态变化时更新日历:
const state = reactive({
events: [
// 初始事件数据
],
});
// 在事件数据变化时更新日历
watch(() => state.events, (newEvents) => {
calendar.removeAllEvents();
calendar.addEventSource(newEvents);
});
结合后端 API
在实际应用中,你可能需要从后端 API 获取事件数据,并在用户进行操作(如添加、编辑、删除事件)时与后端进行交互。你可以使用 Vue 的生命周期钩子和异步函数来实现这一功能:
onMounted(async () => {
// 从后端获取事件数据
const events = await fetchEventsFromAPI();
state.events = events;
// 初始化日历
initCalendar();
});
const addEvent = async (event) => {
// 将新事件发送到后端
await saveEventToAPI(event);
// 更新状态
state.events.push(event);
};
结语
FullCalendar 是一个功能丰富且灵活的日历插件,它可以帮助你轻松实现事件日历管理。通过上述介绍,你应该已经了解了如何在 Vue 组件中使用 FullCalendar,以及如何配置、处理事件和与后端 API 集成。希望这篇文章能够帮助你在项目中实现高效的日历管理功能。
具体代码实现
<template>
<div ref="fullcalendar" class="card"></div>
</template>
<script setup lang="ts">
import { ref, reactive, toRefs, nextTick, onMounted, watch, computed } from 'vue'
import { Calendar } from '@fullcalendar/core'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import listPlugin from '@fullcalendar/list'
import interactionPlugin from '@fullcalendar/interaction'
import dayjs from 'dayjs'
import { useRoute, useRouter } from 'vue-router'
import ESDialogVue from '@/components/ESDialog.vue'
import { useServiceOrg } from '../pages/compositions/ServiceOrg'
const { coms, getCompany, cloneComs } = useServiceOrg()
import { xxxApi } from '@/apis'
import useMapStore from '@/compositions/mapStore'
const { user, isSys, isDba, isPA } = useMapStore()
import useAdmq from '@/compositions/admq'
import { cbSuccess, rules } from '@/utils'
const route = useRoute()
const router = useRouter()
const state = reactive({
showManHour: false,
manHourInfo: {
title: '',
type: 0,
rid: '',
start: '',
end: '',
taskTime: '',
companyId: '',
executorId: isSys.value ? '' : user.value.userId,
remark: ''
} as any,
serviceList: [],
planList: [],
executor: {} as any,
infoList: [] as any,
taskTimeTotalList: [] as any,
activeStartDate: null,
activeEndDate: null,
Tcalendar: ref(),
selectedEvent: null,
executorId: undefined,
users: []
})
const fullcalendar = ref()
const Tcalendar = ref()
const type = ref('dayGridMonth')
const currentData = ref()
const { showManHour, manHourInfo, serviceList, planList, executor } = toRefs(state)
const setType = (path: any) => {
router.push(path)
}
const fetchData = async () => {
let _params = {
userId: +route.params.id,
startDate: dayjs(state.activeStartDate).format('YYYY-MM-DD'),
endDate: dayjs(state.activeEndDate).format('YYYY-MM-DD')
}
let { data } = await getManHourListApi(_params)
state.infoList = data.taskList
state.taskTimeTotalList = data.taskTimeTotalList
}
const getLabel = async (item: any) => {
state.executor = [
{
employeeName: item.executorName,
userId: item.executorId
}
]
coms.value = [
{
id: item.companyId,
name: item.companyName
}
]
}
const updateCalendar = () => {
if (Tcalendar.value) {
Tcalendar.value.removeAllEvents()
Tcalendar.value.addEventSource(state.infoList)
Tcalendar.value.render()
}
}
const initCalendar = () => {
Tcalendar.value = new Calendar(fullcalendar.value, {
plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
initialView: type.value,
aspectRatio: 2.2,
locale: 'zh-cn',
handleWindowResize: true,
// height: 'auto',
// loading: loading //控制表格加载
editable: false, // 允许编辑表格
droppable: false, //允许从外部拖拽进入日历
eventDurationEditable: false, //控制时间段是否可以拖动
eventResizableFromStart: false, //控制事件是否可以拖动
selectable: false, // 允许用户通过单击和拖动来突出显示多个日期或时间段
firstDay: 1, // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推。
unselectAuto: true, // 当点击页面日历以外的位置时,是否自动取消当前的选中状态
dayMaxEvents: 4, //在dayGrid视图中,给定日期内的最大事件数
headerToolbar: false, // 关闭默认日历头部,采取自定义的方式切换日历视图
allDaySlot: false,
nowIndicator: true,
eventMaxStack: 2,
events: state.infoList, //主要数据
slotMinTime: '09:00:00', //时间最大范围
slotMaxTime: '21:00:00', //时间最小范围
slotDuration: '00:15:00', //时间间段
slotLabelFormat: {
hour: '2-digit',
minute: '2-digit',
omitZeroMinute: false,
meridiem: 'short'
},
datesSet: function (info) {
state.activeStartDate = info.start
state.activeEndDate = info.end
},
eventClick: function (info) {
state.manHourInfo = {
id: info.event.id,
title: info.event.title,
type: info.event.extendedProps.type,
rid: info.event.extendedProps.rid,
start: dayjs(info.event.start).format('YYYY-MM-DD HH:mm:ss'),
end: dayjs(info.event.end).format('YYYY-MM-DD HH:mm:ss'),
taskTime: info.event.extendedProps.taskTime,
companyId: info.event.extendedProps.companyId,
executorId: info.event.extendedProps.executorId,
remark: info.event.extendedProps.remark
}
fetchService(info.event.extendedProps.rid, true)
getLabel(info.event.extendedProps)
state.manHourInfo.daterange = [state.manHourInfo.start, state.manHourInfo.end]
state.showManHour = true
},
eventClassNames: function (arg) {
// 添加自定义class
return [arg.event.extendedProps.class]
},
dayCellContent: function (arg) {
if (!state.taskTimeTotalList) return { html: arg.dayNumberText }
const hoursForDay = state.taskTimeTotalList.find((item: any) => item.date === dayjs(arg.date).format('YYYY-MM-DD'))
const hours = hoursForDay ? hoursForDay.hours : 0
let hoursHtml = ''
if (hours > 0) {
hoursHtml = `<div class="fc-daygrid-day-hours mt5 f13">工时:${hours} 小时</div>`
}
return {
html: `
<div class="fc-daygrid-day-top">
<a class="fc-daygrid-day-number">${arg.dayNumberText}</a>
</div>
${hoursHtml}
`
}
},
eventContent: function (arg) {
const italicEl = document.createElement('div')
italicEl.className = 'fc-event-container'
const titleEl = document.createElement('div')
titleEl.setAttribute('class', 'plan_title')
const titleTextEl = document.createElement('div')
titleTextEl.className = 'plan_title_text'
titleTextEl.textContent = arg.event.title
titleEl.appendChild(titleTextEl)
const tooltip = document.createElement('div')
tooltip.className = 'fc-event-tooltip'
tooltip.textContent = arg.event.title
italicEl.appendChild(titleEl)
italicEl.appendChild(tooltip)
// 时间
const timeEl = document.createElement('span')
timeEl.innerHTML = dayjs(arg.event.start).format('HH:mm') + '-' + dayjs(arg.event.end).format('HH:mm')
titleEl.append(timeEl)
italicEl.append(titleEl)
italicEl.setAttribute('class', `plan_card`)
italicEl.addEventListener('mouseenter', () => {
const rect = italicEl.getBoundingClientRect()
tooltip.style.left = `${rect.left}px`
tooltip.style.top = `${rect.top - 40}px`
tooltip.style.display = 'block'
})
italicEl.addEventListener('mouseleave', () => {
tooltip.style.display = 'none'
})
return { domNodes: [italicEl] }
},
noEventsContent: function () {
const noEl = document.createElement('div')
noEl.innerHTML = '暂无日程安排,请安排相关日程'
return { domNodes: [noEl] }
}
})
Tcalendar.value.render()
}
const dayTime = () => {
if (type.value === 'dayGridMonth') {
currentData.value = dayjs(Tcalendar.value.getDate()).format('YYYY年MM月')
} else if (type.value === 'timeGridWeek' || type.value === 'listWeek') {
currentData.value =
dayjs(Tcalendar.value.getDate()).format('YYYY年MM月DD日') +
' - ' +
dayjs(Tcalendar.value.getDate()).add(6, 'day').format('DD日')
} else if (type.value === 'timeGridDay') {
currentData.value = dayjs(Tcalendar.value.getDate()).format('YYYY年MM月DD日')
}
fetchData()
}
const toToday = () => {
Tcalendar.value.today()
dayTime()
}
const toPrev = () => {
Tcalendar.value.prev()
dayTime()
}
const toNext = () => {
Tcalendar.value.next()
dayTime()
}
onMounted(() => {
nextTick(() => {
initCalendar()
})
})
</script>




