功能:
1.日历可以周视图、月视图切换;
2.点击月视图中日期可以切换到对应周视图;
3.点击周视图查看当日对应数据;
4.周、月视图状态下,点击前后按钮,分别切换对应上下的周、月;
5.点击回到今天,立即切回到周、月视图下对应的当日;
引用dayjs处理日期,结合el-calendar完美实现。
要注意的是,日历显示周的话,传的日期范围要按照计算所在星期,比如我们的需求是周日为每周起始日,那么就要给周日的日期和周六日期为起始日,月视图我不想再去计算日期范围了,就直接用了:value,注意用的不是v-model而是value,因为value是单向的,v-model是双向数据绑定了。
<template>
<div class="childContainer">
<CompBar name="XX日历" iconName="rili.png" titleName="回到今天" @handleBarClick="nowCalendar">
<div class="kalendar">
<div class="kalendar-header">
<span class="current-monuth">
<i class="el-icon-caret-left" @click="showPrev"></i>
<i class="el-icon-caret-right" @click="showNext"></i>
</span>
<el-radio-group v-model="monthYear" size="mini">
<el-radio-button label="周"></el-radio-button>
<el-radio-button label="月"></el-radio-button>
</el-radio-group>
</div>
<CalendarMonth v-if="monthYear==='月'" :calendarValue="monthDate" :selectDay="dayDate" :dateList="dateList" @getPlanList="getplanList"></CalendarMonth>
<CalendarWeek v-else :rangeArr="dateRange" :selectDay="dayDate" :dateList="dateList"
@getPlanList="getplanList"></CalendarWeek>
</div>
<tabs :class="monthYear==='月'?'monthTabs':'weekTabs'" v-model="activePlan" v-loading="isLoading">
<el-tab-pane name="tabApplyEndPlan">
//此处是个列表
</el-tab-pane>
<el-tab-pane name="tabEndPlan"></el-tab-pane>
</tabs>
</template>
// 此处省略组件、接口引入
import dayjs from 'dayjs';
var weekplugin = require('dayjs/plugin/weekday');
dayjs.extend(weekplugin)
// 此处省略,直接放核心代码
data(){
return{
activePlan:'tabApplyEndPlan',
monthYear:'周', // 周、月视图切换,默认显示周
monthDate:'', // 传后端参数 YYYY-MM,查视图上需要显示点的日期
dayDate:'', // 传后端参数 YYYY-MM-DD,查视图下面对应的当日数据列表
dateRange:[], // 周日历,传入周日历视图日期范围
dateList:[], // 存放月数据,视图中需要显示点的日期
}
},
watch:{
// 比较简单,直接省略代码了,记录下逻辑
// 监听 monthDate、dayDate 值的改变,调用对应接口
},
methods:{
showPrev() {
// 上个月
if (this.monthYear === '月') {
this.monthDate = dayjs(this.monthDate).add(-1, 'month').format('YYYY-MM');
// 需要判断当前选中日期是否属于当前月份
let _dayDate = dayjs(this.dayDate).format('YYYY-MM');
if (_dayDate === this.monthDate) {
// 计算本周第一天
let day1 = dayjs(this.dayDate).startOf('week').format('YYYY-MM-DD');
// 计算本周最后一天
let day2 = dayjs(this.dayDate).endOf('week').format('YYYY-MM-DD');
this.dateRange = [day1, day2];
} else {
let day1 = dayjs(this.monthDate).startOf('month').startOf('week').format('YYYY-MM-DD');
let day2 = dayjs(this.monthDate).startOf('month').endOf('week').format('YYYY-MM-DD');
this.dateRange = [day1, day2]
}
}
// 上星期
if (this.monthYear === '周') {
// 获取当前周视图
let day1 = dayjs(this.dateRange[0]).add(-1, 'week').startOf('week').format('YYYY-MM-DD');
let day2 = dayjs(this.dateRange[1]).add(-1, 'week').endOf('week').format('YYYY-MM-DD');
this.monthDate = dayjs(this.dateRange[0]).add(-1, 'week').startOf('week').format('YYYY-MM');
this.dateRange = [day1, day2]
}
},
showNext() {
// 下个月
if (this.monthYear === '月') {
this.monthDate = dayjs(this.monthDate).add(1, 'month').format('YYYY-MM');
// 需要判断当前选中日期是否属于当前月份
let _dayDate = dayjs(this.dayDate).format('YYYY-MM');
if (_dayDate === this.monthDate) {
// 计算本周第一天
let day1 = dayjs(this.dayDate).startOf('week').format('YYYY-MM-DD');
// 计算本周最后一天
let day2 = dayjs(this.dayDate).endOf('week').format('YYYY-MM-DD');
this.dateRange = [day1, day2];
} else {
let day1 = dayjs(this.monthDate).endOf('month').startOf('week').format('YYYY-MM-DD');
let day2 = dayjs(this.monthDate).endOf('month').endOf('week').format('YYYY-MM-DD');
this.dateRange = [day1, day2]
}
}
// 下星期
if (this.monthYear === '周') {
// 获取当前周视图
let day1 = dayjs(this.dateRange[0]).add(1, 'week').startOf('week').format('YYYY-MM-DD');
let day2 = dayjs(this.dateRange[1]).add(1, 'week').endOf('week').format('YYYY-MM-DD');
this.monthDate = dayjs(this.dateRange[0]).add(1, 'week').startOf('week').format('YYYY-MM');
this.dateRange = [day1, day2]
}
},
// 返回今日
nowCalendar() {
this.monthDate = dayjs(new Date()).format('YYYY-MM');
this.dayDate = dayjs(new Date()).format('YYYY-MM-DD');
let day1 = dayjs(new Date()).startOf('week').format('YYYY-MM-DD');
let day2 = dayjs(new Date()).endOf('week').format('YYYY-MM-DD');
this.dateRange = [day1, day2];
this.activePlan = 'tabApplyEndPlan'
},
// 周、月视图日期被点击处理方法
getPlanList(date) {
// console.log(this.monthYear)
// console.log(date)
this.dayDate = date.day;
// 点击上、下月/周日期,不涉及视图的切换
if (this.monthYear === '月') {
if (date.type === 'next-month') {
this.showNext()
}
if (date.type === 'prev-month') {
this.showPrev()
}
}
if (this.monthYear === '周') {
let _month = dayjs(date.day).format('YYYY-MM');
if (date.type === 'next-month') {
if (_month !== this.monthDate) {
this.monthDate = dayjs(date.day).format('YYYY-MM');
}
}
if (date.type === 'prev-month') {
if (_month !== this.monthDate) {
this.monthDate = dayjs(date.day).format('YYYY-MM');
}
}
}
if (date.type === 'current-month') {
this.monthYear = '周';
// 计算本周第一天
let day1 = dayjs(this.dayDate).startOf('week').format('YYYY-MM-DD');
// 计算本周最后一天
let day2 = dayjs(this.dayDate).endOf('week').format('YYYY-MM-DD');
// 计算点击日期所在周第一天所属月
this.monthDate = dayjs(day1).startOf('week').format('YYYY-MM');
this.dateRange = [day1, day2];
}
},
}
自定义日历样式(没有用日历原先的头,全部自己重写的,还不错):
::v-deep .kalendar {
&-header {
text-align: center;
margin: 10px 16px 0 16px;
.current-monuth {
font-size: 16px;
letter-spacing: 0;
font-weight: 500;
margin-left: 15%;
color: #262626;
font-family: PingFangSC-Medium;
i {
cursor: pointer;
}
}
.el-radio-group {
float: right;
}
.el-radio-button__orig-radio:checked + .el-radio-button__inner {
background: #ffffff;
box-shadow: -1px 0 0 0 transparent;
border: 1px solid rgba(199, 0, 11, 1);
font-family: PingFangSC-Medium;
font-size: 12px;
color: #c7000b;
letter-spacing: -0.04px;
font-weight: 500;
}
.el-radio-button__inner:hover {
color: #c7000b;
}
}
.calender-dot-box {
width: 100%;
bottom: -8px;
position: absolute;
span {
width: 6px;
height: 6px;
margin-right: 3px;
border-radius: 50%;
display: inline-block;
&:last-of-type {
margin-right: 0;
}
}
.endPlan {
background-color: #d61212;
}
.applyEndPlan {
background-color: #ffd100;
}
}
.el-calendar {
&__body {
padding: 10px 16px;
}
.is-today {
.el-calendar-day {
.calender-date {
width: 34px;
height: 34px;
margin: 0 auto;
color: #ff534f;
border-radius: 10px;
background: #fff;
box-shadow: none;
}
}
}
&__header {
display: none;
}
.current {
.el-calendar-day {
color: #262626;
}
}
.prev,
.next {
color: #bfbfbf;
}
&-day {
padding: 0;
font-weight: 700;
font-size: 16px;
letter-spacing: 0;
text-align: center;
position: relative;
transition: color 0.3s;
font-family: DINAlternate-Bold;
}
&-table {
th {
font-family: PingFangSC-Regular;
font-size: 16px;
color: #262626;
letter-spacing: 0;
text-align: center;
line-height: 34px;
font-weight: 400;
padding: 0;
&:last-of-type,
&:first-of-type {
color: #ff564e;
}
}
td {
border: none;
&.is-selected {
background-color: transparent;
}
}
.el-calendar-day {
height: 34px;
line-height: 34px;
&:hover {
background-color: transparent;
}
.calendar-isSelected {
width: 34px;
height: 34px;
margin: 0 auto;
color: #fff;
border-radius: 10px;
background: #ff534f;
box-shadow: 0px 0px 2px 0px rgba(238, 88, 64, 1);
}
}
}
}
再看看子组件里面,先看月的:
<template>
<!-- 月日历 -->
<el-calendar :value="calendarValue" :first-day-of-week="7" value-format="YYY-MM">
<template slot="dateCell" slot-scope="{ date, data }">
<div v-if="selectedDay === data.day" class="calendar-isSelected" @click="handleDate($event, date, data)">
{{ date.getDate() }}
</div>
<div v-else class="calender-date" @click="handleDate($event, date, data)">
{{ date.getDate() }}
</div>
<div class="calender-dot-box" @click="handleDate($event, date, data, 'dot')">
<template v-for="(item) in dateLists">
<span class="applyEndPlan" v-if="item.date === data.day && item.applyEndPlanNum > 0"></span>
<span class="endPlan" v-if="item.date === data.day && item.endPlanNum > 0"></span>
</template>
</div>
</template>
</el-calendar>
</template>
<script>
export default {
components: {},
name: "CalendarMonth",
props: {
selectedDay: {
type: String,
default: "",
},
calendarValue: {
type: String,
default: new Date(),
},
dateList: {
type: Array,
default: () => {
return [];
},
},
},
watch: {
dateList: {
handler(list) {
this.dateLists = list;
},
immediate: true,
},
},
data() {
return { monthDate: this.calendarValue, dateLists: [] };
},
created() { },
methods: {
handleDate(e, date, data) {
this.$emit("getPlanList", data);
},
},
};
</script>
<style lang="scss" scoped></style>
周日历组件:
<template>
<!-- 周日历 -->
<el-calendar :range="rangeArr" :first-day-of-week="7" value-format="YYY-MM">
<template slot="dateCell" slot-scope="{date,data}">
<div v-if="selectedDay === data.day" class="calendar-isSelected" @click="handleDate($event, date, data)">
{{ date.getDate() }}</div>
<div v-else class="calender-date" @click="handleDate($event, date, data)">{{ date.getDate() }}</div>
<div class="calender-dot-box" @click="handleDate($event, date, data)">
<template v-for="(item) in dateList">
<span class="applyEndPlan" v-if="item.date === data.day && item.applyEndPlanNum > 0"></span>
<span class="endPlan" v-if="item.date === data.day && item.endPlanNum > 0"></span>
</template>
</div>
</template>
</el-calendar>
</template>
<script>
export default {
components: {}, name: 'CalendarWeek', props: {
selectedDay: {
type: String, default: '',
}, rangeArr: {
type: Array,
default: () => {
return [];
}
}, dateList: {
type: Array, default: () => {
return [];
}
}
}, data() {
return {
}
}, created() {
}, methods: {
handleDate(e, date, data) {
// console.log(e,date,data)
this.$emit('getPlanList', data)
},
}
}</script>
<style lang="scss" scoped></style>
其实应该把日历组件二次封装一下,就不用单独再去写周、月日历子组件了,有空了可以试试。
不过不得不再吐槽一句,elementui的日历组件,给提供的API真心的少,功能很简单。。。
最终效果图:
月视图效果图:
月视图下,有时候会出现整整一行的灰色日期,看起来不是很美观,那么就需要操作dom,通过js判断,操作dom来处理。大概思路就是,先通过 document.querySelectorAll('.el-calendar-table__row') 获取到所有.el-calendar-table__row的元素节点lists,然后循环遍历这些节点,若其子元素class中含有.current,那么就说明是带有当月的日期,则不改变样式,若不含,则说明这整行都是前\后月的日期,那么就可以把该.el-calendar-table__row的css里面加上属性display:none。
周视图效果图: