时间选择器
国庆高速公路堵车一天,隔天才到家,后来回家就安心当个死肥宅了,现在得继续努力了。争取这个月把基本的UI库抄完~
需求
-
支持选择年月日;
-
支持选择年月日时;
-
支持选择年月日时分;
-
支持选择时分;
-
支持高亮在指定的值。
看了
vant
的源码,datatime-pick
组件是基于picker
组件开发的,想起picker
组件我在抄的过程中改了很多东西,所以到了考验我的picker
组件易扩展性的时候到了(心虚…)
开发思路
基本结构
直接给picker
组件传值:
<template>
<div class="ar-datetime-picker">
<picker :columns="columns"
:defaultValue="defaultValue"
:itemHeight="itemHeight"
:cancelText="cancelText"
:cancelColor="cancelColor"
:confirmText="confirmText"
:confirmColor="confirmColor"
:title="title"
:toolBarHeight="toolBarHeight"
@change="onChange"
@cancel="onCancel"
@confirm="onConfirm"/>
</div>
</template>
Props确定
@Prop({ default: 'date' }) private type!: typeProps;
@Prop({default: 44}) private itemHeight!: number | string;
@Prop({default: '取消'}) private cancelText!: string;
@Prop({default: '#5e6d82'}) private cancelColor!: string;
@Prop({default: '确认'}) private confirmText!: string;
@Prop({default: '#007bff'}) private confirmColor!: string;
@Prop({default: '请选择'}) private title!: string;
@Prop({default: '44'}) private toolBarHeight!: number | string;
@Prop() private value!: null;
其中typeProps
:
type typeProps = 'date' | 'time' | 'year-month' | 'datehour' | 'datetime';
呈现不同的列
假定type = date
。当type = date
时,代表该组件是一个可以选择年月日的组件。当不传递默认值时,我们默认高亮当前日期,我们再支持传递当前日期选择的最小日期和最大日期(时间戳,单位为秒),要精确到分钟(因为到type=datetime
时会显示到分);不指定的话,界限就默认是当日的前十年和后十年。
-
先获得这一年是哪一年,确定最大值和最小值:
const currentYear = new Date().getFullYear(); @Prop({default: () => new Date(currentYear - 10, 0, 1)}) private minDate!: Date; @Prop({default: () => new Date(currentYear + 10, 11, 31)}) private maxDate!: Date;
-
判断用户传递的当前值是否合理,否则使用当前时间, 使用
defaultValue
高亮在正确的位置:@Watch('value', { immediate: true}) changeValue(newVal: any){ newVal = this.formatValue(newVal); this.innerValue = newVal; this.defaultValue = this.formatDefaultValue(newVal); }
private formatValue(value: any){ if(!isDate(value)){ value = new Date(); } value = Math.max(value, this.minDate.getTime()); value = Math.min(value, this.maxDate.getTime()); return new Date(value); } private formatDefaultValue(value: Date){ const year = value.getFullYear(); const month = padZero(value.getMonth() + 1); const day = padZero(value.getDate()); return `${year},${month},${day}`; }
-
根据当前默认的值,获得
columns
组合innerValue
是一个Date
类型的数据,而defaultValue
是一个string
数据,通过以下做法,可以获得当前合理的默认值。@Watch('value', { immediate: true}) changeValue(newVal: any){ newVal = this.formatValue(newVal); this.innerValue = newVal; this.defaultValue = this.formatDefaultValue(newVal); }
private formatValue(value: any){ value = new Date(); value = Math.max(value, this.minDate.getTime()); value = Math.min(value, this.maxDate.getTime()); return new Date(value); } private formatDefaultValue(value: Date){ const year = value.getFullYear(); const month = padZero(value.getMonth() + 1); const day = padZero(value.getDate()); return `${year},${month},${day}`; }
接下来根据上下限获得三列的数据:
getBoundary
获得最大或者最小的 年月日时分private getBoundary(type: boundaryType, value: Date){ const boundary = type == 'min' ? this.minDate : this.maxDate; const year = boundary.getFullYear(); let month = 1; let date = 1; let hour = 0; let minute = 0; if(type === 'max'){ month = 12; date = getMonthEndDay(value.getFullYear(), value.getMonth()); hour = 23; minute = 59; } if(value.getFullYear() === year){ month = boundary.getMonth() + 1; if(value.getMonth() + 1 === month){ date = boundary.getDate(); if(value.getDate() === date){ hour = boundary.getHours(); if(value.getHours() === hour){ minute = boundary.getMinutes(); } } } } return { [`${type}Year`]: year, [`${type}Month`]: month, [`${type}Date`]: date, [`${type}Hour`]: hour, [`${type}Minute`]: minute } }
ranges
获得我们需要的列数:get ranges(){ const { maxYear, maxDate, maxMonth, maxHour, maxMinute } = this.getBoundary( 'max', this.innerValue ); const { minYear, minDate, minMonth, minHour, minMinute } = this.getBoundary( 'min', this.innerValue ); const result = [ { type: 'year', range: [minYear, maxYear] }, { type: 'month', range: [minMonth, maxMonth] }, { type: 'day', range: [minDate, maxDate] }, { type: 'hour', range: [minHour, maxHour] }, { type: 'minute', range: [minMinute, maxMinute] } ]; if (this.type === 'date') result.splice(3, 2); return result; }
get orginColumns(){ return this.ranges.map(({ type, range: rangeArr}) => { let values = this.times(rangeArr[1] - rangeArr[0] + 1, (index: number): any => { const value = padZero(rangeArr[0] + index); return value; }); return { type, values } }); }
columns
就是我们需要的列组件啦~get columns(){ const res = this.orginColumns.map((item) => { const tmp = []; const column = item.values; for(let i = 0, len = column.length; i < len; i++){ let obj = { id: column[i], name: column[i] } tmp.push(obj); } return tmp; }); return res; }
- 考虑不同
type
的情况:date
: 年月日;year-month
:年月;datehour
:年月日时;datetime
:年月日时分;- time:时分。
private formatDefaultValue(value: Date){
// ...
switch(this.type){
case 'date': return `${year},${month},${day}`;
case 'year-month': return `${year},${month}`;
case 'datehour': return `${year},${month},${day},${hour}`;
case 'datetime': return `${year},${month},${day},${hour},${minutes}`;
case 'time': return `${hour},${minutes}`;
default: return `${year},${month},${day}`;
}
}
get ranges(){
// ...
switch(this.type){
case 'date': return result.splice(3, 2);
case 'year-month': return result.splice(2, 3);
case 'datehour': return result.splice(0, 4);
case 'datetime': return result;
case 'time': return result.splice(-2, 2);
default: return result.splice(3, 2);
}
}
列格式化
由于有时候我们想要显示的是xx年xx月xx日xx时xx分
,所以组件提供一个供用户格式化列显示的函数,用户可以根据type
的值,来格式化。
@Prop() private formatter!: any;
get columns(){
// ...
for(let i = 0, len = column.length; i < len; i++){
const value = this.formatter ? this.formatter(type, column[i]) : column[i];
let obj = {
id: column[i],
name: value
}
tmp.push(obj);
}
return tmp;
});
return res;
}
这样用户就可以这样使用了:
<ar-datetime-picker :formatter="formatter" />
private formatter(type: string, value: any){
if(type === 'year') return `${value}年`;
else if(type === 'month') return `${value}月`;
else if(type === 'day') return `${value}日`;
else if(type === 'hour') return `${value}时`;
else if(type === 'minute') return `${value}分`;
return value;
}
chang事件
private onChange(obj: any){
const type = this.ranges[obj.index].type;
this.$emit('change', {
type,
id: obj.id,
name: obj.name
})
}
confirm事件
private onConfirm(obj: any){
let value = obj.value.split(',');
let res;
if(this.type === 'date' || this.type === 'year-month'){
res = value.join('/');
}else if(this.type === 'datehour') {
res = `${value[0]}/${value[1]}/${value[2]} ${value[3]}:00`;
}else if(this.type === 'datetime'){
res = `${value[0]}/${value[1]}/${value[2]} ${value[3]}:${value[4]}`;
}else {
const year = new Date().getFullYear();
const month = padZero(new Date().getMonth() + 1);
const day = padZero(new Date().getDate());
res = `${year}/${month}/${day} ${value.join(':')}`;
}
this.$emit('confirm',new Date(res))
}
其他类似cancel
等事件就不细写了。
写在最后
最近好忙啊/(ㄒoㄒ)/~~,断断续续写了好几天终于写完了。自己封装的picker
组件扩展性还是可以的,可以基于它再封装成时间选择器。vant
中的源码是将时间和日期拆分出来了,看vant
的源码最大感受就是拆的好细致。
在自己公司项目中使用vant
时间选择器最不好的感受是,不能初始化高亮某个值,在自己封装的选择器里面改良了这个缺点~