vue手写一个简单日历demo

实现效果:

在这里插入图片描述
在这里插入图片描述

  • 左右拖拽实现切换月份,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>

其他

在这里插入图片描述

  • 选中某一天后显示上图,这个左右切换的动画交互还有一点点问题,代码没放上去
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值