为了账号安全,请及时绑定邮箱和手机立即绑定

VUE开发一个组件——日历选择控件

前言

前面文章《如何将你封装的组件使用 npm 发布》,主要时讲如何封装组件,然后通过npm发布,今天就给大家造个轮子。另外一篇文章《小程序日历控件js日历数据组装》,是讲小程序日历控件的封装,但是代码都是用的vue写的,js代码可以复用,除了view部分,需要小小变动外,所以我们之间用来封装就好了。

VUE开发一个组件——日历选择控件

Github地址:vue-c-calendar

快速应用

# 安装
npm install vue-c-calendar -S

引入模块

import cCalendar from 'vue-c-calendar'
Vue.use(cCalendar)

组件参数

labels: {// 头部显示的文字部分(出发/到达)
  type: Array,
  required: true
},
isSame: { // 是否可以选择相同日期(起始日期)
  type: Boolean,
  default: false
},
startDate: String, // 已选择的开始时间
endDate: String, // 已选择的结束时间
showAmount: { // 共显示月份 默认3个月
  type: Number,
  default: 3
},
disableBefore: { // 禁用什么日期之前的日期(默认今天)
  type: String,
  default: formatDate(TODAY)
},
disableAfter: String, // 禁用什么日期之后的日期
start: String, // 什么月份开始(可以为空)
sameEnable: { // 是否需要判断禁用日期(可以为true)
  type: Boolean,
  default: true
}

源码地址:vue-c-calendar

view

<template>
  <div id="calerdar">
    <div>
      <div id="calendar-date" v-bind:class="{single: labels.length === 1}">
        <div class="date fl">
          <span class="year">{{startDateMomentYear}}</span>
          <span class="month-date">{{startDateMomentMonth}}</span>
          <span class="label">{{labels[0]}}</span>
        </div>
        <div class="date fr" v-if="endDateMoment">
          <span class="year">{{endDateMomentYear}}</span>
          <span class="month-date">{{endDateMomentMonth}}</span>
          <span class="label">{{labels[1]}}</span>
        </div>
        <div class="date placeholder fr" v-else=""></div>
        <div class="spliter"></div>
      </div>
      <ul id="week-label">
        <li class="item" v-for="(week, windex) in weeks" :key="windex">{{week}}</li>
      </ul>
    </div>
    <div>
      <div id="month-list" v-bind:state="waiting ? 'waiting' : 'complete' " v-on:scroll="loadRepeat">
        <section class="month-item" v-for="(month, mindex) in monthList" :key="mindex">
          <p class="month-info">{{month.numStr}}月</p>
          <ul class="month-main">
            <li class="item null" v-for="(pmitem, pmindex) in month.prefix" :key="pmindex"></li>
            <li class="item date" @click="checkDate(date)" v-for="(date, dindex) in month.dateList" :key="dindex" :class="{'disable': date.isDisable,'today': date.isToday,'start-date': date.isStartDate, 'end-date': date.isEndDate, 'progress': date.isProgress}">
              <span class="num">{{date.dateData ? date.dateData.date : ''}}</span>
            </li>
            <li class="item null" v-for="(smitem, smindex) in month.surfix" :key="smindex"></li>
          </ul>
        </section>
      </div>
    </div>
  </div>
</template>

js

<script>
import Vue from 'vue'
import moment from 'moment'

var TODAY = moment().startOf('date')
var SCROLL_LIMIT_PERCENT = 0.5
var MONTH_CH = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']

var DateItem
var MonthItem
var makeDateList = function (data) {
  var firstDateInMonth = data.curMonth.clone()
  var lastDateInMonth = data.curMonth.clone().endOf('month')
  var prefixAmount = firstDateInMonth.day()
  var contentAmount = lastDateInMonth.date()
  var surfixAmount = (6 - lastDateInMonth.day()) % 6
  var result = []
  var i, l

  for (i = 0, l = prefixAmount + contentAmount + surfixAmount; i < l; i += 1) {
    if (i < prefixAmount || i >= prefixAmount + contentAmount) {
      result.push(new DateItem())
    } else {
      result.push(new DateItem(firstDateInMonth.clone(), data.disableBeforeMoment, data.disableAfterMoment))
      firstDateInMonth.add(1, 'd')
    }
  }
  return result
}
var formatDate = function (date, format) {
  return moment(date).format(format || 'YYYY-MM-DD')
}

DateItem = function (date, disableBeforeMoment, disableAfterMoment) {
  if (date !== undefined) {
    this.dateData = {
      year: date.year(),
      month: date.month(),
      date: date.date()
    }
    this.timeStamp = date.toDate().getTime()
    this.isToday = date.isSame(TODAY)
    this.isDisable = (disableBeforeMoment && date.isBefore(disableBeforeMoment)) || (disableAfterMoment && date.isAfter(disableAfterMoment))
  } else {
    this.isNull = true
  }
  this.isStartDate = false
  this.isEndDate = false
  this.isProgress = false
}
MonthItem = function (data) {
  var targetMonth = data.curMonth.clone().startOf('month')
  this.num = targetMonth.month()
  this.numStr = MONTH_CH[this.num]
  this.dateList = makeDateList(data)
}

export default {
  name: "CCalendar",
  props: {
    labels: {
      type: Array,
      required: true
    },
    isSame: { // 是否能相同
      type: Boolean,
      default: false
    },
    startDate: String,
    endDate: String,
    showAmount: {
      type: Number,
      default: 3
    },
    disableBefore: {
      type: String,
      default: formatDate(TODAY)
    },
    disableAfter: String,
    start: String,
    sameEnable: {
      type: Boolean,
      default: true
    }
  },
  onShow () {
    this.animateFreeze = false
  },
  data () {
    var startMonth = moment(this.start).startOf('month')
    var singleMode = this.labels.length === 1
    return {
      weeks: ['日', '一', '二', '三', '四', '五', '六'],
      disableBeforeMoment: moment(this.disableBefore),
      disableAfterMoment: this.disableAfter && moment(this.disableAfter),
      firstMonth: startMonth,
      curMonth: startMonth.clone(),
      curAmount: 0,
      monthList: [],
      startDateMoment: this.startDate && moment(this.startDate),
      singleMode: singleMode,
      endDateMoment: !singleMode && this.endDate && moment(this.endDate),
      loadFreeze: false,
      animateFreeze: false,
      waiting: false
    }
  },
  computed: {
    startDateMomentYear () {
      return this.startDateMoment.year()
    },
    startDateMomentMonth () {
      return this.startDateMoment.format('MM-DD')
    },
    endDateMomentYear () {
      return this.endDateMoment && this.endDateMoment.year()
    },
    endDateMomentMonth () {
      return this.endDateMoment && this.endDateMoment.format('MM-DD')
    }
  },
  watch: {
    startDateMoment: 'refreshSelectRagne',
    endDateMoment: 'refreshSelectRagne',
    monthList: 'refreshSelectRagne'
  },
  methods: {
    addMonth () {
      var monthItem = new MonthItem({
        curMonth: this.curMonth,
        disableBeforeMoment: this.disableBeforeMoment,
        disableAfterMoment: this.disableAfterMoment,
        startDateMoment: this.startDateMoment,
        endDateMoment: this.endDateMoment
      })
      this.monthList.push(monthItem)
      this.curAmount += 1
      this.curMonth.add(1, 'months')
    },
    loadRepeat () {
      var self = this
      if (!self.loadFreeze && self.showAmount > self.curAmount) {
        self.loadFreeze = true
        self.addMonth()
        setTimeout(() => {
          self.loadFreeze = false
          self.loadRepeat()
        }, 20)
      }
    },
    refreshSelectRagne () {
      var startTimeStamp = this.startDateMoment && this.startDateMoment.toDate().getTime()
      var endTimeStamp = !this.singleMode && this.endDateMoment && this.endDateMoment.toDate().getTime()
      var prefDate = {}
      this.monthList.forEach((monthData) => {
        monthData.dateList.forEach((dateData) => {
          var dateTimeStamp = dateData.timeStamp
          if (dateData.isNull) {
            dateData.isStartDate = dateData.isEndDate = false
            dateData.isProgress = (prefDate.isProgress || prefDate.isStartDate) && endTimeStamp
          } else {
            dateData.isStartDate = dateTimeStamp === startTimeStamp
            dateData.isEndDate = dateTimeStamp === endTimeStamp
            dateData.isProgress = dateTimeStamp > startTimeStamp && dateTimeStamp < endTimeStamp
          }
          prefDate = dateData
        })
      })
    },
    checkDate (date) {
      if (date.isDisable || date.isNull || this.animateFreeze) {
        return
      }
      if (this.startDateMoment && (this.singleMode || this.endDateMoment)) {
        this.startDateMoment = this.endDateMoment = null
      }
      var dateData = date.dateData
      var checkTargetMoment = moment([dateData.year, dateData.month, dateData.date])
      if (!this.startDateMoment || checkTargetMoment[!this.sameEnable ? 'isSameOrBefore' : 'isBefore'](this.startDateMoment)) {
        this.startDateMoment = checkTargetMoment
      } else {
        if (!this.isSame && '' + checkTargetMoment + '' === '' + this.startDateMoment + '') {
          console.log('入离时间不能相同')
          return
        }
        this.endDateMoment = checkTargetMoment
      }
      if (this.singleMode || this.endDateMoment) {
        this.confirm()
        this.waiting = false
      } else {
        this.waiting = true
      }
    },
    confirm () {
      var startDate = formatDate(this.startDateMoment)
      var endDate = !this.singleMode ? formatDate(this.endDateMoment) : undefined
      this.animateFreeze = true
      this.$emit('complete', startDate, endDate)
    }
  },
  mounted () {
    Vue.nextTick(() => {
      this.loadRepeat()
    })
  }
}
</script>

css(sass)

#calerdar{
    background: #fff;
    ul,li{ 
      padding:0;
      margin:0;
      list-style:none
    }
    .layout-page-main{
        border-top:px2rpx(1) solid #E2E2E2;
    }
    #calendar-date{
        height: 4.2rem;
        overflow: hidden;
        .date{
            padding: 0.31rem 0;
            text-align: center;
            width: 10rem;
            .year{
                display: block;
                line-height: 1rem;
                font-size: 1rem;
                color: #9b9b9b;
            }
            .month-date{
                display: block;
                line-height: 1.5rem;
                font-size: 1.5rem;
                color: #3b4f62;
            }
            .label{
                display: block;
                line-height: 1rem;
                font-size: 1rem;
                color: #9b9b9b;
            }
            &.fl{
                float: left;
            }
            &.fr{
                float: right;
            }
            &.placeholder{
                height: 100%;
                position: relative;
                &::before{
                    content: '';
                    display: block;
                    position: absolute;
                    left: 50%;
                    top: 50%;
                    height: 1px;
                    width: 0.9rem;
                    background-color: gray;
                }
            }
        }
        .spliter{
            $block-height: 2rem;
            height: $block-height;
            margin: 0.7rem auto;
            position: relative;
            overflow: hidden;
            &::before{
                content: '';
                position:absolute;
                display: block;
                height: $block-height;
                width: 1px;
                background-color: gray;
                left: 45%;
                transform: rotate(32deg);
            }
        }
    }
    #week-label{
        $height: 2rem;
        overflow: hidden;
        border-bottom: 1px solid #dedede;
        .item{
            float: left;
            width: (100%/7);
            text-align: center;
            $height: $height;
            line-height: $height;
            color: #b8b8b8;
            font-size: 1.2rem;
            &:first-child,
            &:last-child{
                color: #ff7362;
            }
        }
    }
    $item-width: (10rem / 7);
    $num-width: 2rem;
    $space-w: ($item-width - $num-width) * 0.5;
    #month-list{
        padding-top: 0.2rem;
        height: 100%;
        overflow: auto;
        .month-item{
            overflow: hidden;
            padding-top: 0.35rem;
            .month-info{
                line-height: 0.86rem;
                padding-left: 0.37rem;
                color: #b7b7b7;
                font-size: 1.5rem;
                text-align: left;
            }
            .month-main{
                overflow: hidden;
                padding-top: 0.2rem;
                .item{
                    float: left;
                    width: (100%/7);
                    color: #969696;
                    .num{
                        display:block;
                        position: relative;
                        // margin: auto;
                        height: $num-width;
                        line-height: $num-width;
                        text-align: center;
                        font-size: 1rem;
                    }
                    &.disable{
                        color: #e2e2e2;
                    }
                    &.today{
                      color: #0cc071;
                    }
                    &.start-date .num{
                      border-radius: .2rem 0 0 .2rem;
                      background-color: #0cc071;
                      color: #fff;
                    }
                    &.end-date .num{
                        border-radius: 0 .2rem .2rem 0;
                        background-color: #0cc071;
                        color: #fff;
                    }
                    &.start-date{
                        &.end-date .num{
                          border-radius: .2rem;
                        }
                    }
                    &.progress .num{
                      background-color: #0cc0717d;
                      color: #fff;
                    }
                }
            }
        }
    }
    $space-w: 1rem;
    &[calendar-type="multiple"]{
        [state="complete"]{
            .start-date .num{
                margin-left: $space-w;
                padding-right: $space-w;
                border-top-left-radius: 99rem;
                border-bottom-left-radius: 99rem;
            }
            .end-date .num{
                margin-right: $space-w;
                padding-left: $space-w;
                border-top-right-radius: 99rem;
                border-bottom-right-radius: 99rem;
            }
        }
        [state="waiting"]{
            .start-date .num{
                margin: 0 $space-w;
                border-radius: 100%;
            }
        }
    } 
    &[calendar-type="single"]{
        // set-header-height(1.3rem)
        #week-label{
            margin-top: 0.3rem;
        }
        .item.start-date .num{
            margin: 0 $space-w;
            padding: 0;
            border-radius: 100%;
        }
    }
}
点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消