最近项目需求,需要做一个移动端的日历,类似于安卓原生日历。网上找了很多成熟的插件都不是想要。偶然的机会发现某篇博客上有人写的有类似的,于是拿过来稍加改造,终于可以用了。在这里非常感谢这位博主,省去我很多的开发时间。附上此博客地址:

        废话不多说,直接附上代码供大家参考:

<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>

   效果图:

element 日历视图 移动端日历插件vue_element 日历视图

标注日程:

//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);