Vue实验室

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

2018-09-14  本文已影响8人  VioletJack

上一篇,我们了解了 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 的实现方式:

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 组件的原理。便于对组件的使用和理解。

上一篇下一篇

猜你喜欢

热点阅读