上一篇,我們了解了 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ì)組件的使用和理解。