实现效果:
- 左右拖拽实现切换月份,PC端自行改为左右点击实现切换
v-touch:swipe.left 左右切换,用的插件:vue2-touch-events
transition-group 切换动画效果 官网查看使用方法
逻辑
获取当前时间new Date(),然后据此获取当前年月日,日历需要两个关键值
- 根据年份判断某月有多少天
- 获取当月第一天是周几
<script>
const month_leap = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];//闰年每个月份的天数
const month_normal = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];//非闰年每个月份的天数
const month_name = ['January', 'Febrary', 'March', 'April', 'May', 'June', 'July', 'Auguest',
'September', 'October', 'November', 'December'];
// const month_name_cn = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月',
'9月', '10月', '11月', '12月'];
//获取当天的年月日
const my_date = new Date();
export default {
name: 'test',
data() {
return {
yearTitle:'',
monthTitle:'',
isShowMonth: true,
my_year: my_date.getFullYear(),//获取年份
my_month: my_date.getMonth(),//获取月份,一月份的下标为0
my_day: my_date.getDate(),//获取当前日期
my_hours: my_date.getHours(),
my_minutes: my_date.getMinutes(),
chooseDay: '',
calendarList: [],//显示的本月数据列表,包括上月尾部,本月,下月头部
nowTime: my_date.getHours(),
startNumShow: '', //week的最小日期
endNumShow: '', //week的最大日期
direction: 'left',
clickNum: '',//默认点击的数字=当前天加上上个月余数
};
},
watch:{
yearTitle(_newValue){
this.$emit('update:year',_newValue);
},
monthTitle(_newValue){
this.$emit('update:month',_newValue);
},
},
created() {
const _todayTime = this.my_year + `-${this.my_month + 1 > 9 ? this.my_month + 1 : '0' +
this.my_month + 1}-` + `${this.my_day > 9 ? this.my_day : '0' + this.my_day}`;
this.chooseDay = new Date(_todayTime).getTime();
this.startTime = new Date(_todayTime).getTime();
this.endTime = new Date(_todayTime).setDate(new Date(_todayTime).getDate() + 1);
this.refreshDate();
this.showWeekList(this.clickNum);
},
methods: {
showWeekList(index) {
let _index = Number(index) + 1;
if (_index <= 1) {
// 上一个月;
this.swipeHandlerMonthRight();
this.clickNum = _index = 31;
}
if (_index > this.total) {
//下一个月
this.clickNum = _index = 7;
this.swipeHandlerMonthLeft();
}
const _numCol = parseInt(Math.ceil(_index / 7)); //第几行
this.startNumShow = (_numCol - 1) * 7;
this.endNumShow = _numCol * 7;
},
//根据年月获取当月第一天是周几
dayStart(_month, _year) {
const tmpDate = new Date(_year, _month, 1);
return (tmpDate.getDay());
},
//根据年份判断某月有多少天(11,2019),表示2019年12月
daysMonth(_month, _year) {
const _tmp1 = _year % 4;
const _tmp2 = _year % 100;
const _tmp3 = _year % 400;
if ((_tmp1 == 0 && _tmp2 != 0) || (_tmp3 == 0)) {
return (month_leap[_month]);//闰年
} else {
return (month_normal[_month]);//非闰年
}
},
refreshDate() {
let _list = [];
const _my_year = this.my_year;
const _my_month = this.my_month;
const _my_day = this.my_day;
//计算当月的天数和每月第一天都是周几,day_month和day_year都从上面获得
const _totalDay = this.daysMonth(_my_month, _my_year);
const _dayStart = this.dayStart(_my_month, _my_year);
this.clickNum = _my_day + _dayStart;
//获取当前页面上月总共多少天
let _lastMoth = _my_month - 1;
let _lastMonthYear = this.my_year;
if (_lastMoth < 0) {
_lastMonthYear = this.my_year - 1;
_lastMoth = 11; //即12月份
}
//本月1号,对应星期三_dayStart,上月总共31天,则数据列表开头数字为31-3+1 = 29号,
//日历第一行分别显示"29,30,31,1,2,3,4"
let _last_start = this.daysMonth(_lastMoth, _lastMonthYear) - _dayStart + 1;
//获取当前页面下月总共显示多少天,从1号开始显示,若最后一行为31号,则剩余6天,
//显示结果为:"31,1,2,3,4,5,6"
let _left_days = 7 - (_dayStart + _totalDay) % 7;
_left_days = _left_days === 7 ? 0 : _left_days;
//本页总共显示的数据量(包括上月尾部,本月,下月头部)
this.total = (Number(_dayStart) + _totalDay + _left_days);
//1.添加上个月的信息,置灰显示lightgrey
for (let i = 0; i < _dayStart; i++) {
_list.push({
number: _last_start,
className: 'lightgrey',
isChoose: false,
actualTime: new Date(`${_lastMonthYear}-${_lastMoth}-${_last_start}`),
isCanClick: false
});
_last_start++;
}
const _month = `${_my_month + 1}` > 9 ? `${_my_month + 1}` : 0 + `${_my_month + 1}`;
//2.添加本月信息darkgrey
for (let i = 1; i <= _totalDay; i++) {
const i_day = i > 9 ? i : '0' + i;
const _fullDay = my_date.getFullYear() + '-' + _month + '-' + i_day;
let className = 'text-box';
if (_my_year == my_date.getFullYear() &&
_my_month == my_date.getMonth() && i == _my_day) {
className += ' todaybox';
} else {
className += ' darkgrey';
}
let isChoose = false;
if (new Date(_fullDay).getTime() === this.chooseDay) {
isChoose = true;
}
_list.push({
number: i,
className: className,
isChoose: isChoose,
actualTime: new Date(_fullDay).getTime(),
isCanClick: true
});
}
//3.添加下个月的信息,置灰显示lightgrey
for (let i = 1; i <= _left_days; i++) {
const _fullDay = my_date.getFullYear() + '-' + _month + '-' + _totalDay;
const today = new Date(new Date(_fullDay).setDate(new Date(_fullDay).getDate() + i));
_list.push({
number: i,
className: 'lightgrey',
isChoose: false,
actualTime: today.getTime(),
isCanClick: false //非本月时间不能点击
});
}
this.calendarList = _list;
this.monthTitle = month_name[_my_month];
this.yearTitle = _my_year;
},
swipeHandlerMonthLeft(direction) {
this.direction = direction;
this.my_month++;
if (this.my_month > 11) {
this.my_month = 0;
this.my_year++;
}
this.refreshDate();
},
swipeHandlerMonthRight(direction) {
this.direction = direction;
this.my_month--;
if (this.my_month < 0) {
this.my_year--;
this.my_month = 11; //即12月份
}
this.refreshDate();
},
swipeHandlerWeekLeft(direction) {
this.direction = direction;
this.clickNum = this.clickNum + 7;
this.showWeekList(`${this.clickNum}`);
},
swipeHandlerWeekRight(direction) {
this.direction = direction;
this.clickNum = this.clickNum - 7;
this.showWeekList(`${this.clickNum}`);
},
clikThisDay(_event) {
const _index = _event.target.getAttribute('data-index');
const _obj = this.calendarList[_index] || {};
if (!_obj.isCanClick) {
return;
}
const _chooseTime = _obj.actualTime;
this.chooseDay = _chooseTime;
this.calendarList.map((item, _index) => {
item.isChoose = false;
});
_obj.isChoose = true;
this.startTime = _chooseTime;
this.endTime = new Date(_chooseTime).setDate(new Date(_chooseTime).getDate() + 1);
this.showWeekList(_index);
this.clickNum = Number(_index);
this.isShowMonth = false;
this.$emit('clickDay',this.startTime,this.endTime)
},
toShowMonth() {
this.isShowMonth = !this.isShowMonth;
},
}
};
</script>
html
<!-- v-touch:swipe.left 左右切换,用的插件vue2-touch-events -->
<div class="calendar">
<div class="m-t-md">
<div class="week-title-box">
<ul class="flexbox-row">
<li>SUN</li>
<li>MON</li>
<li>TUE</li>
<li>WED</li>
<li>THU</li>
<li>FRI</li>
<li>SAT</li>
</ul>
</div>
<!-- month show-->
<div class="container-box" v-show="isShowMonth" v-touch:swipe.left="swipeHandlerMonthLeft"
v-touch:swipe.right="swipeHandlerMonthRight">
<transition-group :name="'list-complete-'+direction" tag="div" class="ul-box ">
<div v-for="(item,index) in calendarList" v-bind:key="item"
@click.prevent="clikThisDay($event)"
:data-index="index" class="li-box list-complete-item">
<span :class="[item.className,item.isChoose?'chooseday':'']"
:data-index="index">{{item.number}}</span>
</div>
</transition-group>
</div>
<!-- week show-->
<div class="container-box" v-show="!isShowMonth"
v-touch:swipe.left="swipeHandlerWeekLeft"
v-touch:swipe.right="swipeHandlerWeekRight">
<transition-group :name="'list-complete-'+direction" tag="div" class="ul-box ">
<div v-for="(item,index) in calendarList"
v-bind:key="item" @click.prevent="clikThisDay($event)"
:data-index="index" class="li-box list-complete-item"
v-if="(index+1)>startNumShow && (index+1)<=endNumShow">
<span :class="[item.className,item.isChoose?'chooseday':'']"
:data-index="index">{{item.number}}</span>
</div>
</transition-group>
</div>
</div>
</div>
<style lang="scss" scoped>
.list-complete-item {
display: inline-block;
transition: all .5s;
}
.list-complete-left-enter, .list-complete-left-leave-to {
opacity: 0;
transform: translateX(5vw);
}
.list-complete-right-enter, .list-complete-right-leave-to {
opacity: 0;
transform: translateX(-5vw);
}
.list-complete-left-leave-active, .list-complete-right-leave-active {
/*移除元素动画结束之后并没有立即被销毁,这时被移除元素位置还处于占有状态,
直到transition-group标签的动画流程彻底结束,这时元素直接重新渲染归位,所
以不再执行动画,而把position设置为absolute就不占位置啦,后边元素动画触发
并执行!欢迎各位说出其他看法,相互学习!*/
position: absolute;
transition: 0s;
}
.list-complete-left-item-leave-active, .list-complete-right-item-leave-active {
position: absolute;
transition: 0s;
}
.calendar {
position: relative;
margin: 0px;
background: white;
.calendar-title {
text-align: center;
font-size: 16px;
padding: 14px 0 0 0;
}
.lightgrey { /*浅灰色显示过去的日期*/
color: #CCCCD4;
}
.darkgrey { /*深灰色显示将来的日期*/
color: #120F32;
}
.container-box.ul-box {
position: relative;
width: 100%;
height: 100%;
box-sizing: border-box;
display: flex;
flex-wrap: wrap;
transition: width 2s;
-webkit-transition: width 2s;
}
.container-box .ul-box .li-box {
width: 14.28%;
height: 14.28vw;
text-align: center;
cursor: pointer;
font-size: 14px;
display: inline-flex;
justify-content: center;
align-items: center;
.text-box {
display: block;
width: 50%;
height: 50%;
line-height: 7.14vw;
}
.chooseday {
color: #FFFFFF;
background: #120F32;
border-radius: 50%;
}
/*日期当天用红色背景绿色文字加以显示*/
.todaybox {
color: #F90435;
background: #FBEEF0;
border-radius: 50%;
}
}
.week-title-box ul {
font-size: 14px;
width: 100%;
box-sizing: border-box;
}
.week-title-box ul li {
color: #CCCCD4;
height: 30px;
line-height: 30px;
flex: 1;
list-style: none;
text-align: center;
border-top: 1px solid #F9F9F9;
border-bottom: 1px solid #F9F9F9;
}
}
.header-box {
.search-box {
width: calc(100% - 24px);
display: inline-block;
border-radius: 12px;
.el-input__inner {
background-color: #E8EBF3;
color: #1A1739;
border-radius: 12px;
border-color: #E8EBF3;
padding-left: 8px;
padding-right: 18px;
}
.el-input__suffix {
right: 0px;
}
}
.header-right {
justify-content: flex-end;
align-items: center
}
}
</style>
其他
- 选中某一天后显示上图,这个左右切换的动画交互还有一点点问题,代码没放上去