(基于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 Vite
import 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 /src
import "@/assets/css/calendar.scss"
import '@fullcalendar/core'; // solves problem with Vite
import 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()
const newDate = 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也可以获得好多有用的信息啦
总结
以上就是实现简易排班排课表的雏形啦,复杂的功能还是要基于数据啦,静态只是看看大概。先说这么多,后面有坑我再写;以及后面看看能不能再加上些好玩的功能。