自定义时间组件解析

466 阅读5分钟

时间组件最开始是没有时区概念的,在时区化之前大家都可以看得懂,自从加入时区概念以后,时间组件从此变得惨不忍睹。。。又无奈。今天我们就拿多选时间范围组件来举例拉通一遍加入时区后得逻辑。

01-组件的入参

入参必须是对应用户所在时区的时间时间戳或字符串

startTime: {
    type: [Number, String],
    default: new Date().getTime()
},

endTime: {
    type: [Number, String],
    default: new Date().getTime()
},

入参参数目前由页面自己计算好给组件,例如你的页面初始化是近30天,那页面中就要将对应的30天前的startTime和到今天的最后一天endTime给组件。如果不想自己计算,当然也有懒人方法,需要的可以去时间组件里面了解下面这个props参数

// 是否需要初始化的时候返回时间
needFirstReturn: Boolean,

目前组件仅支持以时间戳和字符串这两种形式入参。

时间戳: (秒级:1652275763562 和 毫秒级:1652275763562000),组件在接收得时候会自动识别传入的时间戳长度并补化成最终的毫秒级,因为之后都一直用毫秒做各种处理。

字符串:'2022-01-09'或者'2022/03/05' 这类能够直接用new Date() new出来的标准字符串格式。

02-组件的出参

出参的开始时间和结束时间都是组件内部转化好的用户时区的时间结果,可用于直接请求后台数据,不需要做任何处理即可使用。 组件通过dataChange回调事件发送出参给页面。

 this.$emit('dateChange', [开始时间, 结束时间], 当前的快捷索引)

这是一个多余的 但是又无法弃用 需要继续使用的涉及出参的prop参数(因为很多页面在用,所以不好重构了):

returnDateType: {
    type: String,
    default: 'timeStamp'
},

顾名思义就是出参的时候页面需要什么样的结构 是字符串'String'还是时间戳'timeStamp',这个其实一开始脑子瓦特了没想好,其实很多余,事后想了下,还不如直接把字符串和时间戳全部return给页面,页面想用哪个就用哪个。这样还可以少传一个props参数。

03-组件的使用

<DatePicker
    v-if="isHasDate"
    :ac-index="acIndexComment"
    :end-time="commentEnd"
    :grayness-style="true"
    :shortcuts-array="[1,2,30,90]"
    :start-time="commentStart"
    class="comment-bottom-date"
    return-date-type="String"
    @acIndexChange="acIndexChangeComment"
    @dateChange="commentDateChange" />
  • acIndex:时间快捷索引标识 1/2/30/90/11 组件用于识别初始化的时间结果显示
  • startTime:开始时间参数
  • endTime:结束时间参数
  • graynessStyle:时间组件皮肤控制 深色和灰色两种 默认深色
  • shortcutsArray:快捷参数 [1,2,30,7]
  • returnDateType:回调时间格式控制 默认时间戳格式
  • @acIndexChange:快捷索引变化回调 返回组件最新的快捷索引
  • @dateChange:时间结果回调事件,返回结果

04-组件核心逻辑

1. 枚举文件解析
2. 传入AcIndex后,件是如何回显出选中的时间结果的?
3. 传入shortcutsArray后,组件是如何回显出快捷按钮列表的,点击快捷列表,组件如何return 时间结果的?
4. 页面中不需要快捷时间选项 如何让组件初始化的时候直接显示自定义时间?
04-1. 枚举文件解析
/* 日期选择器快捷选择 --- 参数数据*/
import moment from 'moment'

export function shortcuts(that) {
    const endFlag = 24 * 3600 * 1000 - 1000
    // 获取当前环境的时区
    const nowZone = moment().utcOffset() / 60
    // 获取自定义的时区
    const customZone = that.$store.getters.timeZone
    // 获取时间差  毫秒级
    const zoneDiff = (nowZone - customZone) * 3600 * 1000
    return [
       //全部
        {
            sortId: 11,
            text: that.$t('datePicker.all'),
            onClick(picker) {
                that.innerAcIndex = 11 // 用于内部快捷键索引值
                picker.shortcutActiveIndex = 11
            }
        },
        // 未来30天
        {
            sortId: 'future30',
            text: that.$t('datePicker.last30Days'),
            onClick(picker) {
                that.innerAcIndex = 'future30' // 用于内部快捷键索引值
                const flagTime = new Date(new Date().toDateString()).getTime()
                const start = that.$timeConversion.todayAndRangeTime().targetStartTime
                const end = that.$timeConversion.todayAndRangeTime(customZone, flagTime + 3600 * 1000 * 24 * 29, 'number').otherTime.end
                const nowStart = that.$timeConversion.todayAndRangeTime(nowZone, start - zoneDiff, 'number').otherTime.start
                const nowEnd = that.$timeConversion.todayAndRangeTime(nowZone, end - zoneDiff, 'number').otherTime.end
                console.log('未来30天转换为本地时区的是时间', nowStart, nowEnd, new Date(nowStart), new Date(nowEnd))
                that.dateType === 'date' && picker.$emit('pick', nowStart)
                that.dateType !== 'date' && picker.$emit('pick', [nowStart, nowEnd])
                return [nowStart, nowEnd]
            }
        },
       
        {
            sortId: 90,
            text: that.$t('datePicker.last90Days'),
            onClick(picker) {
                that.innerAcIndex = 90 // 用于内部快捷键索引值
                const flagTime = new Date(new Date().toDateString()).getTime()
                const end = that.$timeConversion.todayAndRangeTime(customZone, flagTime, 'number').otherTime.end
                const start = that.$timeConversion.todayAndRangeTime(customZone, flagTime - 3600 * 1000 * 24 * 89, 'number').otherTime.start
                const nowEnd = that.$timeConversion.todayAndRangeTime(nowZone, end - zoneDiff, 'number').otherTime.end
                const nowStart = that.$timeConversion.todayAndRangeTime(nowZone, start - zoneDiff, 'number').otherTime.start
                that.dateType === 'date' && picker.$emit('pick', nowStart)
                that.dateType !== 'date' && picker.$emit('pick', [nowStart, nowEnd])
                return [nowStart, nowEnd]
            }
        },
        {
            sortId: 300,
            text: that.$t('datePicker.last3Months'),
            onClick(picker) {
                that.innerAcIndex = 300 // 用于内部快捷键索引值
                const end = new Date(new Date().toDateString()).getTime()
                const start = new Date(new Date().toDateString())
                start.setMonth(start.getMonth() - 2)
                const endDate = that.$timeConversion.todayAndRangeTime(customZone, end, 'number').otherTime.end
                const startDate = that.$timeConversion.todayAndRangeTime(customZone, new Date(start).getTime(), 'number').otherTime.start
                that.dateType === 'date' && picker.$emit('pick', startDate)
                that.dateType !== 'date' && picker.$emit('pick', [startDate, endDate])
                return [startDate, endDate]
            }
        }
        ........省略其他
        }
    ]
}

枚举文件整体结构跟饿了么组件保持一致,只是改动了国际化 和新增了sortId标识用于和acIndex配合使用,传入快捷数组参数shortcutsArray时,数组元素必须跟这个sortId保持一致,这样组件才能通过快捷索引抓取到正确的快捷时间范围。

如果需要新增其他快捷选项,可以按照逻辑新增下去, 需要特殊说明的是return出去的时间是怎么计算出来的,以获取近90天来讲解:

{
    sortId: 90,
    text: that.$t('datePicker.last90Days'),
    onClick(picker) {
        that.innerAcIndex = 90 // 用于内部快捷键索引值
        //获取一个标杆时间戳 此时此刻的00:00:00
        const flagTime = new Date(new Date().toDateString()).getTime()
        
        
        //通过潘总的时区时间获取方法获取到用户自定义时区的结束时间戳  customZone指向用户所在时区
        const end = that.$timeConversion.todayAndRangeTime(customZone, flagTime, 'number').otherTime.end
        //获取到用户自定义时区的开始时间戳 减多天就可以得到多少天前的时区时间戳
        const start = that.$timeConversion.todayAndRangeTime(customZone, flagTime - 3600 * 1000 * 24 * 89, 'number').otherTime.start
        
        
        //因为时间组件是没有时区概念的,它一直都是运行在浏览器里面,所以如果直接把上面算出来的时间扔进时间组件回显,就会发生回显错误,它会以浏览器时区来展示时间。
下面这一步就是为了获取到"当前浏览器时区的时间" 所对应的"用户时区的诗句",这样即时是以浏览器时区时间来展示, 那也是展示的是正确的用户时区时间。 nowZone指向浏览器时区  zoneDiff 指向时区时间戳差值
        const nowEnd = that.$timeConversion.todayAndRangeTime(nowZone, end - zoneDiff, 'number').otherTime.end
        const nowStart = that.$timeConversion.todayAndRangeTime(nowZone, start - zoneDiff, 'number').otherTime.start
        
        //dateType 是页面中props参数 dateType === 'date'代表多选 需要返回开始和结束  否则为单选 只需要返回一个时间参数即可,所以这个枚举文件可兼容多选与单选的回显  picker.$emit('pick', nowStart)就是用于在组件上面回显时间选中的方法,调用此方法并传入参数,时间组件就可以自己高显时间结果;其中 picker是页面带入的诗句组件实例this 'pick'是饿了么组件规定的回显时间的事件名称。
        that.dateType === 'date' && picker.$emit('pick', nowStart)
        that.dateType !== 'date' && picker.$emit('pick', [nowStart, nowEnd])
        return [nowStart, nowEnd]
    }
},
04-2 传入AcIndex后,件是如何回显出选中的时间结果的?

时间组件会到页面传入的acIndex变化,在初始化时就会进行监听:

/* 监听快捷按钮切换--- 返回计算好的快捷时间 */
acIndex: {
    handler(val) {
        console.log('acIndex:', val, '--->', typeof (val))
        if (val === 'datePicker') {
            this.shortcutBtnCurrentIndex = 'datePicker'
            this.hasDateResult = true
        } else {
            if (val !== 11) {
                /* 曹冰冰自定义开始 */
                if (val === 'empty') {
                    this.hasDateResult = false
                }
                /* 曹冰冰自定义结束 */
                this.shortcutBtnCurrentIndex = val
                this.datePickerShortcuts.length === 0 && this.updateShortcuts()
                if (this.hasDateResult) this.shortcutBtnCurrentIndex = 'datePicker'
                let responseDate = null
                !!!!!循环枚举文件,获取时间结果
                this.datePickerShortcuts.forEach((item) => {
                    if (item.sortId === val) {
                        responseDate = item.onClick(this)
                        // console.log('datePicker通过acIndex捕获快捷日期范围:', responseDate)
                        this.date = responseDate
                        // 懒人方法  直接return时区时间出去  因为快捷的回调时间是计算好了的对应时区的时间 可以直接用来请求后台数据
                        this.needFirstReturn && this.$emit('dateChange', [this.formatDate(responseDate[0], 1), this.formatDate(responseDate[1], 2)], this.acIndex)
                    }
                })
            }
        }
    },
    immediate: true
},
04-3 传入shortcutsArray后,组件是如何回显出快捷按钮列表的,点击快捷列表,组件如何return 时间结果的?
传入shortcutsArray后,组件是如何回显出快捷按钮列表的:

组件监听页面传入快捷索引数组参数shortcutsArray

/* 监听配置快捷项数组的变化 来更新组件的快捷选项 */
shortcutsArray: {
    handler() {
        this.updateShortcuts()
    },
    immediate: true,
    deep: true
},

调用方法进行过滤 枚举文件,把命中的快捷参数都提取出来

updateShortcuts() {
    this.shortcuts = []
    this.datePickerShortcuts = []
    // 获取快捷选项数据
    shortcuts(this).forEach((item, index1) => {
        this.shortcutsArray.length > 0 && this.shortcutsArray.forEach((item2, index2) => {
        
            // 过滤时间组件外部的快捷参数 shortcuts指的是外部的那一排快捷索引结构;
            item.sortId === item2 && this.shortcuts.push(item)
            
            // 过滤时间组件使用的快捷参数   datePickerShortcuts指的是时间组件内部的侧边快捷索引结构,要排除全部全部 和  hideInnerShortcuts是否隐藏内部快捷结构的判断
            if (item.sortId === item2 && item.sortId !== 11 && !this.hideInnerShortcuts) 
            this.datePickerShortcuts.push(item)
        })
    })
    this.datePickerShortcuts.length > 0 ? this.pickerOptions.shortcuts = this.datePickerShortcuts : this.$delete(this.pickerOptions, 'shortcuts')
},

这样就将枚举筛选完成并且快捷参数初始化的时候混入时间组件中去,因为它的执行速度快于初始化时间组件的 initDatePicker事件

image.png

点击快捷列表,组件如何return 时间结果的:

通过下面这个方法进行判断并调用$emit进行回调的

/** 切换快捷时间选项卡
 * @param {String|Number} index:点击的快捷标杆
 * @param {Object} item:被点击的快捷枚举对象
 **/
handClickDateItem(index, item) {
    if (index !== 'datePicker') {
        this.$emit('acIndexChange', index)
        this.hasDateResult = false
    }
    // 如果选择全部
    if (index === 11) {
        this.shortcutBtnCurrentIndex = index
        this.date = [] // 直接清空日期
        this.$emit('handClickAll') // $emit 点击全部事件
    }
    // 用户点击非全部的其他快捷选项
    if (index !== 11 && index !== 'datePicker') {
        this.date = item.onClick(this)
        // 避免重复触发dateChange事件
        if (this.shortcutBtnCurrentIndex !== index) {
            this.shortcutBtnCurrentIndex = index
            this.computedDate(this.date[0], this.date[1])
            // 最近24h 需要特殊处理
            if (index === 24) {
                this.$emit('dateChange', this.date, index)
            } else {
                this.$emit('dateChange', [this.formatDate(this.date[0], 1), this.formatDate(this.date[1], 2)], index)
            }
        }
    }
},
04-4 页面中不需要快捷时间选项 如何让组件初始化的时候直接显示自定义时间?

这个算是一个遗憾吧,之前要求页面传入下面这个参数,其实完全没必要,后期页面可以直接将AcIndex 传为'datePicker'来替代

// (不推荐再使用 请初始化时将acIndex传为'datePicker'来代替)
hasDefaultDate: {
    type: Boolean,
    default: false
},

传入这个参数后,组件会进行监听

/* 监听是否初始化的时间组件就需要高显时间结果 (已废弃  请用:acIndex='datePicker'代替)  */
hasDefaultDate: {
    handler(val) {
        // 默认时间参数如果有,展开时间结果
        if (val === true) {
            this.shortcutBtnCurrentIndex = 'datePicker'
        }
        this.hasDateResult = val === true
    },
    immediate: true
},

shortcutBtnCurrentIndex就是用于控制页面上面的快捷与时间组件之间的回显的,如下图: 73b208a9e9204fb9979b361c16db552.png