FullCalendar 日历插件排班表排课表保姆级详解(可拖动排班排课)

芒果63 2024-06-11 11:03:03 阅读 78

(基于vue)实现效果

文章目录

前言

一、FullCalendar是什么?

二、使用步骤

1.引入库

2.html部分代码

3.css样式代码(样式我单独写个scss文件引入的)

4.逻辑代码部分 

总结


前言

闲来无事搞个排班排课表,之前貌似遇到过这类的需求,只不过没做上。主要用的就是FullCalendar插件。基于vue框架写法。

基本的功能可以新建个日程看看样式,可以拖拽日程进去。


提示:以下是本篇文章正文内容,下面案例可供参考

一、FullCalendar是什么?

Fullcalendar是一个非常受欢迎的日历日程处理的js组件,它功能强大,文档齐全,可定制化高,可与你的项目无缝对接。

官网链接:https://fullcalendar.io/demos

主要是针对于jq为基础写的插件,官网文档是全英文的,所以还要靠万能的度娘。

这里附个链接,在Vue框架下使用Fullcalendar_Helloweba

里面是详细讲解用vue写法的插件用法,只不过我原封照搬的时候遇到很多问题,后面我会总结一下。

二、使用步骤

1.引入库

代码如下(示例):

npm install @fullcalendar/core

npm install @fullcalendar/vue

npm install @fullcalendar/daygrid

npm install @fullcalendar/interaction

npm install @fullcalendar/timegrid

npm install @fullcalendar/list

(这个也可以一次npm install 由于我一次性安装出问题了我就一个一个安装的)

import '@fullcalendar/core'; // solves problem with Viteimport FullCalendar from '@fullcalendar/vue';import dayGridPlugin from '@fullcalendar/daygrid';import interactionPlugin,{ Draggable } from '@fullcalendar/interaction';import timeGridPlugin from '@fullcalendar/timegrid';import listPlugin from '@fullcalendar/list';import moment from "moment";


2.html部分代码

这部分没啥说的,做的页面布局,我搞的都是纯静态的,有需要的直接复制粘贴就行,css样式我是用scss文件引入的,代码放在下面。

<template> <div class="calendar"> <div class="calendar_header"> <el-input v-model="searchVal" placeholder="请输入内容"></el-input> <el-button type="primary" @click="dialogAdd">+ 新建日程</el-button> </div> <div class="calendar_body"> <div class="calendar_body_left"> <div class="calendar_body_left_top"> <div class="calendar_body_left_top_title">日程状态图例</div> <div> <el-button class="unstart_btn">未开始</el-button> <el-button class="doing_btn">进行中</el-button> <el-button class="success_btn">已完成</el-button> <el-button class="delay_btn">已延时</el-button> </div> </div> <div class="calendar_body_left_bottom"> <div class="calendar_body_left_bottom_title">可拖动列表</div> <div class="date-box" id="list-group-item"> <div class="flex-b box list-group-item" v-for="item in list" :key="item.name"> <div>{ { item.name }}</div> <div class="circle" :class="item.status">{ { item.value }}</div> </div> </div> </div> </div> <div class="calendar_body_right"> <template> <FullCalendar ref="fullCalendar" :options="calendarOptions" /> </template> </div> </div> <!-- 新建日程弹窗 --> <el-dialog title="新建日程" :visible.sync="dialogFormVisible"> <el-form :model="form" :rules="rules" ref="form"> <el-form-item label="日程名称" :label-width="formLabelWidth" prop="name"> <el-input v-model="form.name" autocomplete="off"></el-input> </el-form-item> <el-form-item label="日程状态" :label-width="formLabelWidth" prop="status"> <el-select v-model="form.status" placeholder="请选择日程状态"> <el-option label="未开始" value="unstart"></el-option> <el-option label="进行中" value="doing"></el-option> <el-option label="已完成" value="success"></el-option> <el-option label="已延时" value="delay"></el-option> </el-select> </el-form-item> <el-form-item label="日程时间" :label-width="formLabelWidth" prop="date"> <template> <el-date-picker v-model="form.date" type="datetimerange" value-format="yyyy-MM-dd HH:mm:ss" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"> </el-date-picker> </template> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="submitForm('form')">确 定</el-button> </div> </el-dialog> </div></template>


3.css样式代码(样式我单独写个scss文件引入的)

.calendar{ padding: 0 20px; .el-button{ width: 100px; } .calendar_header{ display: flex; margin: 30px 0; .el-input{ width: 200px; margin-right: 16px; } } .calendar_body{ display: flex; .calendar_body_left{ display: flex; flex-direction: column; width: 10%; .el-button{ margin-left: 0 !important; color: #fff; margin-bottom: 4px; } .unstart_btn{ background-color: #ffcc99; } .doing_btn{ background-color: #5580ee; } .success_btn{ background-color: #87d068; } .delay_btn{ background-color: #FF0033; } } .calendar_body_right{ width: 85%; } } .el-dialog{width: 30%;} .el-date-editor.el-range-editor.el-input__inner.el-date-editor--datetimerange{ width: 100%; } .el-select{ width: 100%; } .calendar_body_left_top{ .calendar_body_left_top_title{ margin-bottom: 15px; font-size: 18px; font-weight: bolder; } } .calendar_body_left_bottom{ padding: 0 25px; margin-top: 20px; .calendar_body_left_bottom_title{ font-size: 18px; font-weight: bolder; } .circle { background-color: #3788d8; border-radius: 10px; color: #fff; display: inline-block; font-size: 12px; height: 18px; line-height: 18px; padding: 0 6px; text-align: center; white-space: nowrap; border: 1px solid #fff; } .holiday { background-color: #FF6600; } .work{ background-color: #66CCCC; } .date-box { //border: 1px solid #ccc; border-radius: 5px; } .box { margin-top:15px; border: 1px solid #FFFFCC; padding: 10px 20px; border-radius: 5px; display: flex; justify-content: space-between; cursor: pointer; background-color: #FFFFCC; } }}


4.逻辑代码部分 

先上代码

<script>// @ is an alias to /srcimport "@/assets/css/calendar.scss"import '@fullcalendar/core'; // solves problem with Viteimport FullCalendar from '@fullcalendar/vue';import dayGridPlugin from '@fullcalendar/daygrid';import interactionPlugin,{ Draggable } from '@fullcalendar/interaction';import timeGridPlugin from '@fullcalendar/timegrid';import listPlugin from '@fullcalendar/list';import moment from "moment";export default { name: 'Home', components: { FullCalendar }, data() { return { n:4, searchVal:'', dialogFormVisible:false, formLabelWidth: '120px', form:{ name: '', date: '', status:'', }, rules: { name: [ { required: true, message: '请输入日程名称', trigger: 'blur' }, // { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' } ], date: [ { required: true, message: '请选择日期', trigger: 'change' } ], status: [ { required: true, message: '请选择日程状态', trigger: 'change' } ] }, testData:[{ id: 1, title: '任务1未开始', start: '2023-04-06 10:30:00', end: '2023-04-07 10:30:00', // color: '#ffcc99', status:'unstart', editable: true, //允许拖动缩放,不写默认就是false overlap: true, //允许时间重叠,即可以与其他事件并存,不写默认就是false },{ id: 2, title: '任务2进行中', start: '2023-04-06 10:30:00', end: '2023-04-08 10:30:00', // color: '#5580ee', status:'doing', editable: true, //允许拖动缩放 overlap: true, //允许时间重叠,即可以与其他事件并存,不写默认就是false },{ id: 3, title: '任务3已完成', start: '2023-04-09 10:30:00', end: '2023-04-09 18:30:00', // color: '#87d068', status:'success', editable: true, //允许拖动缩放 overlap: true, //允许时间重叠,即可以与其他事件并存,不写默认就是false },{ id: 4, title: '任务4已延时', start: '2023-04-18 10:30:00', end: '2023-04-18 10:30:00', // color: '#ff99b3', status:'delay', editable: true, //允许拖动缩放 overlap: true, //允许时间重叠,即可以与其他事件并存,不写默认就是false }], // 可拖动列表数据 list: [ // { name: '删除假日', value: '0', color: 'blue' } { name: '工作日1', value: '1', status: 'work' }, { name: '工作日2', value: '5', status: 'work' }, { name: '春节放假', value: '7', status: 'holiday' }, { name: '中秋节放假', value: '3', status: 'holiday' }, { name: '国庆节放假', value: '7', status: 'holiday' }, ], calendarOptions: { plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin, listPlugin], initialView: 'dayGridMonth', locale: 'zh-cn', //? 配置中文 firstDay: 1,// 把每周设置为从周一开始 initialDate: moment().format("YYYY-MM-DD HH:mm:ss"), // 自定义设置背景颜色时一定要初始化日期时间 aspectRatio: 2.6, // 设置日历单元格宽度与高度的比例。 buttonText: {/* 设置按钮文字 */ today: '今天', month: '月', week: '周', day: '日', list: '周列表', }, headerToolbar: {//日历头部 left: 'prev,next today', center: 'title', right: 'dayGridMonth,timeGridWeek,timeGridDay listWeek', }, selectable: true,//可编辑 // dayMaxEvents: true, // slotMinutes: 15, editable: false, // 日历上是否可拖拽 droppable: true, dropAccept: '.list-group-item', drop: this.drop, height: 650, validRange: this.validRange, //设置可显示的总日期范围 events: [], //背景色 (添加相同时间的背景色时颜色会重叠) datesSet: this.datesSet, //日期渲染;修改日期范围后触发 eventClick: this.handleEventClick, //点击日程触发 dateClick: this.handleDateClick, //点击日期触发 eventDrop: this.calendarEventDropOrResize, //拖动事件触发 eventResize: this.calendarEventDropOrResize, //缩放事件触发 displayEventTime: false, //不显示具体时间 }, validRange: { start: '2023-01-01 ', end: moment().add(6, 'months').format('YYYY-MM-DD HH:mm:ss'), }, new_startDate:'', new_endDate:'', }},mounted() { // 初始化日历 调用获取视图活动数据方法 this.datesSet(); // 拖拽 var containerEl = document.getElementById('list-group-item'); // 初始化外部事件 new Draggable(containerEl, { itemSelector: '.list-group-item', } )},methods: { datesSet(info) { //注意:该方法在页面初始化时就会触发一次 // console.log(info) // this.search() //请求本页数据 //虚拟数据 this.testData.forEach((item,index) => { // console.log('item',item) if(item.status == 'unstart'){ this.$set(item,"color", "#ffcc99") }else if(item.status == 'doing'){ this.$set(item,"color", "#5580ee") }else if(item.status == 'success'){ this.$set(item,"color", "#87d068") }else if(item.status == 'delay'){ this.$set(item,"color", "#FF0033") }else if(item.status == 'work'){ this.$set(item,"color", "#66CCCC") }else{ this.$set(item,"color", "#FF6600") } }); this.calendarOptions.events = this.testData this.list.forEach((item,index) => { if(item.status == 'work'){ this.$set(item,"color","#66CCCC") }else{ this.$set(item,"color","#FF6600") } }) }, dialogAdd(){ this.dialogFormVisible = true }, submitForm(formName) { this.$refs[formName].validate((valid) => { if (valid) { this.dialogFormVisible = false; this.n ++ // console.log('date',this.form.date) let obj = { id: Number(this.n), title: String(this.form.name), start: String(this.form.date[0]), end: String(this.form.date[1]), // color: '#ff99b3', status:String(this.form.status), editable: true, //允许拖动缩放 overlap: true, //允许时间重叠,即可以与其他事件并存,不写默认就是false } this.testData.push(obj) this.datesSet(); // console.log('this.calendarOptions.events',this.calendarOptions.events) } else { console.log('error submit!!'); return false; } }); }, // 转换时间格式 parseTime(date) { const yy = date.getFullYear()const MM = (date.getMonth() + 1) < 10 ? '0' + ( date.getMonth() + 1) : (date.getMonth() + 1)const dd = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()const HH = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()const mm = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()const ss = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()constnewDate = yy + '-' + MM + '-' + dd + ' ' + HH + ':' + mm + ':' + ss; return newDate; }, handleEventClick(info) {}, handleDateClick(info){}, // 拖拽事件 calendarEventDropOrResize(info){ // console.log(info) //获取拖拽目标信息 // 获取拖拽后的时间 this.new_startDate = this.parseTime(info.event._instance.range.start) this.new_endDate = this.parseTime(info.event._instance.range.end) }, drop(date, allDay) { // let typeNumber = null // const firstChildName = null const isWork = date.draggedEl.lastChild.className.indexOf('work') > 0 console.log('date',date) date.draggedEl.remove() this.n ++ const obj = { // dayNum: date.draggedEl.lastChild.innerHTML, id: Number(this.n), title: String(date.draggedEl.firstChild.innerHTML), start: Date.parse(moment(date.dateStr).format()), // 开始时间 end: Date.parse(moment(date.dateStr).add(date.draggedEl.lastChild.innerHTML, 'days').format()), // 结束时间 // color: '#ff99b3', status:isWork ? 'work' : 'holiday', editable: true, //允许拖动缩放 overlap: true, //允许时间重叠,即可以与其他事件并存,不写默认就是false } this.testData.push(obj) this.datesSet(); // console.log(this.calendarOptions.events) // console.log('date.draggedEl.lastChild.innerHTML',date.draggedEl.lastChild.className.indexOf('holiday')) }}};</script>


 5.踩坑部分

以上代码直接cv应该就可以实现一个静态的日历排班表了。下面来说说我实现的过程遇到那些问题:

(1)这一块看别人写的都是 import '@fullcalendar/core/vdom' // solves problem with Vite

但是咱是vue2写的不适用。但是nodemodules包中有core文件我就把后面去掉了

(2)在安装的时候总是会出现以下报错

viewType "" is not available. Please make sure you've loaded all neccessary plugins

不要指定安装版本直接install就行。报错不行就全部uninstall,重新下载。package.json文件里面没了才是卸载完成。我遇到的问题就是一次性下载后报错,我没卸载,看别人的版本可以,我直接又下载了别人的版本,结果还是不行,一定卸载干净再重新下载。我觉得安装我上面的代码应该不会报错,前提是你的node版本什么的和我差不多哈哈

(3)这块这些Fullcalendar日历插件的事件的Info可以打印出当前拖拽的信息,比如拖拽前后的时间,还有样式什么的都可以在这里修改。打印出来附在下面啦        event就是拖拽后的信息,oldevent就是拖拽前的。我主要拿时间所以截图下面了。

 

(4)drop这块的拖拽主要是实现将左侧可拖拽列表中的日程拖放到Fullcalendar日历中。拖拽左侧日程块后打印date也可以获得好多有用的信息啦

 


总结

以上就是实现简易排班排课表的雏形啦,复杂的功能还是要基于数据啦,静态只是看看大概。先说这么多,后面有坑我再写;以及后面看看能不能再加上些好玩的功能。



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。