fullcalendar日历表

一、引言

FullCalendar 是一个功能强大的 JavaScript 日历插件,它提供了丰富的功能和高度的可定制性,可以轻松地集成到各种 Web 应用程序中。本文将分享 FullCalendar 的基本用法、主要功能以及一些实际应用中的经验。

二、特点

(1)丰富的视图模式

FullCalendar 支持多种视图模式,包括月视图、周视图、日视图和列表视图等。用户可以根据自己的需求选择合适的视图来查看日程安排。

(2)事件管理

  1. 可以轻松地添加、编辑和删除事件。
  2. 支持设置事件的标题、描述、开始时间、结束时间等属性。
  3. 可以对事件进行分类和分组,方便用户管理和查看。

(3)可定制性强

  1. 可以通过 CSS 和 JavaScript 进行高度定制,包括颜色、字体、布局等方面。
  2. 支持自定义事件渲染器,允许用户根据自己的需求来显示事件。

(4)响应式设计

FullCalendar 具有良好的响应式设计,可以在不同尺寸的设备上良好地显示,包括桌面电脑、平板电脑和手机等。

三、基本用法

(1)安装 FullCalendar及月视图、周视图、日视图和列表视图插件

bash 复制代码
npm install @fullcalendar/vue
npm install @fullcalendar/code
npm install @fullcalendar/daygrid
npm install @fullcalendar/timegrid
npm install @fullcalendar/interaction
npm install @fullcalendar/list
// npm install @fullcalendar/resource-timeline

(2)在页面中引入

javascript 复制代码
import FullCalendar from '@fullcalendar/vue'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import interactionPlugin from '@fullcalendar/interaction'
import listPlugin from '@fullcalendar/list'

(3)应用

ini 复制代码
<full-calendar
  ref="fullCalendar"
  style="height: 100%"
  :options="calendarOptions"  // 参数
>
</full-calendar>
kotlin 复制代码
data() {
    return {
      calendarOptions: {
        height: 780,
        contentHeight: 800,
        aspectRatio: 2, // 设置日历单元格宽度与高度的比例。感觉没啥用
        handleWindowResize: true,
        axisFormat: 'h(:mm)tt',
        firstHour: 0,
        plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin],
        headerToolbar: { // 设置表头
          left: '',
          center: 'title',
          right: 'timeGridWeek,dayGridMonth'
        },
        buttonText: {
          // 设置按钮
          // today: '今天',
          month: '月',
          week: '周'
          // dayGrid: '天'
        },
        // allDaySlot: false, // 是否需要全天一栏
        editable: true,
        selectable: true,
        navLinks: true,
        // displayEventEnd: true,//所有视图显示结束时间
        initialView: 'timeGridWeek', // 设置默认显示周日历,可选月、日
        eventDurationEditable: true, // 可以调整事件的时间
        dateClick: this.handleDateClick,
        eventClick: this.handleEventClick,
        eventsSet: this.handleEvents,
        select: this.handleDateSelect,
        // 设置日程事件
        events: [
          {
            id: 1,
            title: '黄',
            start: '2024-07-24 09:30:20',
            end: '2024-07-25 15:30:10',
            color: '#f08f00',
            overlap: true,
            editable: true
          },
          ......
        ],
        eventColor: '#f08f00', // 修改日程背景色
        locale: 'zh-cn', // 设置语言
        // weekNumberCalculation: 'ISO', // 周数
        initialDate: this.$moment().format('YYYY-MM-DD HH:mm:ss'),
        // 日历触发拖拽的钩子
        drop: this.newEventDrop,
        droppable: true,
        displayEventTime: false, // 不显示日程时间,只显示title
        eventMouseout: this.handleMouseLeave,
        // eventClick: this.handleEventClick, // 日程事件点击
        eventDrop: this.handleEventDrop, // 拖动事件
        allDaySlot: false, // 不显示all-day那一行
        firstDay: 0,
        customButtons: {},
        minTime: '00:00',
        scrollTime: '00:00',
        // 时间轴间距
        slotMinTime: '00:00',
        slotMaxTime: '24:00',
        slotEventOverlap: false,
        timeFormat: 'H:mmtt{-H:mmtt }'
      }
    }
  },

效果图:

四、根据需求定制化

(1)表头定制化

1、左边显示日历日期,中间空白不显示,右边显示切换日历按钮

kotlin 复制代码
headerToolbar: {
  left: 'title',
  center: '',
  right: 'timeGridWeek,dayGridMonth'
},
buttonText: {
  // 设置按钮
  // today: '今天',
  month: '月',
  week: '周'
  // dayGrid: '天'
},
customButtons: {
  dayGridMonth: {
    text: '月',
    click: (data) => {
      console.log(data)
      this.$refs.fullCalendar.getApi().changeView('dayGridMonth')
       this.dateType = 'month'
    }
  },
  timeGridWeek: {
    text: '周',
    click: (data) => {
      console.log(data)
      this.$refs.fullCalendar.getApi().changeView('timeGridWeek')
      this.dateType = 'week'
    }
  }
},

2、调整样式

css 复制代码
 ::v-deep .fc-header-toolbar {
    margin-bottom: 12px;
    .fc-toolbar-title {
      font-size: 16px;
      font-weight: 700;
      line-height: 28px;
      color: #333;
      margin-left: 10px;
    }
  }
  
::v-deep .fc-button {
  height: 28px;
  font-size: 14px;
  display: flex;
  align-items: center;
  border: 1px solid rgb(45, 140, 240);
  border-radius: 2px;
  background: rgba(45, 140, 240, 0.1);
  color: #2d8cf0;
  box-shadow: none !important;
  &:hover {
    border: 1px solid rgb(45, 140, 240) !important;
    border-radius: 2px;
    background: rgb(45, 140, 240) !important;
    color: #fff;
  }
}
::v-deep .fc-button-active {
  border: 1px solid rgb(45, 140, 240) !important;
  border-radius: 2px;
  background: rgb(45, 140, 240) !important;
}

(2)周日历定制化

1、自定义顶部日期

在参数calendarOptions中添加views

kotlin 复制代码
views: {
  timeGridWeek: { // 周日历
    dayHeaderContent(item) {
      // 自定义表头,高亮显示当天日期
      return {
        html: `<div class="day-header"><div class="day ${
          item.isToday ? 'today' : ''
        }">${item.date.getDate()}</div><div class="week">星期${
          item.text?.split('周')[1]
        }</div></div>`
      }
    }
    // eventMaxStack: 4 // 同一时段最多展示事件数
    // moreLinkClick: 'week',
    // moreLinkClick: this.moreLinkClick,
  }
}
css 复制代码
// 周视图
      .day-header {
        display: flex;
        align-items: center;
        color: #333;
        .day {
          color: rgb(51, 51, 51);
          font-size: 28px;
        }
        .week {
          color: rgb(153, 153, 153);
          line-height: 21px;
          letter-spacing: 1px;
          margin-left: 8px;
          font-size: 14px;
        }
        .today {
          display: flex;
          justify-content: center;
          align-items: center;
          width: 32px;
          height: 32px;
          border-radius: 50%;
          background-color: #2d8cf0;
          color: #fff;
          font-size: 18px;
        }
      }
2、调整左侧时间轴样式
arduino 复制代码
// 添加参数
scrollTime: '00:00', // 默认从00:00开始显示、滚动
slotDuration: '01:00', // 时间间隔
slotEventOverlap: false, // 重叠时间段不覆盖
3、自定义日程事件样式

需要与后台接口联调,获取当前周的日程数据,根据岗位颜色显示排班颜色

csharp 复制代码
// 查询值班信息
    async getScheduleInfo() {
      const url = `/api/xxx`
      const params = {
        dateType: this.dateType,
        scheduleDate: this.currentDate
      }
      const res = await this.$http.get(url, { params })
      if (res.successful) {
        this.calendarOptions.events = res?.data.map((item) => ({
          ...item,
          title: item.userName, // 标题
          start: item.scheduleStartTime, // 开始时间
          end: item.scheduleEndTime, // 结束时间
          color: item.postColor || '#999999', // 颜色
          textColor: item.postColor || '#999999', // 字体颜色
          visible: false, // 是否弹出详情弹框
          overlap: false,
          editable: false
        }))
      } else {
        this.$message.error(res.msg, 5)
      }
    },
perl 复制代码
<full-calendar ref="fullCalendar" :options="calendarOptions">
      <template v-slot:eventContent="arg">
        <div
          class="event-content text-ellipsis"
          :style="
            arg.view.type === 'timeGridWeek'
              ? {
                  borderTop: `2px solid ${arg.event.borderColor}`,
                  borderColor: arg.event.borderColor,
                  backgroundColor: `${hexToRgba(arg.event.borderColor, 0.1)}`
                }
              : {
                  borderLeft: `2px solid ${arg.event.borderColor}`,
                  backgroundColor: `${hexToRgba(arg.event.borderColor, 0.1)}`,
                  color: arg.event.borderColor
                }
          "
          :title="
            arg.view.type === 'timeGridWeek'
              ? arg.event.title
              : `${$moment(arg.event.start).format('HH:mm')}~${$moment(arg.event.end).format(
                  'HH:mm'
                )} ${arg.event.title}`
          "
        >
          {{
            arg.view.type === 'timeGridWeek'
              ? arg.event.title
              : `${$moment(arg.event.start).format('HH:mm')}~${$moment(arg.event.end).format(
                  'HH:mm'
                )} ${arg.event.title}`
          }}
        </div>
      </template>
    </full-calendar>
css 复制代码
.event-content {
    width: 100%;
    height: 100%;
    text-align: center;
    padding-top: 3px;
  }
4、点击日程事件弹出详情弹框

在参数中定义点击事件

kotlin 复制代码
eventClick: this.handleEventClick, // 日程事件点击

实现点击事件方法

javascript 复制代码
handleEventClick(e) {
      const result = this.calendarOptions.events.map((item) => {
        return {
          ...item,
          visible: item.id === e.event.id // 将当前日程的visible设置为true
        }
      })
      this.calendarOptions.events = result
    },

定义日程事件的详情弹框

ini 复制代码
<full-calendar ref="fullCalendar" :options="calendarOptions">
      <template v-slot:eventContent="arg">
        <div
          class="event-content text-ellipsis"
          :style="
            arg.view.type === 'timeGridWeek'
              ? {
                  borderTop: `2px solid ${arg.event.borderColor}`,
                  borderColor: arg.event.borderColor,
                  backgroundColor: `${hexToRgba(arg.event.borderColor, 0.1)}`
                }
              : {
                  borderLeft: `2px solid ${arg.event.borderColor}`,
                  backgroundColor: `${hexToRgba(arg.event.borderColor, 0.1)}`,
                  color: arg.event.borderColor
                }
          "
          :title="
            arg.view.type === 'timeGridWeek'
              ? arg.event.title
              : `${$moment(arg.event.start).format('HH:mm')}~${$moment(arg.event.end).format(
                  'HH:mm'
                )} ${arg.event.title}`
          "
        >
          {{
            arg.view.type === 'timeGridWeek'
              ? arg.event.title
              : `${$moment(arg.event.start).format('HH:mm')}~${$moment(arg.event.end).format(
                  'HH:mm'
                )} ${arg.event.title}`
          }}
        </div>
        <a-popover
          title=""
          v-model="arg.event.extendedProps.visible"
          :zIndex="100"
          placement="bottom"
        >
          <template slot="content">
            <popper :dataInfo="arg.event" @success="handleSuccess"></popper>
          </template>
        </a-popover>
      </template>
    </full-calendar>
    
    
import Popper from './Modal/Popper.vue'

Popper.vue

xml 复制代码
<template>
  <div id="popper" ref="popper" class="popper-container">
    <div class="container-item">
      <div class="label">值班时间</div>
      <div class="content">
        {{ dataInfo?.extendedProps?.scheduleDate }} {{ $moment(dataInfo.start).format('HH:mm') }} ~
        {{ $moment(dataInfo.end).format('HH:mm') }}
      </div>
    </div>
    <div class="container-item user-info">
      <div class="label">值班人员</div>
      <div class="content">{{ dataInfo.extendedProps.postName || '-' }}:{{ dataInfo.title }}</div>
    </div>
    <div
      class="footer"
      v-if="
        dataInfo.extendedProps.scheduleDate > $moment().format('YYYY-MM-DD') && dutyLevel === '1'
      "
    >
      <a-divider />
      <a-popconfirm title="确定删除?" @confirm="handleDelete">
        <a><a-icon type="delete" /></a>
      </a-popconfirm>
    </div>
  </div>
</template>
<script>
import Cookies from 'js-cookie'

export default {
  name: 'Popper',
  props: {
    dataInfo: {
      type: Object,
      required: false,
      default: () => {}
    }
  },
  data() {
    return {
      dutyLevel: Cookies.get('dutyLevel')
    }
  },
  methods: {
    async handleDelete() {
      const url = `/api/xxx`
      const params = {
        scheduleIdList: [this.dataInfo?.id]
      }
      const res = await this.$http.post(url, params)
      if (res.successful) {
        this.$message.success('删除成功', 5)
        this.$emit('success')
      } else {
        this.$message.error(res.msg, 5)
      }
    }
  }
}
</script>

<style lang="scss" scoped>
::v-deep .ant-divider {
  margin: 12px 0 !important;
}
.popper-container {
  background: #fff;
  border-radius: 4px;
  width: 207px;
  .container-item {
    .label {
      color: rgb(153, 153, 153);
      font-size: 14px;
      line-height: 21px;
      letter-spacing: 1px;
    }
    .content {
      color: rgb(51, 51, 51);
      font-size: 14px;
      line-height: 21px;
      letter-spacing: 1px;
    }
  }
  .user-info {
    margin-top: 15px;
  }
  .footer {
    text-align: center;
  }
  .anticon-delete {
    color: red;
  }
}
</style>

点击其他任意地方关闭详情弹框

javascript 复制代码
// 关闭排班气泡弹框
    handleEventVisible() {
      const result = this.calendarOptions.events.map((item) => {
        return {
          ...item,
          visible: false
        }
      })
      this.calendarOptions.events = result
    }

(3)月日历定制化

1、自定义顶部星期

在参数views中添加月视图dayGridMonth

javascript 复制代码
views: {
  // 月视图阳历转农历
  dayGridMonth: {
    displayEventTime: true, // 是否显示时间
    dayHeaderContent(item) {
    // 自定义表头
      return {
        html: `<div class="day-header">星期${item.text?.split('周')[1]}</div>`
      }
    }
  },
}
2、自定义日期单元格

重写dayCellContent,添加农历日期,使用插件js-calendar-converter的solar2lunar方法将日期转为农历日期,并且高亮显示当天日期

kotlin 复制代码
views: {
  // 月视图阳历转农历
  dayGridMonth: {
    dayCellContent: (item) => {
      const year = this.$moment(item.date).format('YYYY')
      const month = this.$moment(item.date).format('MM')
      const day = this.$moment(item.date).format('DD')
      const newDate = calendarConverter.solar2lunar(year, month, day)
        return {
          html: `<div class="month-date"><div class="cDay">${newDate.cDay}</div><div  class="lunar">${newDate.IDayCn}</div></div>`
        }
      },
   },
}
css 复制代码
      .fc-daygrid-day-number {
          width: 100%;
          .month-date {
            width: 100%;
            display: flex;
            justify-content: space-between;
            .lunar {
              color: #999999;
            }
          }
        }
3、自定义日程事件样式

通过判断当前日历的类型是周日历(timeGridWeek)还是月日历(dayGridMonth)来分别定制样式,日程事件详情弹框与周日历一样

ini 复制代码
    <full-calendar ref="fullCalendar" :options="calendarOptions">
      <template v-slot:eventContent="arg">
        <div
          class="event-content text-ellipsis"
          :style="
            arg.view.type === 'timeGridWeek'
              ? {
                  borderTop: `2px solid ${arg.event.borderColor}`,
                  borderColor: arg.event.borderColor,
                  backgroundColor: `${hexToRgba(arg.event.borderColor, 0.1)}`
                }
              : {
                  borderLeft: `2px solid ${arg.event.borderColor}`,
                  backgroundColor: `${hexToRgba(arg.event.borderColor, 0.1)}`,
                  color: arg.event.borderColor
                }
          "
          :title="
            arg.view.type === 'timeGridWeek'
              ? arg.event.title
              : `${$moment(arg.event.start).format('HH:mm')}~${$moment(arg.event.end).format(
                  'HH:mm'
                )} ${arg.event.title}`
          "
        >
          {{
            arg.view.type === 'timeGridWeek'
              ? arg.event.title
              : `${$moment(arg.event.start).format('HH:mm')}~${$moment(arg.event.end).format(
                  'HH:mm'
                )} ${arg.event.title}`
          }}
        </div>
        <a-popover
          title=""
          v-model="arg.event.extendedProps.visible"
          :zIndex="100"
          placement="bottom"
        >
          <template slot="content">
            <popper :dataInfo="arg.event" @success="handleSuccess"></popper>
          </template>
        </a-popover>
      </template>
    </full-calendar>
4、点击当前日期弹框显示当前日期所有日程事件

在参数中定义日期点击事件

kotlin 复制代码
dateClick: this.handleDateClick,
ini 复制代码
    handleDateClick(e) {
      if (e.view.type === 'dayGridMonth') {
        const dateList = this.calendarOptions.events.filter(
          (item) => item.scheduleDate === e?.dateStr
        )
        if (dateList && dateList?.length > 0) {
          this.$refs.eventModalRef.visible = true
          this.$refs.eventModalRef.scheduleDate = e?.dateStr
          this.$refs.eventModalRef.getScheduleList()
        }
      }
    },

(4)数据更新

日历视图的日期跟随选择的日期跳转(这里用到了日历的gotoDate方法,跳转到指定日期范围),并且在切换日历视图的时候也进行更新

javascript 复制代码
watch: {
    currentDate: {
      handler(newVal) {
        if (newVal) {
          const fullCalendar = this.$refs.fullCalendar.calendar
          fullCalendar.gotoDate(newVal) // 日历视图跟随日期跳转
          this.getScheduleInfo()
        }
      },
      deep: true
      // immediate: true
    },
    dateType: {
      handler(newVal) {
        if (newVal) {
          this.getScheduleInfo()
        }
      }
    }
  },

五、完整代码

vue:

xml 复制代码
<template>
  <div class="full-calendar">
    <full-calendar ref="fullCalendar" :options="calendarOptions">
      <template v-slot:eventContent="arg">
        <div
          class="event-content text-ellipsis"
          :style="
            arg.view.type === 'timeGridWeek'
              ? {
                  borderTop: `2px solid ${arg.event.borderColor}`,
                  borderColor: arg.event.borderColor,
                  backgroundColor: `${hexToRgba(arg.event.borderColor, 0.1)}`
                }
              : {
                  borderLeft: `2px solid ${arg.event.borderColor}`,
                  backgroundColor: `${hexToRgba(arg.event.borderColor, 0.1)}`,
                  color: arg.event.borderColor
                }
          "
          :title="
            arg.view.type === 'timeGridWeek'
              ? arg.event.title
              : `${$moment(arg.event.start).format('HH:mm')}~${$moment(arg.event.end).format(
                  'HH:mm'
                )} ${arg.event.title}`
          "
        >
          {{
            arg.view.type === 'timeGridWeek'
              ? arg.event.title
              : `${$moment(arg.event.start).format('HH:mm')}~${$moment(arg.event.end).format(
                  'HH:mm'
                )} ${arg.event.title}`
          }}
        </div>
        <a-popover
          title=""
          v-model="arg.event.extendedProps.visible"
          :zIndex="100"
          placement="bottom"
        >
          <template slot="content">
            <popper :dataInfo="arg.event" @success="handleSuccess"></popper>
          </template>
        </a-popover>
      </template>
    </full-calendar>
    <eventModal ref="eventModalRef" @success="handleSuccess" />
  </div>
</template>

<script>
import calendarConverter from 'js-calendar-converter'
import FullCalendar from '@fullcalendar/vue'
import dayGridPlugin from '@fullcalendar/daygrid'
import timeGridPlugin from '@fullcalendar/timegrid'
import interactionPlugin from '@fullcalendar/interaction'
import listPlugin from '@fullcalendar/list'
// import resourceTimelinePlugin from '@fullcalendar/resource-timeline'
import dispatchPlan from '@/mixins/dispatch-plan'
import eventModal from './Modal/eventModal.vue'
import Popper from './Modal/Popper.vue'

export default {
  name: 'Rota',
  components: { FullCalendar, eventModal, Popper },
  mixins: [dispatchPlan],
  props: ['currentDate'],
  data() {
    return {
      dateType: 'week',
      calendarOptions: {
        height: 715,
        contentHeight: 800,
        aspectRatio: 1, // 设置日历单元格宽度与高度的比例。
        handleWindowResize: true, // 是否随浏览器窗口大小变化而自动变化
        plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin],
        initialView: 'timeGridWeek', // 设置默认显示月,可选周、日
        headerToolbar: {
          left: 'title',
          center: '',
          right: 'timeGridWeek,dayGridMonth'
        },
        buttonText: {
          // 设置按钮
          // today: '今天',
          month: '月',
          week: '周'
          // dayGrid: '天'
        },
        customButtons: {
          dayGridMonth: {
            text: '月',
            click: (data) => {
              console.log(data)
              this.$refs.fullCalendar.getApi().changeView('dayGridMonth')
              this.dateType = 'month'
            }
          },
          timeGridWeek: {
            text: '周',
            click: (data) => {
              console.log(data)
              this.$refs.fullCalendar.getApi().changeView('timeGridWeek')
              this.dateType = 'week'
            }
          }
        },
        // allDaySlot: false,
        // editable: true, // 是否可以编辑,影响拖动
        selectable: true,
        navLinks: false, // 天链接
        dayMaxEvents: false, // 限制事件最大数量
        // displayEventEnd: true,//所有视图显示结束时间
        eventDurationEditable: true, // 可以调整事件的时间
        dateClick: this.handleDateClick,
        eventClick: this.handleEventClick, // 日程事件点击
        eventsSet: this.handleEvents,
        select: this.handleDateSelect,
        // nowIndicator: true, // 显示当前时间线
        // 设置日程
        events: this.getScheduleInfo(),
        eventColor: '#f08f00', // 修改日程背景色
        locale: 'zh-cn', // 设置语言
        initialDate: this.$moment().format('YYYY-MM-DD HH:mm:ss'),
        displayEventTime: false, // 不显示日程时间,只显示title
        // eventMouseEnter: this.eventMouseEnter,
        // eventMouseLeave: this.eventMouseLeave,
        // 日历触发拖拽的钩子
        // drop: this.newEventDrop,
        // droppable: false, // 拖拽
        // eventDrop: this.handleEventDrop, // 拖动事件
        allDaySlot: false, // 不显示all-day那一行
        scrollTime: '00:00',
        slotDuration: '01:00', // 时间间隔
        slotEventOverlap: false, // 重叠时间段不覆盖
        slotLabelFormat: {
          hour: '2-digit',
          minute: '2-digit',
          omitZeroMinute: false, // 分钟为00时仍显示
          hour12: false
        },
        eventTimeFormat: {
          hour: '2-digit',
          minute: '2-digit',
          omitZeroMinute: false,
          hour12: false
        },
        firstDay: 1,
        // weekNumbers: true, // 启用周编号
        // weekNumberContent: (arg) => {
        //   return `第${arg.num}周`
        // },
        weekNumberCalculation: 'ISO', // 周编号的计算方式
        // dayMaxEventRows: true,
        // datesSet: this.datesSet, // 切换视图时触发
        slotMinTime: '00:00', // 时间轴开始时间
        // slotMaxTime: '24:00',
        // 视图的一些基本设置
        views: {
          // 月视图阳历转农历
          dayGridMonth: {
            displayEventTime: true, // 是否显示时间
            // dayMaxEventRows: 5, // adjust to 6 only for timeGridWeek/timeGridDay
            // titleFormat: { year: "numeric", month: "2-digit", day: "2-digit" }, //控制日历显示的标题
            // moreLinkClick: 'popover',
            dayCellContent: (item) => {
              const year = this.$moment(item.date).format('YYYY')
              const month = this.$moment(item.date).format('MM')
              const day = this.$moment(item.date).format('DD')
              const newDate = calendarConverter.solar2lunar(year, month, day)
              return {
                html: `<div class="month-date"><div class="cDay">${newDate.cDay}</div><div class="lunar">${newDate.IDayCn}</div></div>`
              }
            },
            dayHeaderContent(item) {
              // 自定义表头
              return {
                html: `<div class="day-header">星期${item.text?.split('周')[1]}</div>`
              }
            }
          },
          timeGridWeek: {
            dayHeaderContent(item) {
              // 自定义表头
              return {
                html: `<div class="day-header"><div class="day ${
                  item.isToday ? 'today' : ''
                }">${item.date.getDate()}</div><div class="week">星期${
                  item.text?.split('周')[1]
                }</div></div>`
              }
            }
            // eventMaxStack: 4 // 同一时段最多展示事件数
            // moreLinkClick: 'week',
            // moreLinkClick: this.moreLinkClick,
          }
        }
      }
    }
  },
  mounted() {},
  watch: {
    currentDate: {
      handler(newVal) {
        if (newVal) {
          const fullCalendar = this.$refs.fullCalendar.calendar
          fullCalendar.gotoDate(newVal) // 日历视图跟随日期跳转
          this.getScheduleInfo()
        }
      },
      deep: true
      // immediate: true
    },
    dateType: {
      handler(newVal) {
        if (newVal) {
          this.getScheduleInfo()
        }
      }
    }
  },
  methods: {
    // 查询值班信息
    async getScheduleInfo() {
      const url = `/api/xxx`
      const params = {
        dateType: this.dateType,
        scheduleDate: this.currentDate
      }
      const res = await this.$http.get(url, { params })
      if (res.successful) {
        this.calendarOptions.events = res?.data.map((item) => ({
          ...item,
          title: item.userName,
          start: item.scheduleStartTime,
          end: item.scheduleEndTime,
          color: item.postColor || '#999999',
          textColor: item.postColor || '#999999',
          visible: false,
          overlap: false,
          editable: false
        }))
      } else {
        this.$message.error(res.msg, 5)
      }
    },
    // 删除单个排班成功回调
    handleSuccess() {
      this.$emit('success')
    },
    handleEventClick(e) {
      const result = this.calendarOptions.events.map((item) => {
        return {
          ...item,
          visible: item.id === e.event.id
        }
      })
      this.calendarOptions.events = result
      console.log(e, 'handleEventClick=====')
    },
    handleDateClick(e) {
      this.handleEventVisible()
      if (e.view.type === 'dayGridMonth') {
        const dateList = this.calendarOptions.events.filter(
          (item) => item.scheduleDate === e?.dateStr
        )
        if (dateList && dateList?.length > 0) {
          this.$refs.eventModalRef.visible = true
          this.$refs.eventModalRef.scheduleDate = e?.dateStr
          this.$refs.eventModalRef.getScheduleList()
        }
      }
    },
    // 关闭排班气泡弹框
    handleEventVisible() {
      const result = this.calendarOptions.events.map((item) => {
        return {
          ...item,
          visible: false
        }
      })
      this.calendarOptions.events = result
    }
  }
}
</script>

<style lang="less" scoped>
.full-calendar {
  padding: 20px 10px 10px 10px;
  height: 100%;
  .event-content {
    width: 100%;
    height: 100%;
    text-align: center;
    padding-top: 3px;
  }
  ::v-deep .fc-header-toolbar {
    margin-bottom: 12px;
    .fc-toolbar-title {
      font-size: 16px;
      font-weight: 700;
      line-height: 28px;
      color: #333;
      margin-left: 10px;
    }
  }
  ::v-deep .fc-theme-standard {
    height: 100% !important;
    overflow-y: scroll;
    th,
    td {
      border-right: none;
      border-bottom: none;
    }
    .fc-button {
      height: 28px;
      font-size: 14px;
      display: flex;
      align-items: center;
    }
    .fc-scrollgrid {
      border: none;
      .fc-col-header {
        tr th {
          height: 48px;
          border: none;
        }
      }
      // 周视图
      .day-header {
        display: flex;
        align-items: center;
        color: #333;
        .day {
          color: rgb(51, 51, 51);
          font-size: 28px;
        }
        .week {
          color: rgb(153, 153, 153);
          line-height: 21px;
          letter-spacing: 1px;
          margin-left: 8px;
          font-size: 14px;
        }
        .today {
          display: flex;
          justify-content: center;
          align-items: center;
          width: 32px;
          height: 32px;
          border-radius: 50%;
          background-color: #2d8cf0;
          color: #fff;
          font-size: 18px;
        }
      }
      .fc-timegrid-body {
        height: 100%;
        border: 1px solid #f0f3fd;
        .fc-timegrid-slots {
          height: 100%;
          table {
            height: 100%;
          }
        }
        tr td {
          min-height: 26px;
          font-size: 14px;
          border-color: #f0f3fd !important;
        }
        .fc-timegrid-slot-label {
          width: 50px !important;
          border: none !important;
          display: flex;
          justify-content: center;
          align-items: start;
          color: #999;
        }
        .fc-event {
          background-color: transparent !important;
          border: none;
        }
        .fc-day-today {
          background-color: transparent;
        }
        .fc-timegrid-cols .fc-day {
          border-bottom: 1px solid #f0f3fd;
        }
      }
      // 月视图
      .fc-col-header-cell-cushion {
        color: #333333 !important;
      }
      .fc-daygrid-day-number {
        color: #333333 !important;
      }
      .fc-daygrid-body {
        border: 1px solid #f0f3fd;
        tr td {
          font-size: 14px;
          border-color: #f0f3fd !important;
        }
        .fc-day-other {
          background: rgb(248, 248, 248);
        }
        .fc-event {
          background-color: transparent !important;
          border: none;
        }
        .fc-day-today {
          background-color: #fff;
          .cDay {
            width: 20px;
            height: 20px;
            text-align: center;
            line-height: 20px;
            border-radius: 50%;
            color: #fff;
            background: #2d8cf0;
          }
          .lunar {
            color: #2d8cf0 !important;
          }
        }
        .fc-daygrid-day-events {
          height: 100px;
          overflow-y: scroll;
          &::-webkit-scrollbar {
            width: 3px !important;
            height: 3px !important;
          }
        }
        .fc-daygrid-day-number {
          width: 100%;
          .month-date {
            width: 100%;
            display: flex;
            justify-content: space-between;
            .lunar {
              color: #999999;
            }
          }
        }
        .event-content {
          width: 100%;
          height: 30px;
          line-height: 30px;
          text-align: left !important;
          text-align: center;
          padding: 0 0 0 5px;
        }
      }
    }
  }
  ::v-deep .fc-button {
    border: 1px solid rgb(45, 140, 240);
    border-radius: 2px;
    background: rgba(45, 140, 240, 0.1);
    color: #2d8cf0;
    box-shadow: none !important;
    &:hover {
      border: 1px solid rgb(45, 140, 240) !important;
      border-radius: 2px;
      background: rgb(45, 140, 240) !important;
      color: #fff;
    }
  }
  ::v-deep .fc-button-active {
    border: 1px solid rgb(45, 140, 240) !important;
    border-radius: 2px;
    background: rgb(45, 140, 240) !important;
  }
}
</style>
相关推荐
学不会•2 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
活宝小娜4 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点4 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript
coldriversnow4 小时前
在Vue中,vue document.onkeydown 无效
前端·javascript·vue.js
我开心就好o4 小时前
uniapp点左上角返回键, 重复来回跳转的问题 解决方案
前端·javascript·uni-app
开心工作室_kaic5 小时前
ssm161基于web的资源共享平台的共享与开发+jsp(论文+源码)_kaic
java·开发语言·前端
刚刚好ā5 小时前
js作用域超全介绍--全局作用域、局部作用、块级作用域
前端·javascript·vue.js·vue
沉默璇年6 小时前
react中useMemo的使用场景
前端·react.js·前端框架
yqcoder6 小时前
reactflow 中 useNodesState 模块作用
开发语言·前端·javascript
2401_882727577 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架