先看下效果图
需求:实现预约功能,设置过不可预约的时间段置灰不可选中,比如上图中周六周天不可预约,晚上23:00到24:00不可预约;当前天在第一个,往后排7天;点击上一周下一周时切换数据重新渲染后台返回的数据;待审核已预约的数据后台返回根据状态展示不同的颜色;在FullCalendar中拖拽或者点击时填充开始时间结束时间,时长到右侧的表单中;
首先要安装完引用插件:
官网路径:Vue Component - Docs | FullCalendar
如果使用Vue 2:npm install --save @fullcalendar/vue @fullcalendar/core
如果使用Vue 3:npm install --save @fullcalendar/vue3 @fullcalendar/core
接下来使用啥安装啥npm install --save @fullcalendar/daygrid
import '@fullcalendar/core/vdom' // solves problem with Vite
import dayGridPlugin from "@fullcalendar/daygrid";//日历格子显示
import timeGridPlugin from "@fullcalendar/timegrid";//日历时间线视图
import interactionPlugin from "@fullcalendar/interaction";//拖拽插件
import FullCalendar from "@fullcalendar/vue3";
下面是模板代码
<template>
<div style="position: relative">
<div class="color-box">图例说明:
<div class="gray-box"> <span></span> <b>不可预约</b>
</div>
<div class="audit-box"> <span></span> <b>待审核</b>
</div>
<div class="success-box"> <span></span> <b>已预约</b> </div>
<div class="unplay-box"><span></span> <b>申请</b></div>
</div>
<FullCalendar ref="fullCalendar" :options='calendarOptions'>
<template v-slot:eventContent='arg'>
<el-tooltip
class="box-item"
effect="dark"
placement="top-start"
>
<template #content>
<span v-if="arg.event.extendedProps.num">
{{ parseTime(arg.event.start,'{m}月{d}日 {h}:{i}') }}-{{ parseTime(arg.event.end,'{m}月{d}日 {h}:{i}') }} 预约时长:{{arg.event.extendedProps.num}}小时
</span>
<span v-if="!arg.event.extendedProps.num">
{{ parseTime(arg.event.start,'{m}月{d}日 {h}:{i}') }}-{{ parseTime(arg.event.end,'{m}月{d}日 {h}:{i}') }} 预约时长:{{timeLength}}小时
</span>
</template>
<div >
<p v-if ="arg.event.extendedProps.isCurrData||(!arg.event.extendedProps.isCurrData&&!arg.event.extendedProps.status)" style="color:#ffffff;margin:0px">
{{ parseTime(arg.event.start,'{m}月{d}日') == parseTime(arg.event.end,'{m}月{d}日') ? parseTime(arg.event.start,'{h}:{i}') : parseTime(arg.event.start,'{m}月{d}日 {h}:{i}') }} -
{{ parseTime(arg.event.end,'{m}月{d}日') == parseTime(arg.event.start,'{m}月{d}日') ? parseTime(arg.event.end,'{h}:{i}') : parseTime(arg.event.end,'{m}月{d}日 {h}:{i}')}}
<span v-if="arg.event.extendedProps.num">时长:{{arg.event.extendedProps.num}}h</span>
<span v-if="!arg.event.extendedProps.num">时长:{{timeLength}}h</span>
</p>
<p v-else style="color:#61616E;margin:0px">
{{ parseTime(arg.event.start,'{m}月{d}日') == parseTime(arg.event.end,'{m}月{d}日') ? parseTime(arg.event.start,'{h}:{i}') : parseTime(arg.event.start,'{m}月{d}日 {h}:{i}') }} -
{{ parseTime(arg.event.end,'{m}月{d}日') == parseTime(arg.event.start,'{m}月{d}日') ? parseTime(arg.event.end,'{h}:{i}') : parseTime(arg.event.end,'{m}月{d}日 {h}:{i}')}}
<span v-if="arg.event.extendedProps.num">时长:{{arg.event.extendedProps.num}}h</span>
<span v-if="!arg.event.extendedProps.num">时长:{{timeLength}}h</span>
</p>
<el-button v-if="handleType !='read'&&arg.event.extendedProps.isCurrData" type="danger" circle @click="deleteItem(arg.event.id)"><el-icon size="10"><close-bold /></el-icon></el-button>
</div>
</el-tooltip>
</template>
</FullCalendar>
</div>
</template>
<FullCalendar ref="fullCalendar" :options='calendarOptions'> </FullCalendar>
给FullCalendar一个options里面配置FullCalendar需要的属性;el-tooltip包裹的是拖拽或者点击后渲染的数据,如下图。其中parseTime是封装的一个转换日期格式的方法
option中的属性配置
data(){
return{
calendarOptions : {
plugins: [
dayGridPlugin,
timeGridPlugin,
interactionPlugin // needed for dateClick
],// 引入的插件,比如fullcalendar/daygrid,fullcalendar/timegrid引入后才可显示月,周,日
height:"auto",
dragScroll: false,
headerToolbar: {
left: null,
center: 'prev,title,next',
right: null
},
initialView: 'timeGridWeek',// 默认为那个视图(月:dayGridMonth,周:timeGridWeek,日:timeGridDay)
allDaySlot:false,//是否显示日历上方的allDay
dayMaxEvents: true,// allow "more" link when too many events,只能选中或拖动一次
firstDay: new Date().getDay(), // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推 new Date().getDay()当前天
locale:'zh-cn',// 切换语言,当前为中文
unselectAuto:false,//当点击页⾯⽇历以外的位置时,是否⾃动取消当前的选中状态。false是不取消
// axisFormat:'H:(mm)',
// slotLabelFormat:'H:mm',
// soltDuration:'01:00:00',
// soltMinutes:40,
slotLabelFormat: {
hour: '2-digit',
minute: '2-digit',
meridiem: false,
hour12: false // 设置时间为24小时
},
initialEvents: [], // alternatively, use the `events` setting to fetch from a feed
editable: false,
selectable: true,
selectMirror: true,
weekends: true,
select: this.handleDateSelect,//当用户拖拽日期或时间时传递的参数
//dateClick: this.handleEventClick,//当用户点击日期或时间时触发的事件
// eventClick: this.handleEventClick,
viewRender:this.handleViewRender,
datesRender:this.handleViewRender,
selectOverlap: true,
moreLinkClassNames:'more-btns',
moreLinkContent : "查看更多",
events:[],
slotDuration: "01:00:00", //一格时间槽代表多长时间,默认00:30:00(30分钟)
eventStartEditable: false,
customButtons: {
prev: {
click: () => {
this.prevWeekClick();
}
},
next: {
click: () => {
this.nextWeekClick();
}
}
},
// nowIndicator: true, // 当前的时间线显示,为true时当前小时那一格有个红线,并且有红三角
// validRange: {//设置可显示的总日期范围,全局日期范围;超出范围按钮会禁用 还没用明白
// start: new Date().getDay()
// },
// hiddenDays: [3,6], //隐藏一周中的某一天或某几天,数组形式,如隐藏周二和周五:[2,5],默认不隐藏,除非weekends设置为false。
// eventColor: '#3BB2E3', // 全部日历日程背景色 事件的颜色,拖动日历或者点击日历时事件区域背景颜色
// themeSystem: 'bootstrap', // 主题色
// timeGridEventMinHeight: '20', // 设置事件的最小高度
// aspectRatio: 1.65, // 设置日历单元格宽度与高度的比例,数字代表宽高比例,默认1.3,但本地试没有效果
// displayEventTime: true, // 是否显示时
// eventLimit: true, // 设置月日程,与all-day slot的最大显示数量,超过的通过弹窗显示
},
selectInfo:'',//拖拽或者点击日历后得到的数据信息
timeLength:''//计算的时长
}
下面详细讲解实现效果的代码:
1,点击上一周下一周给后台传时间段获得上一周或者下一周的数据
在option中配置headerToolbar,customButtons中配置回调函数
方法:
let calendarApi = this.$refs['fullCalendar'].getApi();
const startTime = calendarApi.view.activeStart;
const endTime = calendarApi.view.activeEnd;
就可以得到点击上一周或者下一周的开始和结束时间
// 上周点击
prevWeekClick(){
let calendarApi = this.$refs['fullCalendar'].getApi();
calendarApi.prev();
const startTime = calendarApi.view.activeStart;
const endTime = calendarApi.view.activeEnd;
console.log(startTime,endTime);
this.$emit('clickPreNext',startTime,endTime)//拿到的日期格式传给父组件
},
// 下周点击
nextWeekClick(){
let calendarApi = this.$refs['fullCalendar'].getApi();
calendarApi.next();
const startTime = calendarApi.view.activeStart;
const endTime = calendarApi.view.activeEnd;
console.log(startTime,endTime);
this.$emit('clickPreNext',startTime,endTime)
}
初始化时没有点上一周下一周给后台传当前天以及当前天往后的7天日期计算方法:
getCurrentWeek() {
let now = new Date();
let nowTime = now.getTime();//返回当前天的毫秒数
let oneDayTime = 24*60*60*1000;//一天的毫秒数
let SundayTime = nowTime +6*oneDayTime;//当前天到第7天的毫秒数
let sunday = new Date(SundayTime);//第七天的日期
let beginDate=parseTime(new Date(),'{y}-{m}-{d}') //当前天日期转换格式
let endDate = parseTime(sunday,'{y}-{m}-{d}')//第7天日期转换格式
//打印查看结果
return {beginDate:beginDate,endDate:endDate} ;
},
给后台的格式是这样 beginDate:2022-05-05 endDate:2022-05-11
2,不可预约的地方置灰。
周几不可预约可能是个动态的数组,不可预约的时间段也可能是动态的数组返回,比如周三周六不可预约,早上8:00-10:00可预约,下午2:00-5:00可预约,把不可以预约的地方置灰
在渲染日历数据前调用下initOption方法,把置灰部分设置好
initOption(data){
if(this.handleType=='read'){//如果是查看按钮进来的日历页面不可编辑
this.calendarOptions.selectable=false
}else{
this.calendarOptions.selectable=true
}
let arr;//可以预约的星期数组
if(data.subscribeWeek){
arr =data.subscribeWeek.split(',').map(Number)//把字符串数组变成数字数组
//['1','2','3']转换成[1,2,3]
}else{
arr =[1,2,3,4,5]
}
//影藏不开放的星期
// let allDate=[0,1, 2, 3, 4, 5,6]
// let disableDate= allDate.filter(res=>{
// return !arr.some(item=>{return item==res})
// })
// this.calendarOptions.hiddenDays = disableDate
//置灰不开放的时间段和日期
if(data.openPeriodList){
let limitTime=data.openPeriodList//开放的时间段数组
let srt =limitTime.map((item,index)=>{
return {
startTime: item.startTime>9?(item.startTime+":00"):('0'+item.startTime+":00"),
endTime: item.endTime>9?(item.endTime+":00"):('0'+item.endTime+":00"),
daysOfWeek:arr
}
})
//面板灰色
this.calendarOptions.businessHours = this.calendarOptions.selectConstraint =srt
}
},
3,已预约 待审核 申请的样式
拿到后台返回的数据回显到日历看板上的方法
inintBoard(resData){
this.$refs.calendar.initOption(resData);
let data =resData.kanBanVOList//回显其他人预约过的数据到看板上
if(!data)return;
let newD = data.map((item,index)=>{
//根据返回的status判断当前是待审核 已预约的颜色,根据isCurrData判断是否是当前条的编辑查看按钮进来的数据
return {
id:index,
start: item.beginTime,
end: item.endTime,
className:item.isCurrData==true?'':(item.status==3?'borderBlue':(item.status==2?'borderOrange':"")),
color:item.isCurrData==true?'#3788d8':(item.status==3?'#D6F1FF':(item.status==2?'#FFECDC':(item.status==1?'#FFDAD6':"#ffffff"))),
num: item.appointment.appHours,
status: item.status,
isCurrData:item.isCurrData,
}
})
console.log(newD)
this.$refs.calendar.pushData(newD);
},
pushData(newD){
let calendarApi = this.$refs['fullCalendar'].getApi()
let calendarFunc = calendarApi.view.calendar
calendarFunc.unselect()
let getEvents = calendarFunc.getEvents()
if(getEvents && getEvents.length>0){//如果日历看板之前有数据,那么删除之前的数据
getEvents.map(item=>{
calendarFunc.getEventById(item.id).remove()
})
}
newD.map(item=>{
calendarFunc.addEvent(item)//数据填充到日历看板中
})
},
再根据动态类名写背景左边的颜色条
:deep(.borderBlue){ border-left: 5px solid #06A7FF !important; border-radius: 0;}
:deep(.borderOrange){border-left: 5px solid #FE9B02 !important; border-radius: 0;}
3,删除一个预约
点击编辑按钮进入页面,当前条数据上出现红色删除标志,删除的方法如下
<el-button v-if="handleType !='read'&&arg.event.extendedProps.isCurrData" type="danger" circle @click="deleteItem(arg.event.id)"><el-icon size="10"><close-bold /></el-icon></el-button>
// 删除预约
deleteItem(id){
let calendarApi = this.$refs['fullCalendar'].getApi()
let calendarFunc = calendarApi.view.calendar
let getEvents = calendarFunc.getEvents()
if(getEvents && getEvents.length>0){
if(id){
calendarFunc.getEventById(id).remove()//删除当前条预约
}
// else{
// getEvents.map(item=>{
// calendarFunc.getEventById(item.id).remove()
// })
// }
}
this.$emit('deleteData')//清空右侧表单数据
},
4,计算预约时长
<span v-if="!arg.event.extendedProps.num">
{{ parseTime(arg.event.start,'{m}月{d}日 {h}:{i}') }}-{{ parseTime(arg.event.end,'{m}月{d}日 {h}:{i}') }} 预约时长:{{timeLength}}小时
</span>
selectInfo是拖拽或者点击日历面板后返回的数据对象
let time1 = Date.parse(new Date(selectInfo.startStr));
let time2 = Date.parse(new Date(selectInfo.endStr));
this.timeLength = Math.abs((time2 - time1)/1000/3600);
5, 设置成24小时,时间列宽
option中配置
slotLabelFormat: {
hour: '2-digit',
minute: '2-digit',
meridiem: false,
hour12: false // 设置时间为24小时
},
:deep(.fc-scrollgrid) {
col {
width: 75px !important;
text-align: center;
}
}
:deep(.fc-direction-ltr) {
.fc-timegrid-slot-label-frame {
text-align: center;
}
}
去掉黄色当前天的背景
:deep(.fc .fc-day-today){
background: unset ;
}