美文网首页Vue实验室
mint-ui 源码学习三 —— datetime-picker

mint-ui 源码学习三 —— datetime-picker

作者: VioletJack | 来源:发表于2018-09-14 10:34 被阅读8次

    上一篇,我们了解了 picker 的实现及基本原理。顺带着看看 datetime-picker 组件和 picker 有何不同吧~
    PS: 看本文前了解下mint-ui 源码学习二 —— picker 选择器组件源码学习有助于对本篇博客的理解。

    整体结构

    先看下 HTML 代码:

      <mt-popup v-model="visible" :closeOnClickModal="closeOnClickModal" position="bottom" class="mint-datetime">
        <mt-picker
          :slots="dateSlots"
          @change="onChange"
          :visible-item-count="visibleItemCount"
          class="mint-datetime-picker"
          ref="picker"
          show-toolbar>
          <span class="mint-datetime-action mint-datetime-cancel" @click="visible = false;$emit('cancel')">{{ cancelText }}</span>
          <span class="mint-datetime-action mint-datetime-confirm" @click="confirm">{{ confirmText }}</span>
        </mt-picker>
      </mt-popup>
    

    从上面的代码中可以看出,其实 datetime-picker 就是基于 popup 弹层和 picker 选择器来实现的。
    在逻辑方法上使用了很多的日期时间计算的方法,最后通过 v-model 把日期或时间结果给返回。

    v-model的实现

    来看下组件的 v-model 是如何实现的~
    首先在 props 中添加了 value 这个 prop

    props: {
      value: null
    }
    

    然后在 data 里面定义一个记录 value 变化的值 currentValue

    data: {
      return {
        currentValue: null
      }
    }
    

    然后监听 value 将它的值传给 currentValue

    watch: {
      value(val) {
        this.currentValue = val;
      }
    },
    mounted() {
      this.currentValue = this.value;
    }
    

    至此 value 的工作完成了,而 currentValue 会去获取 picker 变化后的值。这里计算时间选择器结果的方法如下:
    当 picker 的值发生变化触发它的 change 事件,在这个事件中去重新获取当前的时间结果传给 currentValue。

    onChange(picker) {
      let values = picker.$children.filter(child => child.currentValue !== undefined).map(child => child.currentValue);
      if (this.selfTriggered) {
        this.selfTriggered = false;
        return;
      }
      if (values.length !== 0) {
        this.currentValue = this.getValue(values);
        this.handleValueChange();
      }
    },
    

    getValue 方法

        // get time value or date value
        getValue (values) {
          let value
          if (this.type === 'time') {
            value = values.map(value => ('0' + this.getTrueValue(value)).slice(-2)).join(':')
          } else {
            let year = this.getTrueValue(values[0])
            let month = this.getTrueValue(values[1])
            let date = this.getTrueValue(values[2])
            let maxDate = this.getMonthEndDay(year, month)
            if (date > maxDate) {
              this.selfTriggered = true
              date = 1
            }
            let hour = this.typeStr.indexOf('H') > -1 ? this.getTrueValue(values[this.typeStr.indexOf('H')]) : 0
            let minute = this.typeStr.indexOf('m') > -1 ? this.getTrueValue(values[this.typeStr.indexOf('m')]) : 0
            value = new Date(year, month - 1, date, hour, minute)
          }
          return value
        },
    

    最后,将 currentValue 的值通过 handleValueChange 方法中的 emit 方法发送将时间结果给父级组件。

    handleValueChange() {
      this.$emit('input', this.currentValue);
    }
    

    这就是整个 datetime-picker 的 v-model 的实现了。当然也是 v-model 的通用实现方式。小结 v-model 的实现方式:

    • 定义 value prop
    • 定义 currentValue data
    • 将 value 任何变化都传给 currentValue
    • 将数据结果传给 currentValue
    • 通过 this.$emit('input', currentValue) 将数据返回。

    confirm 事件和 cancel 事件

    这两个事件很简单,看代码:

    confirm() {
      this.visible = false;
      this.$emit('confirm', this.currentValue);
    },
    
    cancel() {
      this.visible = false;
      this.$emit('cancel')
    }
    

    就是关闭 datetime-picker 然后触发 confirm 和 cancel 事件。

    限定时间范围并填充 slot

    在选择器中还有个限制时间范围的功能,看下是如何实现的。
    首先在 mounted 事件中如果没有定义 value 值会定义 picker 的默认选择 startHour 或者 startDate(看类型是不是 time)。

    mounted() {
      this.currentValue = this.value;
      if (!this.value) {
        if (this.type.indexOf('date') > -1) {
          this.currentValue = this.startDate;
        } else {
          this.currentValue = `${ ('0' + this.startHour).slice(-2) }:00`;
        }
      }
      this.generateSlots();
    }
    

    之后在 computed 对象中有一个 rims 属性计算 年月日时分 的数字范围。可以看到 startHour 和 endHour 值作用域 type 为 time 的时候,而 startDate 和 endDate 用于其他事件选择上。

    rims() {
      if (!this.currentValue) return { year: [], month: [], date: [], hour: [], min: [] };
      let result;
      if (this.type === 'time') {
        result = {
          hour: [this.startHour, this.endHour],
          min: [0, 59]
        };
        return result;
      }
      result = {
        year: [this.startDate.getFullYear(), this.endDate.getFullYear()],
        month: [1, 12],
        date: [1, this.getMonthEndDay(this.getYear(this.currentValue), this.getMonth(this.currentValue))],
        hour: [0, 23],
        min: [0, 59]
      };
      this.rimDetect(result, 'start');
      this.rimDetect(result, 'end');
      return result;
    },
    

    从 result 的构成可以发现时间里面除了年和日会不断变化,其他时间范围是不变的。这有助于我们自己处理时间。
    在知道了时间范围后,填充 picker 的 slot 就变得很简单了~下面是填充 slot 的方法:

    pushSlots(slots, type, start, end) {
      slots.push({
        flex: 1,
        values: this.fillValues(type, start, end)
      });
    },
    

    一些实用的时间计算方法

    快速让所有数字变为两位数的方法,如 02 04 13 55 这样。

    this.currentValue = `${('0' + this.startHour).slice(-2)}:00`
    

    获取年份、短月、某月天数的方法

        // 是否为闰年
        isLeapYear (year) {
          return (year % 400 === 0) || (year % 100 !== 0 && year % 4 === 0)
        },
    
        // 是否为短月
        isShortMonth (month) {
          return [4, 6, 9, 11].indexOf(month) > -1
        },
    
        // 获取某一月的天数
        getMonthEndDay (year, month) {
          if (this.isShortMonth(month)) {
            return 30
          } else if (month === 2) {
            return this.isLeapYear(year) ? 29 : 28
          } else {
            return 31
          }
        },
    

    下面是处理获取年月日时间的方法

        // 过去年月日时分
        getYear (value) {
          return this.isDateString(value) ? value.split(' ')[0].split(/-|\/|\./)[0] : value.getFullYear()
        },
    
        getMonth (value) {
          return this.isDateString(value) ? value.split(' ')[0].split(/-|\/|\./)[1] : value.getMonth() + 1
        },
    
        getDate (value) {
          return this.isDateString(value) ? value.split(' ')[0].split(/-|\/|\./)[2] : value.getDate()
        },
    
        getHour (value) {
          if (this.isDateString(value)) {
            const str = value.split(' ')[1] || '00:00:00'
            return str.split(':')[0]
          }
          return value.getHours()
        },
    
        getMinute (value) {
          if (this.isDateString(value)) {
            const str = value.split(' ')[1] || '00:00:00'
            return str.split(':')[1]
          }
          return value.getMinutes()
        },
    

    最后

    从源码学习中可知,一切都是基于了 picker 组件来实现的。主要麻烦的是对时间逻辑和数据处理上。希望通过这么详细的分析可以让大家更方便的理解 datetime-picker 组件的原理。便于对组件的使用和理解。

    相关文章

      网友评论

        本文标题:mint-ui 源码学习三 —— datetime-picker

        本文链接:https://www.haomeiwen.com/subject/ozlbgftx.html