最近项目需求,需要做一个移动端的日历,类似于安卓原生日历。网上找了很多成熟的插件都不是想要。偶然的机会发现某篇博客上有人写的有类似的,于是拿过来稍加改造,终于可以用了。在这里非常感谢这位博主,省去我很多的开发时间。附上此博客地址:
废话不多说,直接附上代码供大家参考:
<template>
<div id="calendar" :class="{'change':isChange}">
<!-- 年份 月份 -->
<div class="year-month">
<div class="year-month_left">
<span class="top-time">{{currentMonth}}月</span>
<ul>
<li>周{{weekdays[newWeek]}}</li>
<li>{{currentYear}}年</li>
</ul>
</div>
<div class="year-month_right"><x-icon @click="addSchedule" type="ios-plus-empty" size="30" class="i-plus-empty"></x-icon></div>
</div>
<!-- 星期 -->
<ul class="weekdays">
<li v-for="(vo,index) in weekdays" v-text="vo" :key="index"></li>
</ul>
<!-- 日期 -->
<ul
class="days"
:class="{'fadeOut':fadeOut,'fadeIn':fadeIn,'fadeOutR':fadeOutR,'fadeInR':fadeInR}"
@touchstart="allTouchStart"
@touchend="allTouchEnd"
@touchstart.stop="touchStart"
@touchend.stop="touchEnd"
>
<!-- 核心 v-for循环 每一次循环用<li>标签创建一天 -->
<li
v-for="(dayobject,index) in days"
:class="{'weekend':(index%7 === 0)||((index+1)%7 === 0)}"
:key="index"
>
<!--本月-->
<!--如果不是本月 改变类名加灰色-->
<div
v-if="dayobject.day.getMonth()+1 !== currentMonth"
@click="otherMonth(dayobject.day.getDate())"
class="other-month"
>{{ dayobject.day.getDate() }}</div>
<!--如果是本月 还需要判断是不是这一天-->
<div v-else class="everyDay">
<!--今天 同年同月同日-->
<div
@click="getDayMessage(currentYear,currentMonth,dayobject.day.getDate())"
v-if="dayobject.day.getFullYear() === new Date().getFullYear() && dayobject.day.getMonth() === new Date().getMonth() && dayobject.day.getDate() === new Date().getDate()"
class="active"
>{{ dayobject.day.getDate() }}</div>
<div
:class="{'otherday':dayobject.day.getDate() === otherDay}"
v-else
@click="getDayMessage(currentYear,currentMonth,dayobject.day.getDate())"
>{{ dayobject.day.getDate() }}</div>
<div :class="{'circle':dayobject.status==='3','o':dayobject.status==='2'}"></div>
</div>
</li>
</ul>
<!--背景色-->
<div class="background" :class="{'change':isChange}">
<div v-for="(value,index) in 5" :class="{'dbg':(index%2===0),'lbg':(index%2!==0)}" :key="index"></div>
</div>
</div>
</template>
<script>
export default {
name: 'Calendar',
data() {
return {
currentDay: 1,
currentMonth: 1,
currentYear: 1970,
currentWeek: 1,
newWeek:1,
days: [],
weekdays:['日', '一', '二', '三', '四', '五', '六'],
// 上下滑动的鼠标位置
positionSX: "",
positionEX: "",
positionSY: "",
positionEY: "",
isChange: false,
// 左右滑动动画的初始状态
show: true,
fadeOut: false,
fadeIn: false,
fadeOutR: false,
fadeInR: false,
monthList: [],
status: "",
otherDay: ""
};
},
created() {
this.initData(null);
},
mounted() {},
methods: {
getDayMessage(y, m, d) {
this.getCurrentWeek(y, m, d);
const str = this.formatDate(y, m, d);
this.$emit("change", str, m, d);
this.otherDay = d;
},
getCurrentWeek(y, m, d){
const w=`${y}-${m}-${d}`
const weekArr=w.split('-');
const weeks=new Date(weekArr[0], parseInt(weekArr[1] - 1), weekArr[2]);
this.newWeek=weeks.getDay();
},
otherMonth(day){
if(day<15){
this.rightSliding()
}else if(day>15){
this.leftSliding()
}
},
addSchedule(){
this.$emit('add')
},
//向下滑监听坐标
allTouchStart(e) {
//开始x轴坐标
this.positionSX = e.changedTouches[0].clientX;
//开始y轴坐标
this.positionSY = e.changedTouches[0].clientY;
},
allTouchEnd(e) {
//结束x轴坐标
this.positionEX = e.changedTouches[0].clientX;
//结束y轴坐标
this.positionEY = e.changedTouches[0].clientY;
const distanceY = this.positionEY - this.positionSY;
const distanceX = this.positionSX - this.positionEX;
//判断滑动的方向
if (distanceY < -30 && Math.abs(distanceY) > Math.abs(distanceX)) {
this.isChange = true;
}
if (distanceY > 30 && Math.abs(distanceY) > Math.abs(distanceX)) {
this.isChange = false;
}
},
//监听左右滑动坐标
touchStart(e) {
//开始x轴坐标
this.positionSX = e.changedTouches[0].clientX;
//开始y轴坐标
this.positionSY = e.changedTouches[0].clientY;
},
touchEnd(e) {
this.show = !this.show;
//结束x轴坐标
this.positionEX = e.changedTouches[0].clientX;
//结束y轴坐标
this.positionEY = e.changedTouches[0].clientY;
const distanceY = this.positionEY - this.positionSY;
const distanceX = this.positionSX - this.positionEX;
//判断滑动 的方向
if (distanceX > 30 && Math.abs(distanceY) < Math.abs(distanceX)) {
this.rightSliding()
}
if (distanceX < -30 && Math.abs(distanceY) < Math.abs(distanceX)) {
this.leftSliding()
}
},
//向右滑动
rightSliding(){
const self = this;
self.pickNext(self.currentYear, self.currentMonth);
self.fadeOut = true;
self.fadeIn = false;
self.fadeInR = false;
self.fadeOutR = false;
setTimeout(function() {
self.fadeIn = true;
self.fadeOut = false;
self.fadeOutR = false;
self.fadeInR = false;
}, 300);
},
//向左滑动
leftSliding(){
const self = this;
self.pickPre(self.currentYear, self.currentMonth);
self.fadeOutR = true;
self.fadeInR = false;
self.fadeOut = false;
self.fadeIn = false;
setTimeout(function() {
self.fadeInR = true;
self.fadeOutR = false;
self.fadeOut = false;
self.fadeIn = false;
}, 300);
},
initData(cur) {
let date;
if (cur) {
date = new Date(cur);
} else {
const now = new Date();
const t = this.formatDate(now.getFullYear(), now.getMonth(), 1);
const d = new Date(t);
d.setDate(35);
date = new Date(this.formatDate(d.getFullYear(), d.getMonth() + 1, 1));
}
this.currentDay = date.getDate();
this.currentYear = date.getFullYear();
this.currentMonth = date.getMonth() + 1;
this.currentWeek = date.getDay();
if(!cur || !this.otherDay){
this.otherDay= new Date().getDate();
}
const str = this.formatDate(
this.currentYear,
this.currentMonth,
this.currentDay
);
this.days.length = 0;
//初始化本周
for (let i = this.currentWeek; i >= 0; i--) {
const d = new Date(str);
d.setDate(d.getDate() - i);
const dayobject = {}; //用一个对象包装Date对象 以便为以后预定功能添加属性
dayobject.day = d;
dayobject.status = "";
this.days.push(dayobject); //将日期放入data 中的days数组 供页面渲染使用
}
//其他周
for (let i = 1; i <= 34 - this.currentWeek; i++) {
const d = new Date(str);
d.setDate(d.getDate() + i);
const dayobject = {};
dayobject.day = d;
dayobject.status = "";
this.days.push(dayobject);
}
//选中日期在其他月份是否超出最大日期判断
const arr=[]
for(let j=0, length=this.days.length; j<length; j++){
if(this.days[j].day.getMonth() + 1 === this.currentMonth){
arr.push(this.days[j].day.getDate())
}
}
const maxDate=Math.max.apply(null, arr);
if(this.otherDay > maxDate){
this.otherDay=maxDate
}
this.getDayMessage( this.currentYear, this.currentMonth, this.otherDay)
},
// 上个月信息
pickPre(year, month) {
const d = new Date(this.formatDate(year, month, 1));
d.setDate(0);
this.initData(this.formatDate(d.getFullYear(), d.getMonth() + 1, 1));
},
// 下个月信息
pickNext(year, month) {
const d = new Date(this.formatDate(year, month, 1));
d.setDate(35);
this.initData(this.formatDate(d.getFullYear(), d.getMonth() + 1, 1));
},
// 补零处理
formatDate(year, month, day) {
let y = year;
let m = month;
if (m < 10) m = "0" + m;
let d = day;
if (d < 10) d = "0" + d;
return y + "-" + m + "-" + d;
}
}
};
</script>
<style lang="less" scoped>
#calendar {
width: 100%;
height:auto;
margin: 0 auto;
transition: all 0.5s;
overflow: hidden;
background: #fafafa;
.change {
height: 250px !important;
}
.year-month {
height:80px;
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
.year-month_left{
height:100%;
width:80%;
padding-left: 25px;
display: flex;
align-items: center;
.top-time {
color:#333;
font-size:26px;
font-weight:500;
margin-right: 15px;
}
ul{
display:flex;
flex-direction:column;
li{
color:#444;
font-size:16px;
}
}
}
.year-month_right{
height:100%;
width:20%;
display: flex;
align-items: center;
justify-content: flex-end;
padding-right: 12px;
.i-plus-empty{
fill: #f18d2f;
}
}
}
.weekdays {
margin: 0;
height: 40px;
font-size: 15px;
display: flex;
flex-wrap: wrap;
color: #666;
justify-content: space-around;
li{
display: inline-block;
width: 13.6%;
text-align: center;
display:flex;
justify-content:center;
align-items:center;
}
}
.days {
height: 210px;
padding: 0;
margin: 0;
display: flex;
flex-wrap: wrap;
justify-content: space-around;
overflow: hidden;
position: relative;
li{
list-style-type: none;
display: inline-block;
height: 42px;
width: 13.4%;
text-align: center;
font-size: 14px;
color: #000;
position: relative;
.active{
border-radius: 50%;
background: #3a8fea;
color: #fff;
width: 40px;
height: 40px;
margin: auto;
display: flex;
justify-content: center;
align-items: center;
}
.other-month{
width: 40px;
height: 40px;
margin: auto;
display: flex;
justify-content: center;
align-items: center;
color: #a2a2a2;
}
.everyDay {
width: 40px;
height: 40px;
margin: auto;
display: flex;
justify-content: center;
align-items: center;
}
.circle {
width: 4px;
height: 4px;
border-radius: 50%;
background-color: #f2553d;
position: absolute;
bottom: 6px;
left: 48%;
}
.o {
width: 4px;
height: 4px;
border-radius: 50%;
border: 1px solid #f2553d;
position: absolute;
bottom: 1px;
left: 49%;
}
.otherday {
width: 38px;
height: 38px;
margin: auto;
display: flex;
justify-content: center;
align-items: center;
border-radius: 50%;
border:1px solid #b2b2b2;
}
}
}
.fadeOut {
animation-name: fadeOut;
animation-duration: 0.5s;
animation-timing-function: ease-in-out;
}
.fadeOutR {
animation-name: fadeOutR;
animation-duration: 0.5s;
animation-timing-function: ease-in-out;
}
.fadeIn {
animation-name: fadeIn;
animation-duration: 0.5s;
animation-timing-function: ease-in-out;
}
.fadeInR {
animation-name: fadeInR;
animation-duration: 0.5s;
animation-timing-function: ease-in-out;
}
.background {
position: absolute;
top: 100px;
height: 211px;
width: 100%;
z-index: -1;
overflow: hidden;
transition: all 0.5s;
.dbg {
background-color: #e1e1e1;
height: 42.2px;
}
.lbg {
background-color: #d5d5d5;
height: 42.2px;
}
}
}
@keyframes fadeOut {
0% {
transform: translateX(0);
opacity: 1;
}
100% {
transform: translateX(-100%);
opacity: 0;
}
}
@keyframes fadeIn {
0% {
transform: translateX(100%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
@keyframes fadeOutR {
0% {
transform: translateX(0);
opacity: 1;
}
100% {
transform: translateX(100%);
opacity: 0;
}
}
@keyframes fadeInR {
0% {
transform: translateX(-100%);
opacity: 0;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
</style>
效果图:
标注日程:
//import scheduleApi from '@/api' 接口
//当前月份所有有日程的天(标记)接口获取
//用法:直接在initData函数中最后位置调用即可
getMonthEventDay(){
const self=this
const url = scheduleApi.getSign
const yearOrMonth=`${this.currentYear}-${this.currentMonth}`
const postData={
month:yearOrMonth
}
self.$post(url, postData).then(res =>{
if (res.result ===0){
const list=res.data || []
self.days.forEach(item =>{
//判断属于当前月份
if(item.day.getMonth()+1 === this.currentMonth){
list.forEach(vo =>{
//判断接口返回有日程的天,有的话状态status置为3
if(item.day.getFullYear() === new Date(vo.start).getFullYear() && item.day.getMonth() === new Date(vo.start).getMonth() && item.day.getDate() === new Date(vo.start).getDate()){
item.status='3'
}
})
}
})
}
})
}
new Date()在Safari上的坑解决办法
我们经常用yyyy-MM-dd HH:mm:ss
格式表示日期,如2018-11-11 00:00:00
,在js开发中也经常会把此格式字符串格式化为javascript Date类型,如new Date('2018-11-11 00:00:00')
,不幸的是此操作在Safari浏览器(不论是Mac还是iPhone)上会报错,返回Invalid Date
/**
* 在Safari和IE8上执行 new Date('2017-12-8 11:36:45'); 会得到Invalid Date
* 本函数重写默认的Date函数,以解决其在Safari,IE8上的bug
*/
Date = function (Date) {
MyDate.prototype = Date.prototype;
return MyDate;
function MyDate() {
// 当只有一个参数并且参数类型是字符串时,把字符串中的-替换为/
if (arguments.length === 1) {
let arg = arguments[0];
if (Object.prototype.toString.call(arg) === '[object String]' && arg.indexOf('T') === -1) {
arguments[0] = arg.replace(/-/g, "/");
}
}
let bind = Function.bind;
let unbind = bind.bind(bind);
return new (unbind(Date, null).apply(null, arguments));
}
}(Date);