mint-ui 源碼學(xué)習(xí)三 —— datetime-picker 源碼學(xué)習(xí)

上一篇,我們了解了 picker 的實(shí)現(xiàn)及基本原理。順帶著看看 datetime-picker 組件和 picker 有何不同吧~
PS: 看本文前了解下mint-ui 源碼學(xué)習(xí)二 —— picker 選擇器組件源碼學(xué)習(xí)有助于對(duì)本篇博客的理解。

整體結(jié)構(gòu)

先看下 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>

從上面的代碼中可以看出,其實(shí) datetime-picker 就是基于 popup 彈層和 picker 選擇器來實(shí)現(xiàn)的。
在邏輯方法上使用了很多的日期時(shí)間計(jì)算的方法,最后通過 v-model 把日期或時(shí)間結(jié)果給返回。

v-model的實(shí)現(xiàn)

來看下組件的 v-model 是如何實(shí)現(xiàn)的~
首先在 props 中添加了 value 這個(gè) prop

props: {
  value: null
}

然后在 data 里面定義一個(gè)記錄 value 變化的值 currentValue

data: {
  return {
    currentValue: null
  }
}

然后監(jiān)聽 value 將它的值傳給 currentValue

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

至此 value 的工作完成了,而 currentValue 會(huì)去獲取 picker 變化后的值。這里計(jì)算時(shí)間選擇器結(jié)果的方法如下:
當(dāng) picker 的值發(fā)生變化觸發(fā)它的 change 事件,在這個(gè)事件中去重新獲取當(dāng)前的時(shí)間結(jié)果傳給 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 方法發(fā)送將時(shí)間結(jié)果給父級(jí)組件。

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

這就是整個(gè) datetime-picker 的 v-model 的實(shí)現(xiàn)了。當(dāng)然也是 v-model 的通用實(shí)現(xiàn)方式。小結(jié) v-model 的實(shí)現(xiàn)方式:

  • 定義 value prop
  • 定義 currentValue data
  • 將 value 任何變化都傳給 currentValue
  • 將數(shù)據(jù)結(jié)果傳給 currentValue
  • 通過 this.$emit('input', currentValue) 將數(shù)據(jù)返回。

confirm 事件和 cancel 事件

這兩個(gè)事件很簡(jiǎn)單,看代碼:

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

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

就是關(guān)閉 datetime-picker 然后觸發(fā) confirm 和 cancel 事件。

限定時(shí)間范圍并填充 slot

在選擇器中還有個(gè)限制時(shí)間范圍的功能,看下是如何實(shí)現(xiàn)的。
首先在 mounted 事件中如果沒有定義 value 值會(huì)定義 picker 的默認(rèn)選擇 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 對(duì)象中有一個(gè) rims 屬性計(jì)算 年月日時(shí)分 的數(shù)字范圍。可以看到 startHour 和 endHour 值作用域 type 為 time 的時(shí)候,而 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 的構(gòu)成可以發(fā)現(xiàn)時(shí)間里面除了年和日會(huì)不斷變化,其他時(shí)間范圍是不變的。這有助于我們自己處理時(shí)間。
在知道了時(shí)間范圍后,填充 picker 的 slot 就變得很簡(jiǎn)單了~下面是填充 slot 的方法:

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

一些實(shí)用的時(shí)間計(jì)算方法

快速讓所有數(shù)字變?yōu)閮晌粩?shù)的方法,如 02 04 13 55 這樣。

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

獲取年份、短月、某月天數(shù)的方法

    // 是否為閏年
    isLeapYear (year) {
      return (year % 400 === 0) || (year % 100 !== 0 && year % 4 === 0)
    },

    // 是否為短月
    isShortMonth (month) {
      return [4, 6, 9, 11].indexOf(month) > -1
    },

    // 獲取某一月的天數(shù)
    getMonthEndDay (year, month) {
      if (this.isShortMonth(month)) {
        return 30
      } else if (month === 2) {
        return this.isLeapYear(year) ? 29 : 28
      } else {
        return 31
      }
    },

下面是處理獲取年月日時(shí)間的方法

    // 過去年月日時(shí)分
    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()
    },

最后

從源碼學(xué)習(xí)中可知,一切都是基于了 picker 組件來實(shí)現(xiàn)的。主要麻煩的是對(duì)時(shí)間邏輯和數(shù)據(jù)處理上。希望通過這么詳細(xì)的分析可以讓大家更方便的理解 datetime-picker 組件的原理。便于對(duì)組件的使用和理解。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容