vue-fullcalendar日程控件增加节日显示和放假时间安排

3,889 阅读6分钟

最近项目需要实现日历功能需要显示节日,放假时间安排和点击加载更多日程,然后使用fullcalendar插件,需要对该插件源码修改实现。 先展示效果图

先在vue项目上安装依赖包:

npm install --save @fullcalendar/core @fullcalendar/daygrid  //fullcalendar插件
npm install --save moment //时间格式插件

显示节日,修改fullcalendar源码

在node_modules下找到@fullcalendar/daygrid文件夹 打开package.json

 "license": "MIT",
  "main": "main.js",
  "module": "main.esm.js",
  "name": "@fullcalendar/daygrid",
  "peerDependencies": {
    "@fullcalendar/core": "~4.4.0"
  },

找到入口文件main.esm.js,在代码开头把lunar.js代码复制上去,lunar实现功能是传进一个日期参数(YYYY-MM-DD),返回匹配到的节日和假期 由于lunar代码过长就请到git上查看 然后在main.esm.js下找到DayGrid.prototype.renderNumberCellHtml 在这方法后面添加代码

date是renderNumberCellHtml方法传进来的参数 通过lunar方法获取fes和hol。 fes是显示节日,hol是显示上班日和放假日

 if (isDayNumberVisible) {
      var fes = lunar(date).festival();
      if (fes && fes.length > 0) {
        html += '<span class="fc-day-cnTerm-list">';
        for (let key in fes) {
          html +=
            "<span class='fc-day-cnTerm'>" + fes[key].desc.trim() + "</span>";
        }
        html += "</span>";
      }
      // var Lunar = lunar(date)
      var hol = lunar(date).holiday();
      if (hol) {
        if (hol == "班") {
          html += "<span class='fc-day-busy'>" + hol + "</span>";
        } else {
          html += "<span class='fc-day-holiday'>" + hol + "</span>";
        }
      }

      html += buildGotoAnchorHtml(
        options,
        dateEnv,
        date,
        { class: "fc-day-number" },
        dateEnv.format(date, DAY_NUM_FORMAT) // inner HTML
      );
    }
    html += "</td>";
    return html;

holiday是显示放假日期和补班日期,由于节假日时间是不固定,只能通过后端获取最新的放假时间安排去显示, 固定节日的日期可以在lunar里面写死,所以还需要拿到holiday数据传递到lunar里面,才能正确显示 这里还要引入getHoliaday方法,在import{getHoliaday} form "@fullcalendar/core";

然后在在node_modules下找到@fullcalendar/daygrid文件夹下main.esm.js

添加全局代码

var holiaday={}
function getHoliaday(){
    return holiaday
}

在tryRerender下添加

holiaday = this.optionsManager.overrides.holiaday

拿到holiday变量并赋值给全局变量holiday并暴露getHoliaday()方法

节日显示和放假时间安排的源码修改完毕。 然后再在page下创建calendar.vue 引入fullcalendar

<template>
  <div class="calendar">
    <FullCalendar
      ref="calendar"
      defaultView="dayGridMonth"
      locale="zh-cn"
      :slot-event-overlap="false"
      :holiaday="holiday"
      :eventTimeFormat="eventTime"
      :header="header"
      :customButtons="customButtons"
      @dateClick="handleDateClick"
      :plugins="calendarPlugins"
      :events="calendarEvents"
      @eventClick="handleEventClick"
      :button-text="{
        today: '今天'
      }"
    >
    </FullCalendar>
    <calendarDialog
      ref="dialog"
      :visible.sync="confirm.visible"
      @close="close"
      @confirm="checkConfirm(arguments)"
      @del="delEvent(arguments)"
    />
  </div>
</template>

<script>
import FullCalendar from "@fullcalendar/vue";
import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
import calendarDialog from "./calendar-dialog";

export default {
  name: "calendar",
  components: {
    FullCalendar,
    calendarDialog
  },
  data() {
    return {
      holiday: { //放假时间安排
        "2020-07-01": "假",
        "2020-07-05": "班",
      },
      confirm: {
        visible: false
      },
      customButtons: {
        myNext: {
          text: "下个月",
          click: () => {
            this.myButton("next");
          }
        },
        myPrev: {
          text: "上个月",
          click: () => {
            this.myButton("prev");
          }
        },
        myNextYear: {
          text: "下一年",
          click: () => {
            this.myButton("nextYear");
          }
        },
        myPrevYear: {
          text: "上一年",
          click: () => {
            this.myButton("prevYear");
          }
        },
        myToday: {
          text: "今天",
          click: () => {
            this.myToday();
          }
        }
      },
      calendarPlugins: [dayGridPlugin, interactionPlugin],
      header: {
        left: "myPrevYear ,myPrev ,title , today , myNext, myNextYear",
        center: "none",
        right: "none"
      },
      eventTime: {
        hour: "numeric",
        minute: "2-digit",
        hour12: false
      },
      calendarEvents: [
        {
          scheduleId: "1",
          title: "部门会议1",
          start: "2020-07-01 00:00:00",
          end: "2020-07-03 00:00:00",
          startDate: "2020-07-01 00:00:00",
          endDate: "2020-07-03 00:00:00",
          ownerDate: "2020-06-01",
          showMore: false
        },
        {
          scheduleId: "2",
          title: "部门会议2",
          start: "2020-07-01 00:00:00",
          end: "2020-07-03 00:00:00",
          startDate: "2020-07-01 00:00:00",
          endDate: "2020-07-03 00:00:00",
          ownerDate: "2020-06-01",
          showMore: false
        },
       
        {
          scheduleId: "3",
          title: "部门会议3",
          start: "2020-07-01 00:00:00",
          end: "2020-07-03 00:00:00",
          startDate: "2020-07-01 00:00:00",
          endDate: "2020-07-03 00:00:00",
          ownerDate: "2020-06-01",
          showMore: false
        },

      ]
    };
  },
  methods: {
    myToday() {
      this.$refs.calendar.$options.calendar.today();
    },
    myButton(type) {
      if (type === "next") {
        this.$refs.calendar.$options.calendar.next();
      } else if (type === "prev") {
        this.$refs.calendar.$options.calendar.prev();
      } else if (type === "nextYear") {
        this.$refs.calendar.$options.calendar.nextYear();
      } else if (type === "prevYear") {
        this.$refs.calendar.$options.calendar.prevYear();
      }
    },
    checkConfirm(arg) {
      this.confirm.visible = false;
      console.log(arg);
      let data = arg[0];
      if (!data.startDate) {
        return;
      }
      let params = {
       end:data.endDate,
       start:data.startDate
      };
      params = Object.assign(params, data);
      delete params.radio;
      // params.start = moment(params.startDate).format("YYYY-MM-DD HH:mm:ss");
      // params.end = moment(params.endDate).format("YYYY-MM-DD HH:mm:ss");
      let eventIndex = this.calendarEvents.findIndex(
          item => item.scheduleId == params.scheduleId
          );
          console.log(eventIndex)
      if (eventIndex != -1) {  
          this.calendarEvents[eventIndex] = params
          this.calendarEvents = [...this.calendarEvents]
          console.log(this.calendarEvents)
      } else {
        console.log(params)
        this.calendarEvents.push(params);
        console.log(this.calendarEvents)
      }
    },
    close() {
      this.confirm.visible = false;
    },
    delEvent(arg) {
      console.log(arg);
      if (!arg[0].scheduleId) {
        this.close();
        return;
      }
      let eventIndex = this.calendarEvents.findIndex(
         item => item.scheduleId == arg[0].scheduleId
       );
     if (eventIndex > -1) {
         this.calendarEvents.splice(eventIndex, 1);
       }
    
      this.close();
    },
    handleDateClick(arg) {
      console.log(arg)
      console.log(this.calendarEvents)
      if (window.moredafult) {
        window.moredafult = false;
        return;
      }
      let classList = arg.dayEl.classList;
      if (classList.contains("fc-last-month")) {
        let dom = document.getElementsByClassName("fc-myPrev-button");
        dom[0].click();
        return;
      } else if (classList.contains("fc-next-month")) {
        let dom = document.getElementsByClassName("fc-myNext-button");
        dom[0].click();
        return;
      }
      let data = {
        scheduleId: arg.view.uid,
        title: "",
        type:'add',
        dateRange: []
      };
      this.$refs.dialog.setForm(data);
      this.confirm.visible = true;
    },
    handleEventClick(info) {
      console.log(info)
      let data = Object.assign({}, info.event.extendedProps);
      data.title = info.event.title;
      data.type="edit",
      data.dateRange = [info.event.extendedProps.startDate, info.event.extendedProps.startDate];
      console.log(data)
      this.$refs.dialog.setForm(data);
      this.confirm.visible = true;
    },
  
  },
  mounted() {
    var that = this;
    window.addEventListener("clickMore", function(event) {
      console.log(event.detail.eventRange);
      that.calendarEvents.map(item => {
        if (
          // moment(item.viewStartDate).format("YYYY-MM-DD") ==
          // moment(
          //   event.detail.eventRange.def.extendedProps.viewStartDate
          // ).format("YYYY-MM-DD")
          item.viewStartDate === event.detail.eventRange.def.extendedProps.viewStartDate
        ) {
          item.showMore = true;
        }
      });
      console.log(that.calendarEvents);
      that.calendarEvents = [...that.calendarEvents];
    });
  }
};
</script>

<style scoped lang="scss">
@import "~@fullcalendar/core/main.css";
@import "~@fullcalendar/daygrid/main.css";

.calendar {
  height: 100vh;
  padding: 80px 200px;
  // padding-bottom: 30px;
  // width: 40vw;
  // height: 500px;
}
.fullCalendar {
  height: 600px;
  // box-sizing: border-box;
}
</style>

其中holiday对象键名为对应日期格式(YYYY-MM-DD)

calendar-dialog.vue代码如下

/* eslint-disable vue/return-in-computed-property */
<template>
  <el-dialog
    :visible="visible"
    width="800px"
    :title="title"
    :show-close="true"
    :before-close="handleDialogClose"
  >
    <el-form :model="form" ref="form">
      <div class="flex">
        <span class="form-input-title">
          标题:
        </span>
        <el-input
          size="small"
          v-model="form.title"
          autocomplete="off"
          prop="title"
        ></el-input>
      </div>
      <div class="flex">
        <span class="form-input-title">
          生效时间:
        </span>
        <el-date-picker
          size="small"
          v-model="form.dateRange"
          type="datetimerange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
        >
        </el-date-picker>
      </div>
      <div class="flex">
        <span class="form-input-title">
          地点:
        </span>
        <el-input
          type="textarea"
          v-model="form.address"
          placeholder="请输入地点"
        />
      </div>
      <div class="flex">
        <span class="form-input-title">
          备注:
        </span>
        <el-input
          type="textarea"
          v-model="form.comment"
          placeholder="请输入备注"
        />
      </div>
    </el-form>
    <div slot="footer" class="dialog-footer">
      <el-button size="small" type="primary" @click="confirm">确 定</el-button>
      <el-button size="small" @click="close">取 消</el-button>
      <el-button v-show="delStatus" size="small" type="danger" @click="del()"
        >删除</el-button
      >
    </div>
  </el-dialog>
</template>

<script>
import moment from "moment";
export default {
  name: "confirm",
  props: {
    visible: {
      default: true
    },
    delStatus: {
      default: true
    }
  },
  data() {
    return {
      title: "日程事件",
      constForm: {}, //对比form是否改变
      form: {
        scheduleId: "",
        title: "",
        dateRange: [],
        startDate: "",
        endDate: "",
        address: "",
        comment: ""
      }
    };
  },
  computed: {
  },
  methods: {
    deepCheck(obj1, obj2) {
      // 深度遍历,对比值是否相等
      for (let key in obj1) {
        if (obj1[key] instanceof Object && !(obj1[key] instanceof Date)) {
          if (!this.deepCheck(obj1[key], obj2[key])) {
            return false;
          }
        } else {
          if (obj1[key] instanceof Date && obj2[key] instanceof Date) {
            return obj1[key].getTime() == obj1[key].getTime();
          } else if (obj1[key] != obj2[key]) {
            console.log(obj1[key] != obj2[key]);
            return false;
          }
        }
      }
      return true;
    },
    confirm() {
      console.log(this.form);
      let form = this.form;
      if (form.title == "") {
        this.$message({
          showClose: true,
          message: "标题不能为空",
          type: "warning"
        });
        return;
      } else if (!form.dateRange || form.dateRange.length < 1) {
        this.$message({
          showClose: true,
          message: "请选择时间",
          type: "warning"
        });
        return;
      }
      this.form.startDate = this.form.dateRange[0];
      this.form.endDate = this.form.dateRange[1];
      this.form.ownerDate = moment(form.dateRange[0]).format("YYYY-MM-DD");
      this.$emit("confirm", this.form);
    },
    close() {
      this.$emit("close");
    },
    handleDialogClose() {
      this.close();
    },
    getForm() {
      return this.form;
    },
    setForm(data) {
      this.form = {
        scheduleId: undefined,
        title: "",
        dateRange: [],
        type:'',
        startDate: "",
        endDate: "",
        address: "",
        comment: ""
      };
      for (let key in this.form) {
        if (data[key] && data[key] != "") {
          this.form[key] = data[key];
        }
      }
      console.log(data)
      if(data.type == "add"){
        this.title = "添加日程事件"
      }
      else{
         this.title = "修改日程事件"
      }
      this.constForm = Object.assign({}, this.form);
    },
    del() {
      this.$emit("del", this.form);
    }
  }
};
</script>

<style scoped lang="scss">
.radio-group {
  margin-left: 20px;
}
.radio-group ::v-deep .el-radio__inner {
  box-shadow: initial !important;
}

.flex {
  display: flex;
  flex-direction: row;
  padding: 10px 20px 10px 0px;
  line-height: 30px;
}
.form-input-title {
  display: inline-block;
  width: 100px;
}
.el-input {
  flex: 1;
}
.el-textarea {
  flex: 1;
}
.el-radio-group {
  float: left;
  display: flex !important;
  height: 32px;
  align-items: center;
}
.dialog-footer {
  text-align: left;
  padding-left: 120px;
}
</style>

附上git地址github.com/heweisheng-…

注意事项

  • fullcalendar next ,prev等切换月份的按钮是没有回调函数,要想由回调函数必须用customButtons(自定义按钮),它能提供回调函数,然后再回调函数里通过调用this.$refs.calendar.$options.calendar.next();去切换月份。
  • fullcalendar events日程数据源的start和end 分别对应开始日期和结束日期,如果开始日期和结束日期是同一天的那么在@eventClick回调参数中end是默认为null的