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