【UI库】datetime-picker组件

时间选择器

国庆高速公路堵车一天,隔天才到家,后来回家就安心当个死肥宅了,现在得继续努力了。争取这个月把基本的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时间选择器最不好的感受是,不能初始化高亮某个值,在自己封装的选择器里面改良了这个缺点~

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值