Vue 项目中如何使用FullCalendar 时间段选择插件(类似会议室预定、课程表)
本文中是基于VUE+elementui项目中实现的前后端分离的前端功能部分:
插件的官方文档:FullCalendar
1.安装对应依赖(统一安装版本为6.15)
npm install --save @fullcalendar/core@6.15
npm install --save @fullcalendar/daygrid@6.15
npm install --save @fullcalendar/interaction@6.15
npm install --save @fullcalendar/moment@6.15
npm install --save @fullcalendar/resource@6.15
npm install --save @fullcalendar/resource-timeline@6.15
npm install --save @fullcalendar/timegrid@6.15
npm install --save @fullcalendar/vue@6.15
2.放置组件展示的div(包含选中之后的弹框)
<template>
<div class="app-container meetingroomApply">
<el-button
type="primary"
icon="el-icon-plus"
size=" medium"
@click="openDialog"
style="margin-bottom: 10px;"
>会议室预定</el-button
>
<div class="fullCalendar" id="calendar"></div>
<div class="tips-text">请用鼠标滑动选择时间段进行会议预约!</div>
<el-dialog
:visible.sync="dialogVisible"
:close-on-click-modal="false"
:title="title"
>
<el-form
ref="form"
:model="form"
:rules="rules"
label-width="110px"
style="padding: 10px 10px"
>
<el-form-item label="会议室" prop="meetingroomName">
<el-select
v-model="form.meetingroomName"
placeholder="请选择会议室"
@change="roomChange"
:disabled="this.eventClickFlag ? true : false"
>
<el-option
v-for="room in meetingroomList"
:key="room.id"
:label="room.meetingroomName"
:value="room.meetingroomName"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="会议名称" prop="meetingName">
<el-input
placeholder="请输入会议名称"
v-model="form.meetingName"
:disabled="this.eventClickFlag ? true : false"
></el-input>
</el-form-item>
<el-form-item label="会议开始时间" prop="meetingStartTime">
<el-date-picker
clearable
v-model="form.meetingStartTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择会议开始时间"
:disabled="this.eventClickFlag ? true : false"
>
</el-date-picker>
</el-form-item>
<el-form-item label="会议结束时间" prop="meetingEndTime">
<el-date-picker
clearable
v-model="form.meetingEndTime"
type="datetime"
value-format="yyyy-MM-dd HH:mm:ss"
placeholder="请选择会议结束时间"
:disabled="this.eventClickFlag ? true : false"
>
</el-date-picker>
</el-form-item>
<el-form-item label="预定人" prop="userName">
<el-input
v-model="form.userName"
:disabled="this.eventClickFlag ? true : false"
></el-input>
</el-form-item>
<el-form-item label="预定人电话" prop="userPhone">
<el-input
v-model="form.userPhone"
placeholder="请输入联系电话"
:disabled="this.eventClickFlag ? true : false"
/>
</el-form-item>
<el-form-item label="预定人公司" prop="companyId">
<el-input
v-model="form.companyId"
type="text"
:disabled="this.eventClickFlag ? true : false"
></el-input>
</el-form-item>
<el-form-item label="预定部门" prop="deptId">
<el-input
v-model="form.deptId"
type="text"
:disabled="this.eventClickFlag ? true : false"
></el-input>
</el-form-item>
<el-form-item label="参会人" prop="participant">
<el-input
v-model="form.participant"
type="textarea"
:disabled="this.eventClickFlag ? true : false"
></el-input>
</el-form-item>
<el-row>
<el-col :span="6">
<el-form-item label="投屏" prop="projectionScreen">
<el-checkbox
v-model="form.projectionScreen"
:true-label="1"
:false-label="0"
:disabled="this.eventClickFlag ? true : false"
></el-checkbox>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="桌牌" prop="tableBrand">
<el-checkbox
v-model="form.tableBrand"
:true-label="1"
:false-label="0"
:disabled="this.eventClickFlag ? true : false"
></el-checkbox>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="瓶装水" prop="bottleWater">
<el-checkbox
v-model="form.bottleWater"
:true-label="1"
:false-label="0"
:disabled="this.eventClickFlag ? true : false"
></el-checkbox>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="其他需要" prop="otherNeeds">
<el-input
v-model="form.otherNeeds"
type="textarea"
:disabled="this.eventClickFlag ? true : false"
></el-input>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input
v-model="form.remark"
type="textarea"
:disabled="this.eventClickFlag ? true : false"
></el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button
type="primary"
:loading="buttonLoading"
@click="submitForm"
v-if="!this.eventClickFlag"
>确 定</el-button
>
</span>
</el-dialog>
</div>
</template>
3.对应的js代码
<script>
import FullCalendar from "@fullcalendar/vue";
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";
export default {
components: {
FullCalendar, // 像使用自定义组件一样使用
},
constructor() {
const name = Calendar.name;
},
data() {
return {
// 显示搜索条件
showSearch: true,
// 按钮loading
buttonLoading: false,
dialogVisible: false,
eventClickFlag: false,
title: "",
// 部门树选项
deptOptions: [],
deptOptionsLink: [], //根据公司id查询部门数据
// 公司名称数组
companyList: [],
meetingroomList: [],
meetingroomEventList: [],
form: {
meetingName: "",
participant: "",
projectionScreen: "",
tableBrand: "",
bottleWater: "",
otherNeeds: "",
remark: "",
meetingStartTime: "",
meetingEndTime: "",
meetingroomName: "",
meetingroomId: "",
},
rules: {},
// 查询参数
queryParams: {
meetingroomNo: undefined,
meetingroomName: undefined,
meetingDay: undefined,
viewType: undefined,
beginDayTime: undefined,
endDayTime: undefined,
year: undefined,
month: undefined,
},
meetingroomList: [],
meetingroomEventList: [],
//监听到的当前view模式
viewType: "",
calendar: null,
calendarOptions: {
// timeGridPlugin 可显示每日时间段
height: 700,
allDaySlot: false, //是否在日历上方显示all-day(全天)
axisFormat: "h(:mm)tt",
plugins: [
dayGridPlugin,
interactionPlugin,
timeGridPlugin,
resourceTimelinePlugin,
],
headerToolbar: {
left: "prev,next today",
center: "title",
// right: "dayGrid,dayGridWeek,dayGridMonth",
right: "resourceTimelineDay,resourceTimelineWeek,dayGridMonth",
},
buttonText: {
// 设置按钮
today: "今天",
day: "日",
week: "周",
month: "月",
},
editable: true,
selectable: true,
navLinks: true,
datesSet: this.datesSet, //日期渲染;修改日期范围后触发
// displayEventEnd: true,//所有视图显示结束时间
// initialView: "dayGrid", // 设置默认显示周,可选周、日
initialView: "resourceTimelineDay",
dateClick: this.handleDateClick,
eventClick: this.handleEventClick,
// eventsSet: this.handleEvents,
select: this.handleSelect,
resourceAreaColumns: [
{
headerContent: "会议室",
},
],
eventColor: "#f08f00", // 修改日程背景色
locale: "zh-cn", // 设置语言
weekNumberCalculation: "ISO", // 周数
customButtons: {
prev: {
// this overrides the prev button
text: "PREV",
click: () => {
this.prev();
},
},
next: {
// this overrides the next button
text: "PREV",
click: () => {
this.next();
},
},
today: {
text: "今天",
click: () => {
this.today();
},
},
},
// 最小时间
slotMinTime: "07:00:00",
// 最大时间
slotMaxTime: "22:00:00",
resourceAreaWidth: "15%", //高级功能配置Column宽度
// 高级功能许可key,需要用到高级功能时候添加上,避免左下角出现警告提醒
schedulerLicenseKey: "CC-Attribution-NonCommercial-NoDerivatives",
},
};
},
created() {
this.getList();
},
mounted() {
this.initCalendar();
},
methods: {
/** 查询会议室列表 (替换为自己本公司的接口)*/
getList() {
this.queryParams.companyIds = 1319;
this.queryParams.viewType = "day";
this.queryParams.meetingDay = this.setCurrentDate(new Date());
this.getListApplyMeetingroom(this.queryParams);
},
///获取当前的年月日,做查询参数
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;
},
getListApplyMeetingroom(queryParams) {
this.$nextTick(() => {
// listApplyMeetingroom(queryParams).then((response) => {}); //修改为自己的接口
//清空原始数据
this.meetingroomList = [
{
id: 1,
companyId: 1319,
companyName: "测试",
companyShortName: null,
tenantId: 1319,
meetingroomNo: "HYS20241118001",
meetingroomName: "测试 - 309会议室",
meetingroomLocation: "测试公司三层309会议室",
meetingroomArea: "86.0000",
meetingroomGalleryful: 40,
meetingroomEquipment: "投影仪",
appliable: 1,
remark: "第一次申请",
scopeDeptId: 1321,
projectionScreen: null,
tableBrand: null,
bottleWater: null,
},
];
this.meetingroomEventList = [
{
id: 20,
meetingroomId: 1,
meetingroomName: "测试 - 309会议室",
meetingName: "22好的会议",
participant: "我额人他",
meetingStartTime: "2024-11-22 09:30:00",
meetingEndTime: "2024-11-22 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 值
const resourceIds = this.calendar
.getResources()
.map((resource) => resource.id);
// 逐个删除原有资源,防止显示出错
resourceIds.forEach((id) => {
this.calendar.getResourceById(id).remove();
});
if (this.meetingroomList.length > 0) {
// 遍历 this.meetingroomList 并添加资源
this.meetingroomList.forEach((room) => {
this.calendar.addResource({
id: room.id,
title: room.meetingroomName,
});
});
}
if (this.meetingroomEventList.length > 0) {
// 获取现有的事件列表
const existingEvents = this.calendar.getEvents();
// 添加新的事件,避免重复
this.meetingroomEventList.forEach((event) => {
const isDuplicate = existingEvents.some((existingEvent) => {
const formattedStart = this.formmatTime(existingEvent.start);
const formattedEnd = this.formmatTime(existingEvent.end);
return (
formattedStart === this.formmatTime(event.meetingStartTime) &&
formattedEnd === this.formmatTime(event.meetingEndTime)
);
});
if (!isDuplicate) {
this.calendar.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,
},
});
}
});
}
});
},
//加载会议事件
initCalendar() {
var calendarEl = document.getElementById("calendar");
this.calendar = new Calendar(calendarEl, this.calendarOptions);
this.calendar.render();
},
openDialog() {
this.resetForm();
this.title = "会议室预定";
this.form.meetingStartTime = "";
this.form.meetingEndTime = "";
this.form.meetingroomName = "";
this.form.meetingroomId = "";
this.eventClickFlag = false;
this.dialogVisible = true;
},
roomChange(value) {
// 根据选中的会议室名称找到对应的会议室对象
const selectedRoom = this.meetingroomList.find(
(room) => room.meetingroomName === value
);
if (selectedRoom) {
// 更新 form.meetingroomId 为选中的会议室meetingroomNo
this.form.meetingroomId = selectedRoom.id;
}
},
//选择会议室和时间
handleSelect(info) {
this.resetForm();
this.form.meetingStartTime = this.handleSelectDate(info.startStr);
this.form.meetingEndTime = this.handleSelectDate(info.endStr);
if (info.resource) {
this.form.meetingroomName = info.resource.title;
this.form.meetingroomId = info.resource.id;
}
this.eventClickFlag = false;
this.dialogVisible = true;
},
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;
},
resetForm() {
this.form.meetingName = "";
this.form.participant = "";
this.form.projectionScreen = "";
this.form.tableBrand = "";
this.form.bottleWater = "";
this.form.otherNeeds = "";
this.form.remark = "";
},
//获取视图的日期参数
getViewDate() {
const currentDate = this.calendar.getDate();
const currentViewType = this.calendar.view.type;
if (currentViewType === "resourceTimelineDay") {
// 日视图
this.queryParams.viewType = "day";
this.queryParams.meetingDay = this.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);
this.queryParams.viewType = "week";
this.queryParams.beginDayTime = this.formatDate(
startOfWeek,
"yyyy-MM-dd"
);
this.queryParams.endDayTime = this.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);
this.queryParams.viewType = "month";
this.queryParams.beginDayTime = this.formatDate(
startOfMonth,
"yyyy-MM-dd"
);
this.queryParams.endDayTime = this.formatDate(endOfMonth, "yyyy-MM-dd");
}
this.queryParams.companyIds =
this.queryParams.tenantIdList.length > 0
? this.queryParams.tenantIdList.join(",")
: this.form.companyId;
},
// 辅助方法:格式化日期
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()));
},
prev() {
this.calendar.prev();
this.getViewDate();
this.getListApplyMeetingroom(this.queryParams);
},
// 切换下一个按钮事件
next() {
this.calendar.next();
this.getViewDate();
this.getListApplyMeetingroom(this.queryParams);
},
// 点击今天按钮
today() {
this.calendar.today();
this.getViewDate();
this.getListApplyMeetingroom(this.queryParams);
},
handleDateClick: function (arg) {
this.$forceUpdate();
console.log(arg, "事件1");
},
handleEventClick(calEvent) {
console.log(calEvent, "事件2");
this.title = "查看会议详情";
// 将 extendedProps 里的字段及数值逐个放入 this.form
const extendedProps = calEvent.event.extendedProps;
for (const key in extendedProps) {
if (extendedProps.hasOwnProperty(key)) {
this.form[key] = extendedProps[key];
}
}
this.eventClickFlag = true;
this.dialogVisible = true; // 显示dialog弹窗
},
getShowTime(beginDate, endDate) {
this.form.startDate = this.dealWithTime(beginDate);
this.form.startTime = this.getHoursMin(beginDate);
this.form.endDate = this.dealWithTime(endDate);
this.form.endTime = this.getHoursMin(endDate);
},
// 获取时分时间
getHoursMin(value) {
return value.substring(11, 16);
},
// 处理会议时间格式
dealWithTime(date) {
let newDate = /\d{4}-\d{1,2}-\d{1,2}/g.exec(date)[0];
return newDate;
},
handleEvents(events) {
console.log(events, "事件3");
},
getReservationList(arrayData) {
let newArr = [];
this.subList = arrayData;
arrayData.forEach((item) => {
newArr.push({
start: this.dealWithTime(item.beginDate),
end: this.addDate(this.dealWithTime(item.endDate), 1),
color: item.status,
id: item.id,
title: `${this.getTitle(item.beginDate, item.endDate)} ${item.title}`,
});
});
this.calendarOptions.events = newArr;
},
//UTC时间去掉T
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;
},
datesSet(info) {
//注意:该方法在页面初始化时就会触发一次
this.viewType = info.view.type;
},
/** 提交按钮 */
submitForm() {
this.$refs["form"].validate((valid) => {
if (valid) {
}
});
},
},
};
</script>
<style scoped lang="scss">
::v-deep .el-select {
width: 100%;
}
::v-deep .el-date-editor.el-input,
.el-date-editor.el-input__inner {
width: 100%;
}
</style>