fullcalendar_会议预约

项目中已经包含 ant-desing-vue 组件库和css预处理库scss配置

fullcalendar官网open in new window

安装依赖到Vue项目中

yarn add @fullcalendar/vue
yarn add @fullcalendar/core @fullcalendar/interaction @fullcalendar/timegrid
yarn add tippy.js

应用组件

<template>
  <div class="meeting">
    <a-spin :spinning="spinningLoading">
      <FullCalendar ref="myCalendar" :options="calendarOptions" />
    </a-spin>
  </div>
</template>
<script>
import FullCalendar from '@fullcalendar/vue'
import timeGridPlugin from '@fullcalendar/timegrid'
import interactionPlugin from '@fullcalendar/interaction'
import moment from 'moment'
import tippy from 'tippy.js'
import 'tippy.js/dist/tippy.css'
import 'tippy.js/themes/light.css'

function getWeekDate() {
  const start_week = moment().startOf('week')
  const end_week = moment().endOf('week')
  let start = moment(start_week.add(1, 'days')).format('YYYY-MM-DD')
  let end = moment(end_week.add(1, 'days')).format('YYYY-MM-DD')
  return { start, end }
}

function formatDate(params) {
  if (!params) return '格式化日期不能为空'
  let date = moment(params).format('YYYY-MM-DD')
  date = date.replace('星期', '周')
  let vNowDate = moment(new moment(date).format('YYYY-MM-DD'))
  let vWeekOfDay = moment(vNowDate).format('E') //算出这周的周几
  let weekList = ['', '周一', '周二', '周三', '周四', '周五', '周六', '周日']
  let result = `${date}${weekList[vWeekOfDay]}`
  return result
}

export default {
  name: 'CalendarDemo',
  components: {
    FullCalendar
    // addEdit
  },
  data() {
    return {
      spinningLoading: false,
      calendarOptions: {
        // listPlugin
        // 引入的插件,比如fullcalendar/daygrid,fullcalendar/timegrid引入后才可显示月,周,日
        plugins: [interactionPlugin, timeGridPlugin],
        initialView: 'timeGridWeek', // 默认为那个视图(月:dayGridMonth 周:timeGridWeek 日:timeGridDay)
        firstDay: 1, // 设置一周中显示的第一天是哪天,周日是0,周一是1,类推
        locale: 'zh-cn', // 切换语言,当前为中文
        eventColor: '#3BB2E3', // 全部日历日程背景色
        themeSystem: 'bootstrap', // 主题色(本地测试未能生效)
        initialDate: moment().format('YYYY-MM-DD'), // 自定义设置背景颜色时一定要初始化日期时间
        timeGridEventMinHeight: '20', // 设置事件的最小高度
        // height: 800, //设置日历的高度,包括header日历头部,默认未设置,高度根据aspectRatio值自适应。
        // contentHeight: 820, //设置日历主体内容的高度,不包括header部分,默认未设置,高度根据aspectRatio值自适应。
        aspectRatio: 2.2, //设置日历单元格宽度与高度的比例。
        // displayEventTime: false, // 是否显示时间
        allDaySlot: false, // 周,日视图时,all-day 不显示
        eventLimit: true, // 设置月日程,与all-day slot的最大显示数量,超过的通过弹窗显示
        handleWindowResize: true, //是否随浏览器窗口大小变化而自动变化
        nowIndicator: false, // 是否显示当前时间轴
        headerToolbar: {
          // 日历头部按钮位置
          left: '',
          // center: 'prevYear,prev title next,nextYear',
          right: ''
          // right: 'today dayGridMonth,timeGridWeek,timeGridDay'
        },
        buttonText: {
          today: '今天',
          month: '月',
          week: '周',
          day: '日'
        },
        slotLabelFormat: {
          hour: 'numeric',
          minute: '2-digit',
          meridiem: false,
          hour12: false // 设置时间为24小时
        },
        // 不可预约背幕设置
        // businessHours: [
        //   {
        //     daysOfWeek: [1],
        //     startTime: '12:00', // 可接受字符串或处理函数
        //     endTime: '13:00'
        //   }
        // ],
        eventLimitNum: {
          // 事件显示数量限制(本地测试未能生效)
          dayGrid: {
            eventLimit: 5
          },
          timeGrid: {
            eventLimit: 2
          }
        },
        //视图数据加载中、加载完成触发
        loading: this.loadingHandle,
        views: {
          //对应周视图调整
          timeGridWeek: {
            slotMinTime: '08:00', //周视图开始时间
            slotMaxTime: '21:00', //周视图结束时间
            displayEventTime: false, //是否显示时间
            dayHeaderContent(item) {
              let _date = formatDate(item.date)
              return {
                html: `<div>${_date}</div>`
              }
            }
          }
        },
        // 日程事件 json
        events: [
          {
            title: 'xxx会议1',
            date: '2023-03-16 12:00',
            date: '2023-03-16 13:00',
            color: '#05818A',
            borderColor: '#05818A',
            textColor: '#ffffff',
            id: '2c69cbc273ca263119714a85320e75c9',
            extendedProps: {
              date: '2023-03-17',
              startTime: '12:00',
              endTime: '13:00'
            }
          },
          {
            title: 'xxxx会议',
            start: '2023-03-16 08:23',
            end: '2023-03-16 09:10',
            editable: false,
            id: 'hjhgkjgp[agapoigadgidfihdi',
            color: '#05818A',
            borderColor: '#05818A',
            textColor: '#ffffff',
            extendedProps: {
              date: '2023-03-15',
              startTime: '08:23',
              endTime: '09:10'
            }
          }
        ],
        // 事件
        dateClick: this.handleDateClick, // 点击日历空白处
        eventClick: this.handleEventClick, // 点击日历日程事件
        eventMouseEnter: this.handleEventMouseEnter, // 鼠标移入
        eventMouseLeave: this.handleEventMouseLeave, // 鼠标移出
        allowContextMenu: false,
        editable: true, // 是否可以进行(拖动、缩放)修改
        eventStartEditable: true, // Event日程开始时间可以改变,默认true,如果是false其实就是指日程块不能随意拖动,只能上下拉伸改变他的endTime
        eventDurationEditable: true, // Event日程的开始结束时间距离是否可以改变,默认true,如果是false则表示开始结束时间范围不能拉伸,只能拖拽
        selectable: false, // 是否可以选中日历格
        selectMirror: true,
        selectMinDistance: 0, // 选中日历格的最小距离
        dayMaxEvents: true,
        weekends: true,
        navLinks: false, // 表头下钻链接
        selectHelper: false,
        slotEventOverlap: false, // 相同时间段的多个日程视觉上是否允许重叠,默认true允许
        eventContent: this.renderEverntContent
      }
    }
  },
  mounted() {
    console.log('getWeekDate() :>> ', getWeekDate())
  },
  methods: {
    // 获取预约会议内容
    loadDataHandle() {
      let params = {
        ...getWeekDate()
      }
      this.spinningLoading = true
      getAction('', params).then((res) => {
        if (res.success) {
          this.calendarOptions.events = res.result
          this.spinningLoading = false
        }
      })
    },
    // 点击已预约会议
    handleEventClick(eventSource) {
      let data = eventSource.event._def
      let id = data.publicId
      this.$refs.addEdit.edit({ id: id })
    },
    // 空白的日期区,单击时触发
    handleDateClick(info) {
      let calendarApi = this.$refs['myCalendar'].getApi()
      console.log('calendarApi :>> ', calendarApi)
      let date = moment(info.dateStr).format('YYYY-MM-DD')
      // TODO:实现新增预约会议弹框
    },
    // 自定义日程渲染函数
    renderEverntContent(arg) {
      let italicEl = document.createElement('div')
      let obj = arg.event._def
      if (obj) {
        let html = `
        <div style="padding: 1px 6px">
          <h4 style="padding:0;margin:0">${obj.title}</h4>
        </div>
        `
        italicEl.innerHTML = html
      }
      let arrayOfDomNodes = [italicEl]
      return { domNodes: arrayOfDomNodes }
    },
    // 鼠标移入
    handleEventMouseEnter(mouseEnter) {
      let { event, el, jsEvent, view } = mouseEnter

      let obj = event._def
      let htmlTemplate = `<div style='padding: 2px 6px;min-width: 260px'>
          <h4 style='padding: 0;margin: 0;'>${obj.title}</h4>
          <div>
            <label>会议时间:</label>
            <span>${obj.extendedProps.date} ${obj.extendedProps.startTime}~${obj.extendedProps.endTime}</span>
          </div>
          <div>
            <label>会议地点:</label>
            <span>
              xxxxxxxx办公楼1楼105室
            </span>
          </div>
        `
      let options = {
        theme: 'light', //主题选取
        arrow: true,
        interactive: true, //可交互的
        placement: 'right', //悬浮框位置
        allowHTML: true, //是否允许html文本
        zIndex: 99999,
        content: htmlTemplate
      }
      tippy(el, options)
    },
    // 鼠标移出
    handleEventMouseLeave(mouseLeave) {
      // let { event, el, jsEvent, view } = mouseLeave
      // tippy(el, {
      //   // onCreate(instance) {
      //   //   console.log('instance :>> ', instance)
      //   //   instance.unmount()
      //   // },
      //   // onDestroy(instance) {
      //   //   console.log('instance -=1:>> ', instance)
      //   //   instance.destroy()
      //   //   // instance.unmount()
      //   // }
      // })
    },
    // 新增测试按钮
    addTestHandle() {
      let copyData = JSON.parse(JSON.stringify(this.calendarOptions.events))
      let addData = [
        {
          title: '讨论会议',
          editable: false,
          start: moment().format('YYYY-MM-DD') + ' 08:23',
          end: moment().format('YYYY-MM-DD') + ' 12:10',
          id: 'gvashpksahkjo-ajhps]ajh]opjhe',
          color: '#05818A',
          borderColor: '#05818A',
          textColor: '#ffffff',
          extendedProps: {
            date: '2023-03-13',
            startTime: '08:23',
            endTime: '12:10'
          }
        }
      ]
      this.calendarOptions.events = [...copyData, ...addData]
    },
    loadingHandle(isLoading, view) {
      if (isLoading == true) {
        this.spinningLoading = true
      } else if (isLoading == false) {
        this.spinningLoading = false
      } else {
        this.spinningLoading = false
        this.$message.warning('加载异常')
      }
    }
  }
}
</script>

<style scoped lang="scss">
.meeting {
  padding: 35px;
  button {
    margin-bottom: 16px;
    position: relative;
    display: inline-block;
    font-weight: 400;
    white-space: nowrap;
    text-align: center;
    background-image: none;
    border: 1px solid transparent;
    box-shadow: 0 2px 0 rgb(0 0 0 / 2%);
    cursor: pointer;
    transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
    user-select: none;
    touch-action: manipulation;
    height: 32px;
    padding: 0 15px;
    font-size: 14px;
    border-radius: 1px;
  }
  .meeting-hander {
    display: flex;
    margin-bottom: 8px;
    .meeting-hander-select {
      margin-right: 16px;
    }
    .meeting-hander-info {
      display: flex;
      flex: 1;
      justify-content: flex-start;
      align-items: flex-end;
      padding-bottom: 4px;
      &-item {
        span {
          font-weight: 700;
        }
      }
      &-item:not(:first-child) {
        margin-left: 18px;
      }
    }
    .meeting-hander-legend {
      margin-left: 10px;
      display: flex;
      align-items: flex-end;
      &-item {
        width: 26px;
        height: 26px;
        border: 1px solid #057f88;
        background: #057f88;
      }
      &-item:last-child {
        border: 1px solid #d8d8d8;
        background-color: #d8d8d8;
        margin-left: 10px;
      }
    }
  }
  :deep(.fc-toolbar) {
    display: none;
  }
  :deep(.fc-col-header) {
    height: 39px;
    line-height: 39px;
    background-color: #c6e9ed;
  }
  :deep(.fc-theme-standard td, .fc-theme-standard th) {
    border: 1px solid #e9e9e9;
  }
  :deep(.fc-timegrid-slot) {
    height: 40px;
  }
}
</style>