静态Html使用fullcalendar实现日历/周历/月历
需求背景:项目上遇到新需求,要求实现工单以日/周/月历形式展示。而且要求不同工单根据状态显示不同颜色,一个工单内部,需要以不同颜色显示三个阶段。之前文章写的是vue使用,非常便捷,但是如果是静态html实现的话,就有些麻烦了。因为不能便捷的使用插槽了。
效果图:
日历
周历
月历
代码
<div id="calendar" ref="fullcalendar"></div>
由于这里不能使用组件的写法,所以这里想要实现插槽嵌入自定义内容就遇到了点麻烦。用了模板字符串拼接,来实现内部的自定义单个event
的需求。见下方doHtml
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<!-- 引入样式 -->
<link rel="stylesheet" href="./utils/element.css">
<link rel="stylesheet" href="./index.css">
<script src='./fullcalendar/dist/index.global.js'></script>
</head>
<body>
<div id="app">
<div class="app-container home">
<!-- 按钮部分-->
<div style="margin-bottom: 10px;">
<el-row>
<!-- 会议申请按钮-->
<!-- <el-button type="primary" round style="float: left;" @click="toMeetingClick()">申请会议</el-button> -->
<!-- 普通查询日期区域-->
<span style="margin-left: 20px;">
<span style="margin-right: 0px;">会议开始日期:</span>
<el-date-picker v-model="queryStartDate" type="date" placeholder="选择日期">
</el-date-picker>
<span style="margin-left: 10px;">
<el-button icon="el-icon-search" @click="quertStart()" circle></el-button>
</span>
</span>
<!-- 高级查询按钮-->
<!-- <el-button type="primary" round style="float: right;" @click="drawer = true">高级查询</el-button> -->
</el-row>
</div>
<!-- 日程部分-->
<div style="box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);padding: 10px;">
<el-button type="primary" round size="mini" style="float: right;" @click="changeCalendarView('timeGridDay')">日</el-button>
<el-button type="primary" round size="mini" style="float: right;" @click="changeCalendarView('timeGridWeek')">周</el-button>
<el-button type="primary" round size="mini" style="float: right;" @click="changeCalendarView('dayGridMonth')">月</el-button>
<div id="calendar" ref="fullcalendar"></div>
<!-- 点击会议弹窗内容 -->
<div v-if="selectedEvent">
<el-dialog :close-on-click-modal="true" :visible="this.showDialog" @close="closeDialog()" title="日程详情">
<h3>{{ this.selectedEvent.title }}</h3>
<p>开始时间:{{ this.selectedEvent.start }}</p>
<p>结束时间:{{ this.selectedEvent.end }}</p>
<p>内容:{{ this.selectedEvent.context }}</p>
<p>职位:{{ this.selectedEvent.zw }}</p>
<p>性别:{{ this.selectedEvent.sex }}</p>
<p>年龄:{{ this.selectedEvent.age }}</p>
</el-dialog>
</div>
</div>
<!-- 抽屉框部分-->
<div>
<el-drawer title="查询条件" :visible.sync="drawer" :with-header="true">
<span>......</span>
</el-drawer>
</div>
<!-- 悬浮信息框 -->
<div id="infoBox" style="display: none;" >
<h3>{{ hoverEvent.title }}</h3>
<h3>{{ hoverEvent.extendedProps.reportId }}</h3>
<p>开始时间:{{ hoverEvent.start }}</p>
<p>结束时间:{{ hoverEvent.end }}</p>
</div>
</div>
</div>
<!-- 引入Vue -->
<script src="./utils/vue.js"></script>
<!-- 引入Vuex -->
<script src="./utils/vuex.js"></script>
<!-- 引入vuex -->
<script src="./utils/store.js"></script>
<!-- 引入组件库 -->
<script src="./utils/element.js"></script>
<script src="./utils/events.js"></script>
<script>
let app = ''
//日历对象
let fullCalendar = ''
//显示信息框
function showInfoBox(event){
app._data.hoverEvent = {title:'',start:'',end:'',extendedProps:{reportId:''}}
const testDiv = event.currentTarget;
const dataId = testDiv.getAttribute('data-id');
let evensArr = fullCalendar.getEvents()||[]
evensArr.forEach((item,index)=>{
if(item.id==dataId){
app._data.hoverEvent = item
}
})
//
setTimeout(()=>{
const infoBox = document.getElementById('infoBox');
if(!app._data.hoverEvent.title)return
infoBox.style.display = 'block';
infoBox.style.top = event.clientY + 'px';
infoBox.style.left = event.clientX + 'px';
},100)
}
//隐藏信息框
function hideInfoBox(){
app._data.hoverEvent = {title:'',start:'',end:'',extendedProps:{reportId:''}}
const infoBox = document.getElementById('infoBox');
infoBox.style.display = 'none';
}
//UTC时间去掉T
function formmatTime(time){
const utcTimestamp = time;
const date = new Date(utcTimestamp);
const year = date.getUTCFullYear();
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
const day = String(date.getUTCDate()).padStart(2, '0');
const hours = String(date.getUTCHours()).padStart(2, '0');
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
const seconds = String(date.getUTCSeconds()).padStart(2, '0');
const formattedDateTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
return formattedDateTime
}
//计算距离起点时间的距离
function getStandLen(obj,type){
let styleObj = {
top:0,
left:0,
height:0,
background:''
}
let startTime = new Date(obj.start).getTime();
let endTime = new Date(obj.end).getTime();
let specificTime = 0
let intervalEndTime = 0
if(type=='top'){
specificTime = new Date(obj.extendedProps.topSt).getTime();
intervalEndTime = new Date(obj.extendedProps.topEt).getTime();
styleObj.background='rgba(204, 204, 204,0.4)'
}else if(type=='mid'){
specificTime = new Date(obj.extendedProps.midSt).getTime();
intervalEndTime = new Date(obj.extendedProps.midEt).getTime();
styleObj.background='rgba(255,255,0,0.4)'
}else if(type=='bottom'){
specificTime = new Date(obj.extendedProps.bottomSt).getTime();
intervalEndTime = new Date(obj.extendedProps.bottomEt).getTime();
styleObj.background='rgba(229, 229, 229,0.4)'
}
let elapsedTimePercentage = ((specificTime - startTime) / (endTime - startTime)) * 100;
let intervalPercentage = ((intervalEndTime - specificTime) / (endTime - startTime)) * 100;
styleObj.top = elapsedTimePercentage?elapsedTimePercentage+'%':0
styleObj.height = intervalPercentage?intervalPercentage+'%':0
// console.log('top:'+elapsedTimePercentage);
// console.log(`Percentage of time interval from 03:00:00 to 04:00:00: ${intervalPercentage.toFixed(2)}%`);
return styleObj
}
app = new Vue({
el: '#app',
store,
data() {
return {
//监听到的当前view模式
viewType:'',
//普通查询时间
queryStartDate: null,
//日程弹窗开关
showDialog: false,
//日程弹窗内容
selectedEvent: null,
hoverEvent: {
title:'',
start:'',
end:'',
extendedProps:{reportId:''},
},
//抽屉窗开关
drawer: false,
//日程内容集合
meetingArr:meetingArr,
calendarOptions: {
allDaySlot:false,//是否在日历上方显示all-day(全天)
axisFormat:'h(:mm)tt',
// 视图类型 初始显示的视图 视图名称规则 比如dayGridPlugin去掉Plugin 加上Month或者Week或者Day
initialView: 'dayGridMonth',//dayGridMonth timeGridWeek timeGridDay
// 语言选项 设置为中文后 部分文本会替换为中文 但是不全面
locale: 'zh-cn',
firstDay: 1,
firstHour:1, //无用
// slotMinutes:10,//在agenda的视图中, 两个时间之间的间隔(分钟) 无用
handleWindowResize:true,//是否随浏览器窗口大小变化而自动
contentHeight:'600px', //设置日历主体内容的高度,不包括header部分
aspectRatio:2,//日历单元格宽高比
dayMaxEvents:true,//时间中网格内容高度超出单元格高度时,是否折叠,如果选择false,则会完全展开网格内容,并自动撑开日历单元格高度
eventLimit:false,// 设置月日程,与all-day slot 的最大显示数量,超过的通过弹窗展示
selectEventOverlap:false,// 相同时间段的多个日程视觉上是否允许重叠,默认为true,允许 无用
slotEventOverlap:false,//设置视图中的事件显示是否可以重叠覆盖 有用
// 配置日历头部
// 按钮: prev为切换(上)下一月(/周/日) next today 跳转到今天
// 文本: title 年月(YYYY-MM)
// 按钮: dayGridMonth月 timeGridWeek周 timeGridDay日
headerToolbar: {
left: 'prev next today',
center: 'title',
// right: 'dayGridMonth,timeGridWeek,timeGridDay',
right: '',
},
eventTimeFormat: {
// like '14:30:00'
hour: "2-digit",
minute: "2-digit",
meridiem: false,
hour12: false, // 设置时间为24小时
},
// 设置各种按钮的文字 默认是英文的
buttonText: {
today: '今天',
month: '月',
week: '周',
day: '日',
list: '表'
},
customButtons: {
prev: {
click: () => {
this.prevWeekClick();
}
},
next: {
click: () => {
this.nextWeekClick();
}
}
},
// 初始就存在的事件【日程内容】
initialEvents: [],
// 是否可拖拽
editable: false,
// 是否可选择添加
selectable: true,
// datesSet:this.datesSet, //日期渲染;修改日期范围后触发
// 选择时触发函数
select: this.handleDateSelect,
// 点击事项触发函数
eventClick: this.handleEventClick,
// 移动事项或者拓展事项时间触发函数
eventsSet: this.handleEvents,
// 全天行 的文本显示内容
// allDayText: '全天',
// 最小时间
slotMinTime: '00:00:00',
// 最大时间
slotMaxTime: '23:59:59',
// 是否可以进行(拖动、缩放)修改
//editable: false,
eventContent: (arg)=> {
return this.doHtml(arg)
},
eventClick: function(info) {
console.log('Event clicked:', info.event.title);
},
},
};
},
computed: {
},
watch:{
},
created(){
//给日程记录初始化
this.calendarOptions.initialEvents = this.meetingArr
},
mounted(){
window.addEventListener('resize', () => {
});
this.changeCalendarView('dayGridMonth')
},
methods: {
//初始化日历 以及手动切换视图
changeCalendarView(type){
let calendarEl = document.getElementById('calendar');
if(fullCalendar){
fullCalendar.destroy()
}
setTimeout(()=>{
this.calendarOptions.initialView = type
fullCalendar = new FullCalendar.Calendar(calendarEl,this.calendarOptions)
fullCalendar.render();
})
},
//插槽内容 有些局限
doHtml(arg){
console.log('当时',this.calendarOptions.initialView)
var tooltipContent = document.createElement('div');
let htmlInnerTopStr = ``
let htmlInnerMidStr = ``
let htmlInnerBottomStr = ``
if(arg.event.id == 1||arg.event.id == 5||arg.event.id == 6){
htmlInnerTopStr = `<div class="mid-block" style="background:${getStandLen(arg.event,'top').background};height:${getStandLen(arg.event,'top').height};top:${getStandLen(arg.event,'top').top};left:0">工作</div>`
htmlInnerMidStr = `<div class="mid-block" style="background:${getStandLen(arg.event,'mid').background};height:${getStandLen(arg.event,'mid').height};top:${getStandLen(arg.event,'mid').top};left:0">工作</div>`
htmlInnerBottomStr = `<div class="mid-block" style="background:${getStandLen(arg.event,'bottom').background};height:${getStandLen(arg.event,'bottom').height};top:${getStandLen(arg.event,'bottom').top};left:0">工作</div>`
}
let htmlStr = ``
if(this.calendarOptions.initialView=='dayGridMonth'){
htmlStr = `
<div class="test-div" data-id="${arg.event.id}" onmouseover="showInfoBox(event)" onmouseout="hideInfoBox()" style="background:${arg.event.backgroundColor};color:#fff">
<div class="info-part">
<div class="info-desc">${arg.event.start}</div>
</div>
</div>`
}else{
htmlStr = `
<div class="test-div" data-id="${arg.event.id}" onmouseover="showInfoBox(event)" onmouseout="hideInfoBox()">
<div class="info-part">
<div class="info-desc">${arg.event.start}</div>
<div class="info-desc">${arg.event.extendedProps.reportId}</div>
<div class="info-desc">${arg.event.title}</div>
</div>
`+
htmlInnerTopStr+
htmlInnerMidStr+
htmlInnerBottomStr+
`</div>`
}
tooltipContent.innerHTML = htmlStr
tooltipContent.style.width = '100%'
tooltipContent.style.height = '100%'
return { domNodes: [tooltipContent] };
},
datesSet(info) { //注意:该方法在页面初始化时就会触发一次
console.log('间接监听view的变化',info.view.type)
this.viewType = info.view.type
},
// 向前点击
prevWeekClick(){
fullCalendar.prev();
const startTime = fullCalendar.view.activeStart;
const endTime = fullCalendar.view.activeEnd;
console.log(startTime,endTime);
},
// 向后点击
nextWeekClick(){
fullCalendar.next();
const startTime = fullCalendar.view.activeStart;
const endTime = fullCalendar.view.activeEnd;
console.log(startTime,endTime);
},
//计算距离起点时间的距离
getStandLen(obj,type){
let styleObj = {
top:0,
left:0,
height:0,
background:''
}
let startTime = new Date(obj.start).getTime();
let endTime = new Date(obj.end).getTime();
let specificTime = 0
let intervalEndTime = 0
if(type=='top'){
specificTime = new Date(obj.extendedProps.topSt).getTime();
intervalEndTime = new Date(obj.extendedProps.topEt).getTime();
styleObj.background='rgba(204, 204, 204,0.4)'
}else if(type=='mid'){
specificTime = new Date(obj.extendedProps.midSt).getTime();
intervalEndTime = new Date(obj.extendedProps.midEt).getTime();
styleObj.background='rgba(255,255,0,0.4)'
}else if(type=='bottom'){
specificTime = new Date(obj.extendedProps.bottomSt).getTime();
intervalEndTime = new Date(obj.extendedProps.bottomEt).getTime();
styleObj.background='rgba(229, 229, 229,0.4)'
}
let elapsedTimePercentage = ((specificTime - startTime) / (endTime - startTime)) * 100;
let intervalPercentage = ((intervalEndTime - specificTime) / (endTime - startTime)) * 100;
styleObj.top = elapsedTimePercentage?elapsedTimePercentage+'%':0
styleObj.height = intervalPercentage?intervalPercentage+'%':0
// console.log('top:'+elapsedTimePercentage);
// console.log(`Percentage of time interval from 03:00:00 to 04:00:00: ${intervalPercentage.toFixed(2)}%`);
return styleObj
},
/* 普通查询方法*/
quertStart() {
//非空
if (this.queryStartDate != null) {
// gotoDate方法是让当前视图跳转到指定日期的位置
fullCalendar.gotoDate(this.queryStartDate)
} else {
//跳转到今日
fullCalendar.today()
}
},
/* 获取指定日期范围的所有日程信息 */
toClick() {
// 获取 FullCalendar 实例
// 定义搜索范围的起始和结束时间
const startDate = moment("2015-06-06");
const endDate = moment("2028-06-08");
// 获取日历中的所有事件
const events = fullCalendar.getEvents();
// 根据范围条件筛选事件
const filteredEvents = events.filter(event => {
// 获取事件的开始时间和结束时间
const eventStart = moment(event.start);
const eventEnd = moment(event.end);
// 判断事件是否在范围内
return eventStart.isBetween(startDate, endDate, null, '[]') || eventEnd.isBetween(startDate,
endDate, null, '[]');
});
// 处理筛选出的事件
console.log(filteredEvents);
},
/* 点击会议弹窗具体内容*/
handleEventClick(info) {
// 获取点击的事件对象
const event = info.event;
// 更新选中的事件
this.selectedEvent = {
title: event.title,
start: moment(event.start).format('YYYY-MM-DD HH:mm'),
end: moment(event.end).format('YYYY-MM-DD HH:mm'),
zw: event.extendedProps.zw,
context: event.extendedProps.context,
age: event.extendedProps.age,
sex: event.extendedProps.sex
};
console.info(info)
console.info(this.selectedEvent)
//开启弹窗
this.showDialog = true
},
//
handleDateSelect(info){
const event = info.event;
console.log('信息',info)
// Message.success('点击日期'+info.startStr+'-'+info.endStr);
},
/* 关闭日程弹窗方法*/
closeDialog() {
this.showDialog = false
},
/* 申请会议按钮 */
toMeetingClick() {
//根据自己的业务进行处理...
}
}
});
</script>
</body>
</html>