官网链接:FullCalendar - JavaScript Event Calendar
本文中是基于VUE3+elementui-plus项目中实现的前后端分离的前端功能部分:
一、安装组件
pnpm install @fullcalendar/core --save
pnpm install @fullcalendar/vue3 --save
pnpm install @fullcalendar/daygrid --save
pnpm install @fullcalendar/interaction--save
pnpm install @fullcalendar/timegrid --save
pnpm install @fullcalendar/resource-timeline --save
1.放置组件展示的div
<template>
<div class="app-container meetingroomApply">
<!--按钮添加内容开启自定义弹窗-->
<el-button
type="primary"
size=" medium"
@click="openDialog"
style="margin-bottom: 10px"
>会议室预定</el-button
>
<div class="fullCalendar" id="calendar"></div>
</div>
</template>
2.对应的js代码
<script setup>
import FullCalendar from '@fullcalendar/vue3'
import { Calendar } from '@fullcalendar/core'
import dayGridPlugin from '@fullcalendar/daygrid'
import interactionPlugin from '@fullcalendar/interaction'
import timeGridPlugin from '@fullcalendar/timegrid'
import resourceTimelinePlugin from '@fullcalendar/resource-timeline'
const FullCalendarRef = ref(null)
const calendar = ref(null)
const showSearch = ref(true)
const buttonLoading = ref(false)
const title = ref('')
const deptOptions = ref([])
const deptOptionsLink = ref([])
const companyList = ref([])
const meetingroomList = ref([])
const meetingroomEventList = ref([])
const form = ref({
meetingName: '',
participant: '',
projectionScreen: '',
tableBrand: '',
bottleWater: '',
otherNeeds: '',
remark: '',
meetingStartTime: '',
meetingEndTime: '',
meetingroomName: '',
meetingroomId: ''
})
const rules = ref({})
const queryParams = ref({
meetingroomNo: undefined,
meetingroomName: undefined,
meetingDay: undefined,
viewType: undefined,
beginDayTime: undefined,
endDayTime: undefined,
year: undefined,
month: undefined
})
//监听到的当前view模式
const viewType = ref('')
const calendarOptions = ref({
height: 800,
allDaySlot: false,
axisFormat: 'h(:mm)tt',
plugins: [dayGridPlugin, interactionPlugin, timeGridPlugin, resourceTimelinePlugin],
locale: 'zh-cn', // 中文
initialView: 'resourceTimelineDay',
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'resourceTimelineDay,resourceTimelineWeek,dayGridMonth'
},
buttonText: {
today: '今天',
day: '日',
week: '周',
month: '月'
},
editable: true,
selectable: true,
navLinks: true,
datesSet: (info) => {
datesSet(info)
},
dateClick: (arg) => {
handleDateClick(arg)
},
eventClick: (calEvent) => {
handleEventClick(calEvent)
},
select: (info) => {
handleSelect(info)
},
resourceAreaColumns: [
{
headerContent: '会议室'
}
],
eventColor: '#f08f00',
locale: 'zh-cn',
weekNumberCalculation: 'ISO',
customButtons: {
prev: {
text: '前一天',
click: () => {
prev()
}
},
next: {
text: '后一天',
click: () => {
next()
}
},
today: {
text: '今天',
click: () => {
today()
}
}
},
slotMinTime: '00:00:00',
slotMaxTime: '23:59:00',
resourceAreaWidth: '15%',
schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives'
})
onMounted(() => {
initCalendar()
getList()
})
//加载会议事件
const initCalendar = () => {
var calendarEl = document.getElementById('calendar')
calendar.value = new Calendar(calendarEl, calendarOptions.value)
calendar.value.render()
}
const getList = () => {
queryParams.value.companyIds = 1319
queryParams.value.viewType = 'day'
queryParams.value.meetingDay = setCurrentDate(new Date())
getListApplyMeetingroom(queryParams.value)
}
///获取当前的年月日,做查询参数
const setCurrentDate = (data) => {
const currentDate = data
const year = currentDate.getFullYear()
const month = String(currentDate.getMonth() + 1).padStart(2, '0')
const day = String(currentDate.getDate()).padStart(2, '0')
const formattedDate = `${year}-${month}-${day}`
return formattedDate
}
const getListApplyMeetingroom = (queryParams) => {
nextTick(() => {
// listApplyMeetingroom(queryParams).then((response) => {}); //修改为自己的接口
//清空原始数据
meetingroomList.value = [
{
id: 1,
companyId: 1319,
companyName: '测试',
companyShortName: null,
tenantId: 1319,
meetingroomNo: 'HYS20241118001',
meetingroomName: '测试 - 500会议室',
meetingroomLocation: '测试公司三层309会议室',
meetingroomArea: '86.0000',
meetingroomGalleryful: 40,
meetingroomEquipment: '投影仪',
appliable: 1,
remark: '第一次申请',
scopeDeptId: 1321,
projectionScreen: null,
tableBrand: null,
bottleWater: null
},
{
id: 2,
companyId: 1319,
companyName: '测试',
companyShortName: null,
tenantId: 1319,
meetingroomNo: 'HYS20241118001',
meetingroomName: '测试 - 500会议室',
meetingroomLocation: '测试公司三层309会议室',
meetingroomArea: '86.0000',
meetingroomGalleryful: 40,
meetingroomEquipment: '投影仪',
appliable: 1,
remark: '第一次申请',
scopeDeptId: 1321,
projectionScreen: null,
tableBrand: null,
bottleWater: null
}
]
meetingroomEventList.value = [
{
id: 20,
meetingroomId: 1,
meetingroomName: '测试 - 309会议室',
meetingName: '22好的会议',
participant: '我额人他',
meetingStartTime: '2025-01-16 09:30:00',
meetingEndTime: '2025-01-16 13:30:00',
specialRequest: null,
companyId: 1319,
companyName: '测试公司',
userCode: '1',
userName: 'sysadmin',
userPhone: '1222222222',
deptId: 1323,
deptName: '研发部',
applyTime: null,
applyStatus: 1,
cancelRemark: null,
remark: '问问嗯嗯',
tenantId: 1319,
scopeDeptId: 1323,
projectionScreen: 1,
tableBrand: 1,
bottleWater: 1,
otherNeeds: '问问'
},
{
id: 20,
meetingroomId: 1,
meetingroomName: '测试 - 309会议室',
meetingName: '22好的会议',
participant: '我额人他',
meetingStartTime: '2025-01-16 10:30:00',
meetingEndTime: '2025-01-16 15:30:00',
specialRequest: null,
companyId: 1319,
companyName: '测试公司',
userCode: '1',
userName: 'sysadmin',
userPhone: '1222222222',
deptId: 1323,
deptName: '研发部',
applyTime: null,
applyStatus: 1,
cancelRemark: null,
remark: '问问嗯嗯',
tenantId: 1319,
scopeDeptId: 1323,
projectionScreen: 1,
tableBrand: 1,
bottleWater: 1,
otherNeeds: '问问'
}
]
// 提取所有资源的 id 值
const resourceIds = calendar.value.getResources().map((resource) => resource.id)
// 逐个删除原有资源,防止显示出错
resourceIds.forEach((id) => {
calendar.value.getResourceById(id).remove()
})
if (meetingroomList.value.length > 0) {
// 遍历 this.meetingroomList 并添加资源
meetingroomList.value.forEach((room) => {
calendar.value.addResource({
id: room.id,
title: room.meetingroomName
})
})
}
if (meetingroomEventList.value.length > 0) {
// 获取现有的事件列表
const existingEvents = calendar.value.getEvents()
// 添加新的事件,避免重复
meetingroomEventList.value.forEach((event) => {
const isDuplicate = existingEvents.some((existingEvent) => {
const formattedStart = formmatTime(existingEvent.start)
const formattedEnd = formmatTime(existingEvent.end)
return (
formattedStart === formmatTime(event.meetingStartTime) &&
formattedEnd === formmatTime(event.meetingEndTime)
)
})
if (!isDuplicate) {
calendar.value.addEvent({
resourceId: event.meetingroomId,
title: `${event.meetingName} 预定人: (${event.userName})`,
start: event.meetingStartTime,
end: event.meetingEndTime,
extendedProps: {
meetingroomId: event.meetingroomId,
meetingroomName: event.meetingroomName,
meetingStartTime: event.meetingStartTime,
meetingEndTime: event.meetingEndTime,
meetingName: event.meetingName,
userName: event.userName,
userPhone: event.userPhone,
companyName: event.companyName,
deptName: event.deptName,
participant: event.participant,
projectionScreen: event.projectionScreen,
tableBrand: event.tableBrand,
bottleWater: event.bottleWater,
otherNeeds: event.otherNeeds,
remark: event.remark
}
})
}
})
}
})
}
//添加自定义添加弹出框
const openDialog = () => {
}
const roomChange = (value) => {
// 根据选中的会议室名称找到对应的会议室对象
const selectedRoom = meetingroomList.value.find((room) => room.meetingroomName === value)
if (selectedRoom) {
// 更新 form.meetingroomId 为选中的会议室meetingroomNo
form.value.meetingroomId = selectedRoom.id
}
}
//选择会议室和时间
const handleSelect = (info) => {
form.value.meetingStartTime = handleSelectDate(info.startStr)
form.value.meetingEndTime = handleSelectDate(info.endStr)
if (info.resource) {
form.value.meetingroomName = info.resource.title
form.value.meetingroomId = info.resource.id
}
}
const handleSelectDate = (selectData) => {
const originalTime = selectData
const date = new Date(originalTime)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const formattedTime = `${year}-${month}-${day} ${hours}:${minutes}:00`
return formattedTime
}
//获取视图的日期参数
const getViewDate = () => {
const currentDate = Calendar.getDate()
const currentViewType = Calendar.view.type
if (currentViewType === 'resourceTimelineDay') {
// 日视图
queryParams.value.viewType = 'day'
queryParams.value.meetingDay = formatDate(currentDate, 'yyyy-MM-dd')
} else if (currentViewType === 'resourceTimelineWeek') {
// 周视图
const startOfWeek = new Date(currentDate)
startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay() + 1)
const endOfWeek = new Date(startOfWeek)
endOfWeek.setDate(endOfWeek.getDate() + 6)
queryParams.value.viewType = 'week'
queryParams.value.beginDayTime = formatDate(startOfWeek, 'yyyy-MM-dd')
queryParams.value.endDayTime = formatDate(endOfWeek, 'yyyy-MM-dd')
} else if (currentViewType === 'dayGridMonth') {
// 月视图
const startOfMonth = new Date(currentDate)
startOfMonth.setDate(1)
const endOfMonth = new Date(currentDate)
endOfMonth.setMonth(endOfMonth.getMonth() + 1)
endOfMonth.setDate(0)
queryParams.value.viewType = 'month'
queryParams.value.beginDayTime = formatDate(startOfMonth, 'yyyy-MM-dd')
queryParams.value.endDayTime = formatDate(endOfMonth, 'yyyy-MM-dd')
}
queryParams.value.companyIds =
queryParams.value.tenantIdList.length > 0
? queryParams.value.tenantIdList.join(',')
: form.value.companyId
}
// 辅助方法:格式化日期
const formatDate = (date, format) => {
const pad = (n) => (n < 10 ? '0' + n : n)
return format
.replace('yyyy', date.getFullYear())
.replace('MM', pad(date.getMonth() + 1))
.replace('dd', pad(date.getDate()))
}
const prev = () => {
calendar.value.prev()
getViewDate()
getListApplyMeetingroom(queryParams.value)
}
// 切换下一个按钮事件
const next = () => {
calendar.value.next()
getViewDate()
getListApplyMeetingroom(queryParams.value)
}
// 点击今天按钮
const today = () => {
calendar.value.today()
getViewDate()
getListApplyMeetingroom(queryParams.value)
}
const handleDateClick = (arg) => {
console.log(arg, '事件1')
}
const handleEventClick = (calEvent) => {
console.log(calEvent, '事件2')
title.value = '查看会议详情'
// 将 extendedProps 里的字段及数值逐个放入 this.form
const extendedProps = calEvent.event.extendedProps
for (const key in extendedProps) {
if (extendedProps.hasOwnProperty(key)) {
form.value[key] = extendedProps[key]
}
}
}
const getShowTime = (beginDate, endDate) => {
form.value.startDate = dealWithTime(beginDate)
form.value.startTime = getHoursMin(beginDate)
form.value.endDate = dealWithTime(endDate)
form.value.endTime = getHoursMin(endDate)
}
// 获取时分时间
const getHoursMin = (value) => {
return value.substring(11, 16)
}
// 处理会议时间格式
const dealWithTime = (date) => {
let newDate = /\d{4}-\d{1,2}-\d{1,2}/g.exec(date)[0]
return newDate
}
const handleEvents = (events) => {
console.log(events, '事件3')
}
//UTC时间去掉T
const formmatTime = (time) => {
const utcTimestamp = time
const date = new Date(utcTimestamp)
const year = date.getUTCFullYear()
const month = String(date.getUTCMonth() + 1).padStart(2, '0')
const day = String(date.getUTCDate()).padStart(2, '0')
const hours = String(date.getUTCHours()).padStart(2, '0')
const minutes = String(date.getUTCMinutes()).padStart(2, '0')
const seconds = String(date.getUTCSeconds()).padStart(2, '0')
const formattedDateTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
return formattedDateTime
}
const datesSet = (info) => {
//注意:该方法在页面初始化时就会触发一次
viewType.value = info.view.type
}
</script>
<style lang="scss">
::v-deep .el-select {
width: 100%;
}
::v-deep .el-date-editor.el-input,
.el-date-editor.el-input__inner {
width: 100%;
}
.fc-icon-chevron-right::before {
content: '>' !important;
}
.fc-icon-chevron-left::before {
content: '<' !important;
}
</style>