-.- -.-
首页
  • 分类
  • 标签
  • 归档
  • Lodash 源码分析 (opens new window)
Mozilla (opens new window)
GitHub (opens new window)

江月何年初照人

首页
  • 分类
  • 标签
  • 归档
  • Lodash 源码分析 (opens new window)
Mozilla (opens new window)
GitHub (opens new window)
  • 小程序时间选择控件封装

    • JS
      • HTML
        • css
        江月何年初照人
        2021-05-02
        随笔

        小程序时间选择控件封装

        # 小程序时间选择控件封装

        在工作中,有一个需求是产品想按照企业微信审批的时间选择来做我们的需求,故此简单封装了一个小程序的时间选择控件

        也是使用了微信原生的组件,保证了 安卓和iOS交互的统一性,具体如下

        # JS

        首先对公共的方法提取了 class

        /**
         *
         * @export
         * @class DateControl
         * @param options: { rangeYear : Number, startYear : Number}
         */
        export default class DateControl {
          constructor (now, options) {
            // 获取年份跨度以及起始年
            const {rangeYear, startYear} = options
            this.now = now
            this.years = Array.from(Array(rangeYear).keys(), x => x + startYear) 
            this.months = Array.from(Array(12).keys(), x => ++x)
            this.days = Array.from(Array(31).keys(), x => ++x)
          }
        
          // 判断是否闰年
          static isLeap(year) {
            if((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0){
              return true;
            }
            return false;
          }
          // 计算日期差值
          static differenceDays(start, end) {
            return (end - start) / (1000 * 60 * 60 * 24)
          }
        
          // 日期减法
          static subtractDate (date, n) {
            return new Date(date.setDate(date.getDate() - n))
          }
        
          // 月份补零  ios 不补零 new Date 会有问题
          static addZero(n) {
            return n.toString().padStart(2, '0')
          }
        
          // 返回和当前相比较小的日期
          static minDate (date, compare = new Date) {
            return new Date(Math.min(date, compare))
          }
        
          // 获取年
          static getYear (date) {
            return date.getFullYear()
          }
        
          static getMonth (date, type) {
            return type ? DateControl.addZero(date.getMonth() + 1) : date.getMonth() + 1
          }
        
          static getDate (date, type) {
            return type ? DateControl.addZero(date.getDate()) : date.getDate()
          }
          // 处理为合适的日期 不超过 now 对应的 月份 及 天数
          optionalDateControl (year = DateControl.getYear(this.now), month = DateControl.getMonth(this.now), day = DateControl.getDate(this.now)) {
            
            month = month >>> 0 // 保证为数字
            day = day >>> 0
            // 取出 30天的
            const _30 = [4,6,9,11].includes(month)
        
            const currentYear = DateControl.getYear(this.now) // 获取初始化时间的年份
            const currentMonth = DateControl.getMonth(this.now) // 月份
            const currentDay = DateControl.getDate(this.now) // 天
        
            // 判断是否闰年
            const isLeap = DateControl.isLeap(year)
        
            let days = _30 ? this.days.filter(d => d < 31) : this.days; // 处理 30天和31天的
        
            // 单独对2月进行判断
            if(month === 2) {
              days = isLeap ? this.days.filter(d => d < 30) : this.days.filter(d => d < 29)
            }
        
            // 取出当前和最大中的较小值  对已经选择的天做修正
            day = Math.min(day, days[days.length - 1]);
        
        
            // 处理月份
            // 当前年
            const isCurrentYear = year === currentYear; // 判断是否是当前年
        
            let months = this.months
        
            if (isCurrentYear) {
              months = this.months.filter(item => item <= currentMonth); // 如果是当前年,日期可选最大为当月,不可超过当前时间
              month = Math.min(month, months[months.length - 1]) // 对已经选择的月做修正
        
              if (month === currentMonth) { // 如果是当前月 还要对天数也做一个修正
                day = Math.min(day, currentDay);
                days = this.days.filter(d => d <= currentDay)
              }
            }
            
            return [months, days, year, month, day]
          }
        
          // type 为 0 表示需要增加 currentDate 应小于 comparedDate  为 1 表示减少 currentDate 应 大于 comparedDate
          // currentDate 为当前选中的日期  comparedDate 为需要对比的日期
          // rangeDays 表示二者相差的最大天数
          dateToRepair (currentDate, comparedDate, rangeDays, type = 0 ) {
            // 获取二者相差天数
            const differenceDays = DateControl.differenceDays(comparedDate, currentDate);
            // 根据 currentDate 获取到 当前日
            const currentDay = DateControl.getDate(currentDate)
            
            // 定义一个结果,但并不赋值
            let resultDate
        
            // 如果二者相差天数超过了最大限制
            if (Math.abs(differenceDays) > rangeDays) {
              // 拿到需要修正的天数
              const fixDays = type ? 0 - rangeDays : 0 + rangeDays
              // 根据 fixDays 对日期做加法,这里用减法,所以取负
              resultDate = DateControl.minDate(DateControl.subtractDate(currentDate, -fixDays), this.now)
            } else if (!type && differenceDays > 0) {
              // 当前应该为 currentDate 小于 comparedDate ,结果大于了,这里对进行加1天 作为修复数据
              resultDate = DateControl.minDate(DateControl.subtractDate(currentDate, -1), this.now)
            } else if(type && differenceDays < 0) {
              // 当前应为 currentDate 大于 comparedDate , 结果小于了,对其进行减一天作为修复数据
              resultDate = DateControl.minDate(DateControl.subtractDate(currentDate, 1), this.now)
            }
        
            // 最终,如果有值,则返回修复时间对应的 年月日,并且补零,否则返回 undefined
            return resultDate ? {
              repairedYear: DateControl.getYear(resultDate),
              repairedMonth: DateControl.getMonth(resultDate, true),
              repairedDays: DateControl.getDate(resultDate, true)
            } : undefined
          }
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        123
        124
        125
        126
        127
        128
        129
        130
        131
        132
        133
        134

        组件的 js

        /*
         * @Date: 2021-04-25 10:44:10
         * @LastEditTime: 2021-04-29 22:38:53
         * @Description: 时间选择控件,支持 开始时间和 结束时间选择
         *               1. 支持传入 range 指最大可选时间跨度,单位 年
         *               2. 支持传入 startTime 和 endTime 用作数据回显以及默认选中,如 结束时间超过了当前时间或者
         *                  结束时间 和 开始时间差值超过了最大可选时间跨度,会默认使用结束时间减去 最大可选时间跨度得到开始时间
         *               3. 如选择开始时间和已经存在的结束时间相差超过时间跨度,会同步修改结束时间
         *               4. 开始时间大于结束时间,会修改结束时间为 开始时间 + 1天
         *               5. 结束时间小于开始时间,会同步修改开始时间为 结束时间 - 1天
         *               6. 在所有时间超过当前时间时,会默认和当前时间比较,取最小
         *               7. 最终返回的日期为 年月日 组成的时间戳, 以数组形式返回, [start, end]
         *               8. 最大可选日期为当日,超过后会修正
         * 
         *               不支持
         *               1. 不支持传入默认时间配置,如有需要可自己复制代码实现业务需求
         *               2. 默认添加了一层保险,在最终返回时间时。会做一个判断,如果结束时间小于开始时间,会吐司提示,吐司提示内容不支持传入
         *               3. 时间控件还存在一些问题,目前为想好怎么处理
         *                    a. 在闰年的 2.29 号,切换年时 结束时间如果同步修改的话,会到 3.2 号,这是一个 bug
         *               4. 目前未支持 时间的筛选,只有日期的
         *               5. 目前未支持自定义颜色等
         *               6. 返回的日期格式 为 时间戳,默认不会添加时分秒的时间,为当日的 00:00:00
         * 
         * @FilePath: /digital-applet/components/datePicker/datePicker.js
         */
        
        import DateControl from './handler'
        import MessageBox from '../../utils/ui/messageBox'
        
        const self_control = new DateControl(new Date, {
          rangeYear: new Date().getFullYear() - 1989,
          startYear: 1990
        })
        
        const end_date = self_control.now
        const start_date = new Date(self_control.now.getTime() - 86400000)
        
        Component({
          properties: {
            range: {
              type: Number,
              value: 1
            },
            startTime: {
              type: Number,
              value: start_date.getTime()
            },
            endTime: {
              type: Number,
              value: end_date.getTime()
            },
            dataType: {
              type:  String,
              value: 0
            }
          },
          data: {
            years: self_control.years,
            start_year: '',
            end_year: '',
        
            months: self_control.months,
            start_month: '',
            end_month: '',
        
            days: self_control.days,
            start_day: '',
            end_day: '',
        
            value: [],
            type: 0,
            _range_days: 0
          },
          ready: function () {
            this._init()
          },
          methods: {
            // 初始化各种数据
            _init() {
              
              // 判断传入的值,是否超过时间跨度
              const _range_days = this.data.range * 365
              const _isError = DateControl.differenceDays(new Date(this.data.startTime), new Date(this.data.endTime)) > _range_days
        
              // 定义初始化开始时间对象
              const startTimeDate = _isError ? DateControl.subtractDate(new Date(this.data.endTime), _range_days) : new Date(this.data.startTime)
              const endTimeDate = new Date(this.data.endTime)
        
              // 获取开始时间年月日
              const start_year = DateControl.getYear(startTimeDate)
              const start_month = DateControl.getMonth(startTimeDate, true)
              const start_day = DateControl.getDate(startTimeDate, true)
        
              // 初始化数据 日期最大可选范围
              const [months, days] = self_control.optionalDateControl(start_year, start_month, start_day)
        
        
              this.setData({
                start_year,
                end_year: DateControl.getYear(endTimeDate),
                start_month,
                end_month: DateControl.getMonth(endTimeDate, true),
                start_day,
                end_day: DateControl.getDate(endTimeDate, true),
                value: [start_year - 1990, Number(start_month) - 1, Number(start_day) - 1],
                days,
                months,
                _range_days
              })
            },
            
            // 确认时间
            _confirm() {
              const _startText = `${this.data.start_year}/${this.data.start_month}/${this.data.start_day}`
              const _endText = `${this.data.end_year}/${this.data.end_month}/${this.data.end_day}`
              const startTime = new Date(`${_startText} 00:00:00`).getTime()
              const endTime = new Date(`${_endText} 00:00:00`).getTime()
        
              console.log(new Date(`${_startText} 00:00:00`))
              if (endTime - startTime < 0) {
                MessageBox.toast('开始时间不能大于结束时间')
                return
              }
              this.triggerEvent('confirm', {time: [startTime, endTime]}, {})
            },
            // 切换开始和结束
            _tabChange(event) {
              const type = +event.currentTarget.dataset.type;
              const prefix = type ? 'end_' : 'start_';
              this.setData({
                type,
                value: [this.data[`${prefix}year`] - 1990, this.data[`${prefix}month`] - 1, this.data[`${prefix}day`] - 1]
              })
            },
        
            _bindChange: function (e) {
              const val = e.detail.value
        
              let year = this.data.years[val[0]];
              let month = this.data.months[val[1]];
              let day = this.data.days[val[2]];
        
              const [months, days, _year, _month, _day] = self_control.optionalDateControl(year, month, day)
        
              let fix_prefix;
              const prefix = this.data.type ? (fix_prefix = 'start_', 'end_') : ( fix_prefix = 'end_', 'start_');
        
               // 判断是否闰年
              const _isLeap = DateControl.isLeap(_year)
        
              const _temp_day = DateControl.addZero((!_isLeap && _month === 2 && _day > 28) ? 28 : _day)
        
              const _temp = new Date(`${_year}-${DateControl.addZero(_month)}-${_temp_day}`); // 本次选中的时间的日期对象
        
              const fix_date = new Date(`${this.data[`${fix_prefix}year`]}-${this.data[`${fix_prefix}month`]}-${this.data[`${fix_prefix}day`]}`); // 需要对比的时间日期对象
        
              const result = self_control.dateToRepair(_temp, fix_date, this.data._range_days, this.data.type)
        
              if (result) {
                this.setData({
                  [`${fix_prefix}year`]: result.repairedYear,
                  [`${fix_prefix}month`]: result.repairedMonth,
                  [`${fix_prefix}day`]: result.repairedDays
                })
              }
              // 更新数据
              this.setData({
                [`${prefix}year`]: _year,
                [`${prefix}month`]: DateControl.addZero(_month), 
                [`${prefix}day`]: DateControl.addZero(_day),
                days,
                months
              })
            }
          }
        })
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        78
        79
        80
        81
        82
        83
        84
        85
        86
        87
        88
        89
        90
        91
        92
        93
        94
        95
        96
        97
        98
        99
        100
        101
        102
        103
        104
        105
        106
        107
        108
        109
        110
        111
        112
        113
        114
        115
        116
        117
        118
        119
        120
        121
        122
        123
        124
        125
        126
        127
        128
        129
        130
        131
        132
        133
        134
        135
        136
        137
        138
        139
        140
        141
        142
        143
        144
        145
        146
        147
        148
        149
        150
        151
        152
        153
        154
        155
        156
        157
        158
        159
        160
        161
        162
        163
        164
        165
        166
        167
        168
        169
        170
        171
        172
        173
        174
        175
        176

        # HTML

        <view>
          <view class="header">
            <view class="btns">
              <view class="btn {{!type ? 'active' : ''}}" data-type="0" catchtap="_tabChange">
                <view>开始</view>
                <text>{{start_year}}年{{start_month}}月{{start_day}}日</text>
              </view>
              <view class="btn  {{type ? 'active' : ''}}" data-type="1" catchtap="_tabChange">
                <view>结束</view>
                <text>{{end_year}}年{{end_month}}月{{end_day}}日</text>
              </view>
            </view>
            <view data-type="{{dataType}}" catchtap="_confirm" class="text">确定</view>
          </view>
          <picker-view indicator-class="active-date" style="width: 100%; height: 300px;background:#fff;" value="{{value}}" bindchange="_bindChange">
            <picker-view-column>
              <view wx:for="{{years}}" wx:key="*this" class="date-item">{{item}}年</view>
            </picker-view-column>
            <picker-view-column>
              <view wx:for="{{months}}" wx:key="*this" class="date-item">{{item}}月</view>
            </picker-view-column>
            <picker-view-column>
              <view wx:for="{{days}}" wx:key="*this" class="date-item">{{item}}日</view>
            </picker-view-column>
          </picker-view>
        </view>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26

        # css

        .intro {
          margin: 30px;
          text-align: center;
        }
        .header{
          font-size: 24rpx;
          background:#fff;
          display: flex;
          height: 100rpx;
          align-items: center;
          border-radius: 8rpx 8rpx 0 0;
          border-bottom: 1px solid #eee;
          box-sizing: border-box;
          padding: 12rpx 24rpx;
        }
        
        
        .header .btns{
          flex: 1;
          display: flex;
          margin-right: 80rpx;
          align-items: center;
          box-sizing: border-box;
          overflow: hidden;
          border-radius:8rpx;
        }
        
        .header .btns .btn{
          width: 220rpx;
          box-sizing: border-box;
          padding-left:24rpx;
          color:#219AFF;
          border:1px solid #219AFF;
        }
        .header .btns .btn.active{
          background:#219AFF;
          color:#fff;
        }
        
        .header .text{
          flex:0 0 166rpx;
          height:100%;
          text-align: center;
          font-size: 28rpx;
          line-height:76rpx;
          color:#219AFF;
        }
        
        .active-date{
          height: 110rpx;
        }
        
        .date-item{
          line-height: 88rpx;
          text-align: center;
          font-size:28rpx;
        }
        
        .active-date .date-item{
          font-size: 32rpx;
          line-height: 110rpx;
        }
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        编辑 (opens new window)
        上次更新: 2021/05/26 14:18:07
        最近更新
        01
        a标签下载限制
        08-08
        02
        必应每日一图
        05-27
        03
        小球碰壁反弹动画
        05-26
        更多文章>
        Theme by Vdoing | Copyright © 2021-2023 Himawari | MIT License
        • 跟随系统
        • 浅色模式
        • 深色模式
        • 阅读模式