最近项目需要实现日历功能需要显示节日,放假时间安排和点击加载更多日程,然后使用fullcalendar插件,需要对该插件源码修改实现。 先展示效果图
npm install --save @fullcalendar/core @fullcalendar/daygrid //fullcalendar插件
npm install --save moment //时间格式插件
显示节日,修改fullcalendar源码
"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
}
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的