学习新技术是不可能的,这辈子都不可能,要不是需求有这个,我肯定不学,好了吐槽完毕,开始进行事件日历fullendar v5版本的分享
效果图
首先看一哈效果图,这是总览图
这个是添加事件预览图
这个是点击单元格快捷添加,最大的区别就是单元格点击事件日期已经确定
另外一个就是点击事件时,出现对事件的再次编辑,相当于预览加编辑二合一
另外针对事件的删除功能,做成了事件长按删除
此外还能拖拽事件到另外一个日期,扩大一个事件的日期范围等功能。
如何使用
企鹅自己使用的vue项目,所以保证你的项目是vue2.x版本。
首先是安装
yarn add @fullcalendar/daygrid @fullcalendar/interaction @fullcalendar/list @fullcalendar/vue
基本上这四个插件足够使用了,这几个插件是视图插件,拖拽插件,日程插件以及针对vue使用的插件。而且这种是属于按需使用,避免项目过大的体积。
这里在推荐一个插件,处理日期的dayjs
,这个日期插件几乎和moment
相同的用法,而且更加轻量,且由于moment
不再维护,所以还是转向dayjs
怀抱吧。
在组件内部,引入以下依赖。
import FullCalendar from '@fullcalendar/vue';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import listPlugin from '@fullcalendar/list';
import interactionPlugin from '@fullcalendar/interaction';
import { addLongPress } from '@/utils/assits';
import dayjs from 'dayjs';
同时,FullCalendar
是暴露出来的组件,所以模版中需要写上
<FullCalendar ref="calendar" :options="options"></FullCalendar>
记得FullCalendar
需要在组件局部注册下,像这样
components: {
FullCalendar
},
然后就是FullCalendar
的options
定义了,我们在data
中这样定义
data() {
options: {
// 自定义渲染视图
views: {
dayGridMonth: {
// https://fullcalendar.io/docs/date-formatting
titleFormat: { year: 'numeric', month: '2-digit', day: '2-digit' }
}
},
plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin],
// 自定义渲染按钮
customButtons: {},
// 自定义渲染头部
headerToolbar: {
start: 'prevYear prev next nextYear today',
center: 'title',
end: 'dayGridMonth timeGridWeek timeGridDay listMonth'
},
// 是否可以拖拽
editable: true,
// 是否可添加
selectable: true,
// 自定义文本
buttonText: {
today: '今天',
month: '月',
week: '周',
day: '天',
list: '日程'
},
// 语言
locale: 'zh-cn',
// 设置周几为第一天
firstDay: 1,
// 初始化视图
initialView: 'dayGridMonth',
// 是否显示周末
weekends: true,
// 当事件过多时是否隐藏
dayMaxEvents: true,
// 事件存储
events: [],
// 事件渲染完加载这个,类似mounted
eventDidMount: info => {
addLongPress(info, this.clickEvent, this.longPressEvent);
},
// 点击事件
// eventClick: info => {
// console.log(info);
// },
// 缩放事件
eventResize: info => {
console.log(info);
},
// 拖拽事件
eventDrop: info => {
console.log(info);
},
// 重新渲染
handleDatesRender: () => {},
// 日期选中
select: () => {
this.addModal = true;
this.isRapid = true;
}
}
}
基本上用到的api都写上了注释,然后就是自己一些🌰,比如自定义增加事件,显示事件信息,删除事件等等。
不对针对事件删除设想的是和编辑事件放一起,但是最后还是拿出去,做成了长按删除。主要逻辑在这,鼠标长按事件也好做,大致思想是同时监听click
事件和mousedown
事件,mousedown
事件时开启一个定时器,进行长按计时,如果满足则向外回调一个长按callBack
,如果不满足,则定义一个开关阻止掉click
事件,同样在鼠标移出时,进行复位。这样一个点击和长按的功能就做好了,而且我们还对外传递了两个参数,点击的回调和长按的回调。
export function addLongPress(info, clickCb, longPressCb) {
let timer = null;
let onOff = false;
const { el } = info;
const start = e => {
// 这里的button的意思是鼠标点击可能不是左键点击可能是右键或者中键点击,所以这里需要判断下
if (e.type === 'click' || e.button !== 0) return;
if (timer === null) {
timer = setTimeout(() => {
onOff = true;
longPressCb && longPressCb(info);
}, 1000);
}
};
const cancel = e => {
if (e.type === 'click' && e.button === 0 && !onOff) {
clickCb && clickCb(info);
}
if (timer !== null) {
clearTimeout(timer);
timer = null;
onOff = false;
}
};
el.addEventListener('mousedown', start);
el.addEventListener('click', cancel);
el.addEventListener('mouseout', cancel);
}
在事件日历初始化完毕时,给每个事件进行监听
eventDidMount: info => {
addLongPress(info, this.clickEvent, this.longPressEvent);
},
methods: {
clickEvent() {},
longPressEvent() {}
}
记得要关闭掉原来事件日历提供的点击事件。
针对添加和删除已经很好做了,每个事件都有对应的id,新增和删除直接找到事件id,进行事件数组的操作就行。
针对拖拽事件,扩大事件日期范围的事件,id固定,只是日期改变,改动以下日期即可,企鹅这里未做更多演示。
下面贴上完整的代码
<template>
<div :class="`${prefixCls}-wrap`">
<Button type="primary" @click="(addModal = true), (isRapid = false)">
添加事件
</Button>
<FullCalendar ref="calendar" :options="options"></FullCalendar>
<a-modal
title="添加事件"
v-model="addModal"
@ok="handleOk"
@cancel="handleCancel"
>
<a-form :label-col="{ span: 5 }" :wrapper-col="{ span: 19 }">
<a-form-item label="事件标题">
<a-input v-model="form.title"></a-input>
</a-form-item>
<a-form-item label="事件描述">
<a-textarea v-model="form.description"></a-textarea>
</a-form-item>
<a-form-item label="事件开始日期" v-if="!isRapid">
<a-date-picker v-model="form.startDay" valueFormat="YYYY-MM-DD" />
<a-time-picker
v-model="form.startTime"
valueFormat="HH:mm:ss"
style="margin-left: 15px"
/>
</a-form-item>
<a-form-item label="事件结束日期" v-if="!isRapid">
<a-date-picker v-model="form.endDay" valueFormat="YYYY-MM-DD" />
<a-time-picker
v-model="form.endTime"
valueFormat="HH:mm:ss"
style="margin-left: 15px"
/>
</a-form-item>
<a-form-item label="事件文本颜色">
<colorPicker v-model="form.textColor" class="color" />
</a-form-item>
<a-form-item label="事件背景颜色">
<colorPicker v-model="form.backgroundColor" class="color" />
</a-form-item>
<a-form-item label="事件边框颜色">
<colorPicker v-model="form.borderColor" class="color" />
</a-form-item>
</a-form>
</a-modal>
<a-modal title="预览事件" v-model="infoModal">
<a-row type="flex">
<a-col>标题:</a-col>
<a-col push="1">{{ info && info.title }}</a-col>
</a-row>
<a-row type="flex">
<a-col>描述:</a-col>
<a-col push="1">
{{ info && info.extendedProps && info.extendedProps.description }}
</a-col>
</a-row>
<a-row type="flex">
<a-col>时间:</a-col>
<a-col push="1">
<span>{{ info && info.startStr | dateFilter }}</span>
<span v-if="info && info.endStr">
~ {{ info && info.endStr | dateFilter }}
</span>
</a-col>
</a-row>
</a-modal>
</div>
</template>
<script>
import FullCalendar from '@fullcalendar/vue';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import listPlugin from '@fullcalendar/list';
import interactionPlugin from '@fullcalendar/interaction';
import { addLongPress } from '@/utils/assits';
import dayjs from 'dayjs';
const prefixCls = 'ezb-calendar';
export default {
name: 'Calendar',
components: {
FullCalendar
},
data() {
return {
prefixCls,
isRapid: false,
addModal: false,
infoModal: false,
info: null,
form: {
id: '',
title: '',
description: '',
startDay: '',
startTime: '',
endDay: '',
endTime: '',
textColor: '#000',
backgroundColor: '#3788d8',
borderColor: '#3788d8'
},
options: {
// 自定义渲染视图
views: {
dayGridMonth: {
// https://fullcalendar.io/docs/date-formatting
titleFormat: { year: 'numeric', month: '2-digit', day: '2-digit' }
}
},
plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin],
// 自定义渲染头部
headerToolbar: {
start: 'prevYear prev next nextYear today',
center: 'title',
end: 'dayGridMonth timeGridWeek timeGridDay listMonth'
},
// 是否可以拖拽
editable: true,
// 是否可添加
selectable: true,
// 自定义文本
buttonText: {
today: '今天',
month: '月',
week: '周',
day: '天',
list: '日程'
},
// 语言
locale: 'zh-cn',
// 设置周几为第一天
firstDay: 1,
// 初始化视图
initialView: 'dayGridMonth',
// 是否显示周末
weekends: true,
// 当事件过多时是否隐藏
dayMaxEvents: true,
events: [],
eventDidMount: info => {
addLongPress(info, this.clickEvent, this.longPressEvent);
},
// 点击事件
// eventClick: info => {
// console.log(info);
// },
// 缩放事件
eventResize: info => {
console.log(info);
},
// 拖拽事件
eventDrop: info => {
console.log(info);
},
// 重新渲染
handleDatesRender: () => {},
// 日期选中
select: () => {
this.addModal = true;
this.isRapid = true;
}
}
};
},
filters: {
dateFilter(date) {
return dayjs(date).format('YYYY-MM-DD HH:mm:ss');
}
},
created() {
// 模拟异步数据
setTimeout(() => {
this.options.events = [
{
id: '1',
title: 'event 1',
extendedProps: {},
description: '这是一个事件',
start: '2021-01-01',
end: '2021-01-07',
textColor: '#000',
backgroundColor: 'red',
borderColor: 'red'
},
{ id: '2', title: 'event 2', start: '2021-01-01' },
{ id: '3', title: 'event 1', start: '2021-01-01' },
{ id: '4', title: 'event 2', start: '2021-01-01' },
{ id: '5', title: 'event 3', start: '2021-01-01' },
{ id: '6', title: 'event 4', start: '2021-01-01' },
{
id: '7',
title: 'event 2',
start: '2021-01-07 07:00:00',
textColor: 'yellow',
backgroundColor: 'red',
className: ['red']
},
{
id: '8',
title: 'event 5',
start: '2021-01-08 12:00:00',
end: '2021-01-08 14:00:00',
color: 'yellow'
// textColor: 'red',
// backgroundColor: 'red',
// borderColor: 'yellow'
},
{
id: '9',
title: 'event 5',
start: '2021-01-09'
// textColor: 'yellow'
// borderColor: 'yellow',
// backgroundColor: 'red'
}
];
}, 2000);
},
methods: {
addEvents() {
const params = {};
params.id = this.form.id ? this.form.id : dayjs().unix();
params.title = this.form.title;
params.description = this.form.description;
params.start = this.filterDate(this.form.startDay, this.form.startTime);
params.end = this.filterDate(this.form.endDay, this.form.endTime);
params.textColor = this.form.textColor;
params.backgroundColor = this.form.backgroundColor;
params.borderColor = this.form.borderColor;
this.addModal = false;
this.options.events.push(params);
},
clickEvent(info) {
this.addModal = true;
const event = info.event;
this.form.id = event.id;
this.form.title = event.title;
this.form.description = event.extendedProps.description;
this.form.startDay = event.startStr;
this.form.endDay = event.endStr;
if (event.startStr.includes(' ')) {
this.form.startDay = event.startStr.split(' ')[0];
this.form.startTime = event.startStr.split(' ')[1];
}
if (event.endStr.includes(' ')) {
this.form.endDay = event.endStr.split(' ')[0];
this.form.endTime = event.endStr.split(' ')[1];
}
this.form.textColor = event.textColor;
this.form.backgroundColor = event.backgroundColor;
this.form.borderColor = event.borderColor;
this.info = info.event;
},
longPressEvent(info) {
this.$confirm({
title: '温馨提示',
content: '是否确认删除',
onOk: () => {
this.options.events = this.options.events.filter(
event => event.id !== info.event.id
);
},
onCancel: () => {}
});
},
filterDate(day, time) {
return day + (time ? ' ' : '') + time;
},
handleOk() {
this.addEvents();
},
handleCancel() {}
}
};
</script>
<style lang="scss" scoped>
/deep/ a {
color: #000;
}
.color {
position: relative;
left: 0px;
top: 2px;
}
</style>
因为下一个项目用到这项技术,因此提前做了项目预研,ui样式以及功能都未涉及,敬请谅解。
如果有其它不对或者其它见解,还请指正,一同进步。