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

上一篇,我们了解了 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 组件的原理。便于对组件的使用和理解。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容