先看下效果图

需求:实现预约功能,设置过不可预约的时间段置灰不可选中,比如上图中周六周天不可预约,晚上23:00到24:00不可预约;当前天在第一个,往后排7天;点击上一周下一周时切换数据重新渲染后台返回的数据;待审核已预约的数据后台返回根据状态展示不同的颜色;在FullCalendar中拖拽或者点击时填充开始时间结束时间,时长到右侧的表单中;

首先要安装完引用插件:

官网路径:Vue Component - Docs | FullCalendar

如果使用Vue 2npm install --save @fullcalendar/vue @fullcalendar/core

如果使用Vue 3npm 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">图例说明:&nbsp;&nbsp;
      <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 ;
}

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐