釘釘小程式自定義年月日日期picker選擇器元件-日無限迴圈滾動

IFS_myki發表於2019-12-02

釘釘小程式有四個場景,一直在用的是企業內部應用開發. 釘釘小程式幾乎和微信小程式一樣,闊以說是搬了微信小程式過來,釘釘一些原生元件樣式和功能上並不能滿足我們的需求,比如釘釘小程式的picker原生元件: <picker mode="{{item.pickerData?'selector':'date'}}"..... mode為date時就是日期選擇器,ios和安卓展現形式並不一致 ios展現形式:

釘釘小程式自定義年月日日期picker選擇器元件-日無限迴圈滾動
安卓展現形式:
釘釘小程式自定義年月日日期picker選擇器元件-日無限迴圈滾動
可以看出來ios和安卓的日期選擇器展示形式並不一樣,並且這個原生picker元件的時分不能去除,當然選擇一個日期後我們使用了substring去掉了時分的展示,但是不能去掉彈窗裡面時分的顯示,可以看出來展現形式是比較醜的,應需求我們只能自定義一個年月日的picker日期選擇器。

先給大家看下自定義元件最後呈現的樣式(ios和安卓展現樣式一致):

釘釘小程式自定義年月日日期picker選擇器元件-日無限迴圈滾動
並且日的滾動可以無限迴圈,意思是向上滾動到1之後緊接著就有31、30、29,向下滾動到31後緊接著就有1、2、3,年月日picker日期選擇器幾乎符合設計的要求!

下面開始講解這個年月日picker自定義元件:

ai-multi-picker.axml

<view>
  <view class="picker-button-group">
    <text catchTap="cancelChoose">取消</text>
    <text catchTap="confirmChoose">確認</text>
  </view>
  <!-- 年月日 -->
  <picker-view value="{{initialValue}}" onChange="changeTime" a:if="{{type == 'days'}}">
    <picker-view-column>
      <view a:for="{{years}}">{{item}}</view>
    </picker-view-column>
    <picker-view-column>
      <view a:for="{{months}}">{{item}}</view>
    </picker-view-column>
    <picker-view-column>
      <view a:for="{{days}}">{{item}}</view>
    </picker-view-column>
  </picker-view>
  <!-- 年月 -->
  <picker-view value="{{initialValue}}" onChange="changeTime" a:else>
    <picker-view-column>
      <view a:for="{{years}}">{{item}}</view>
    </picker-view-column>
    <picker-view-column>
      <view a:for="{{months}}">{{item}}</view>
    </picker-view-column>
  </picker-view>
</view>
複製程式碼

ai-multi-picker.js

Component({
  mixins: [],
  data: {
    initMinDate: new Date(2010,1,0).getTime(),
    initMaxDate: new Date().getTime(),
    initialValue: [0, 0, 0],
    years: [],
    months: [],
    days: [],
    inintDaysArr: []
  },
  props: {
    type:'days', //展示型別 預設展示年月日 如只要月,則傳type為months
    format: "yyyy/MM/dd",//返回的格式要求 1.yyyy-MM-dd 2.yyyy-MM-dd hh:mm:ss 3.yyyy/MM/dd 4.yyyy年MM月dd日  5.yyyy年MM月  5.yyyy-MM
    value: new Date().getTime(),
    minDate: new Date(2010,1,0).getTime(),
    maxDate: new Date().getTime(),
    initialArr: [],
    onPickerChange: () => { },
    onHiddenMask: () => { },
  },
  didMount() {
    this.updateByValue();
  },
  didUpdate(prevProps, prevData) {
    if (prevProps.value != this.props.value) {
      this.updateByValue();
    }
  },
  didUnmount() { },
  methods: {
    updateByValue() {
      let value = new Date(this.props.value);
      let years = this.getYears();
      let months = this.getMonths(value.getFullYear());
      let days = this.getDays(value.getFullYear(), value.getMonth() + 1);
      //let inintDaysArr = this.data.inintDaysArr.length? this.data.inintDaysArr:this.getDays(value.getFullYear(), value.getMonth() + 1);
      let initialValue = [0, 0, 0];
      if(this.props.initialArr.length){
        initialValue = this.props.initialArr;
      }else{
        //最開始沒有initialArr值時,則計算出來
        initialValue[0] = years.indexOf(value.getFullYear());
        initialValue[1] = months.indexOf(value.getMonth() + 1);
        initialValue[2] = days.indexOf(value.getDate());
      }
      (days.length > 6) && days.unshift(...this.data.inintDaysArr);
      (days.length > 6) && days.push(...this.data.inintDaysArr);
      this.setData({
        years: years,
        months: months,
        days: days,
      }, () => {
        this.setData({
          initialValue: initialValue,
        })
      })
    },
    getYears() {
      let minDate = this.props.minDate?new Date(this.props.minDate): new Date(this.data.initMinDate);
      let maxDate = this.props.maxDate?new Date(this.props.maxDate): new Date(this.data.initMaxDate);
      let years = Array(maxDate.getFullYear() - minDate.getFullYear() + 1).fill(minDate.getFullYear()).map((x, y) => x + y);
      return years;
    },
    getMonths(year) {
      //let maxDate = new Date(this.props.maxDate);
      let minDate = this.props.minDate?new Date(this.props.minDate): new Date(this.data.initMinDate);
      let maxDate = this.props.maxDate?new Date(this.props.maxDate): new Date(this.data.initMaxDate);
      let maxMonth = (year == maxDate.getFullYear() ? (maxDate.getMonth() + 1) : 12);
      let months = Array(maxMonth).fill(1).map((x, y) => x + y);

      let minDateIndex = -1;
      //跟最小值的年對比
      if(year == minDate.getFullYear()){
        minDateIndex = months.indexOf(minDate.getMonth() + 1);
        months.splice(0, minDateIndex);
      }
      return months
    },
    getDays(year, month) {
      //month = this.getMonths(year)[month];
      let minDate = this.props.minDate?new Date(this.props.minDate): new Date(this.data.initMinDate);
      let maxDate = this.props.maxDate?new Date(this.props.maxDate): new Date(this.data.initMaxDate);
      let maxDay = (year == maxDate.getFullYear() && month == (maxDate.getMonth() + 1)) ? maxDate.getDate() : new Date(year, month, 0).getDate();
      let days = Array(maxDay).fill(1).map((x, y) => x + y);

      let minDateIndex = -1;
      //跟最小值的年對比
      if(year == minDate.getFullYear() && month == minDate.getMonth() + 1){
        minDateIndex = days.indexOf(minDate.getDate());
        days.splice(0, minDateIndex);
      }
      this.setData({
        inintDaysArr: this.deepCopy(days)
      })
      return days
    },
    changeTime(e) {
      let _valArr = e.detail.value;
      let months = this.getMonths(this.data.years[_valArr[0]]);
      if (_valArr[1] >= months.length) {
        _valArr[1] = months.length - 1;
      }
      let days = [];
      //let inintDaysArr = this.getDays(this.data.years[_valArr[0]], this.data.months[_valArr[1]]);
      if(_valArr[0] != this.data.initialValue[0] || (_valArr[1] != this.data.initialValue[1])){
        days = this.getDays(this.data.years[_valArr[0]], months[_valArr[1]]);
      }
      days = days.length ? days: this.data.days;
      if (_valArr[2] >= days.length) {
        _valArr[2] = days.length - 1;
      }
      //年和月change時才改變days,且展示的個數是7個
      if(days.length > 6){
        let isAddDays = (_valArr[0] != this.data.initialValue[0]) || (_valArr[1] != this.data.initialValue[1]);
        //如果年或者月變化時,日新增重複陣列資料,固定3個
        if(isAddDays){
          days.unshift(...this.data.inintDaysArr);
          days.push(...this.data.inintDaysArr);
          //以下代表日位於陣列第一個範圍內時比如10月1,因為在前面新增了一個陣列資料,所以日所在位置也新增一個陣列長度,視覺上前面還有值可選
          if(_valArr[2] < (days.length / 3)){
            _valArr[2] = _valArr[2] + days.length / 3;
          }
        }
        //如果日變化,就判斷是第一組還是第三組
        if(_valArr[2] != this.data.initialValue[2]){
          let initDays = days.length?days: this.data.days;
          let daysLen = initDays.length;
          let inintDaysArrLen = this.data.inintDaysArr.length;
          //代表第一組,則刪除第三組,新增到最前面,日所在位置對應新增一個陣列長度
          if(_valArr[2] < (daysLen / 3)){
            initDays.splice(daysLen - daysLen / 3,daysLen / 3);
            initDays.unshift(...this.data.inintDaysArr);
            days = initDays;
            _valArr[2] = _valArr[2] + inintDaysArrLen;
          }
          //代表第三組,則刪除第一組,新增到最後面,日所在位置對應減去一個陣列長度
          if(_valArr[2] > (daysLen / 3 * 2)){
            initDays.splice(0, daysLen / 3);
            initDays.push(...this.data.inintDaysArr);
            days = initDays;
            _valArr[2] = _valArr[2] - inintDaysArrLen;
          }
        }
      }
      this.setData({
        months: months,
        days: days.length?days: this.data.days,
        'initialValue': _valArr
      })
    },
    cancelChoose() {
      this.props.onHiddenMask();
    },
    confirmChoose(e) {
      let initialValue = this.data.initialValue;
      let _val = new Date(this.data.years[initialValue[0]], this.data.months[initialValue[1]] - 1, this.data.days[initialValue[2]]);
      let crtTime = this.dateFtt(this.props.format,_val);
      //第一個引數為固定格式的時間,第二個選中的index陣列,第三個時間戳
      this.props.onPickerChange(crtTime, initialValue, _val.getTime());
      this.props.onHiddenMask();
    },
    dateFtt(fmt,date) { //author: meizz   
      var o = {   
        "M+" : date.getMonth()+1,                 //月份   
        "d+" : date.getDate(),                    //日   
        "h+" : date.getHours(),                   //小時   
        "m+" : date.getMinutes(),                 //分   
        "s+" : date.getSeconds(),                 //秒   
        "q+" : Math.floor((date.getMonth()+3)/3), //季度   
        "S"  : date.getMilliseconds()             //毫秒   
      };   
      if(/(y+)/.test(fmt))   
        fmt=fmt.replace(RegExp.$1, (date.getFullYear()+"").substr(4 - RegExp.$1.length));   
      for(var k in o)   
        if(new RegExp("("+ k +")").test(fmt))   
      fmt = fmt.replace(RegExp.$1, (RegExp.$1.length==1) ? (o[k]) : (("00"+ o[k]).substr((""+ o[k]).length)));   
      return fmt;   
    },
    typeCheck(data) {
      let typeObj = {
        "[object String]": 'string',
        "[object Number]": 'number',
        "[object Boolean]": 'boolean',
        "[object Object]": 'object',
        "[object Null]": 'null',
        "[object Undefined]": 'undefined',
        "[object Symbol]": 'symbol',
        // 上面的是7種基本型別。下面的是自帶原生型別情況。
        "[object Function]": 'function',   // object
        "[object Date]": 'date',           // object  -- new Date() 出來的值。
        "[object Array]": 'array',         // object
        "[object RegExp]": 'regexp',       // object  正則的簡寫也同樣是object,有test,match這個方法。      
      };
      // 得到一個資料最原始的型別,直接去呼叫Object.prototype.toString()的方法。
      let strKey = Object.prototype.toString.call(data);  // 直接呼叫call方法,call為傳單個引數的值,apply傳入list;
      return typeObj[strKey];
    },
    deepCopy(data) {
      // 1.先進行資料的型別判斷
      let typeKey = this.typeCheck(data);
      // 2.只有兩種情況。 1種是這個資料是值型別,另一種是object.
      let res_data = "", list = [], obj = {};
      if (typeof data === 'object') { // 會有一個null的問題。  
        if (typeKey === 'object') {
          // 對像
          for (let o in data) {
            obj[o] = this.deepCopy(data[o])
          }
          return obj;
        } else if (typeKey === 'array') {
          // list;
          data.forEach(item => {
            list.push(this.deepCopy(item));
          });
          return list;
        } else {
          // 其它物件型別。eg: function, date, reg 是物件也是以值型別存在。
          return data
        }
      } else {
        // 基礎值型別--在這一個基礎值型別中只會執行一次。
        return data;
      }
    },
  },
});
複製程式碼

typeCheck和deepCopy方法可以寫在共用方法中去,這裡我直接寫在了js中,方便你們複製

怎麼使用年月日picker自定義元件?

index.axml

<ai-multi-picker a:if="{{!chooseData.datatimePopup && timeRange.timebegin.multiPickerType}}"
                   type="{{timeRange.timebegin.multiPickerType}}" 
                   format="{{timeRange.timebegin.format}}"
                   value="{{pickerValue}}" 
                   initialValue="{{initialValue}}" 
                   minDate="{{minDate}}"
                   maxDate="{{maxDate}}"
                   onHiddenMask="onHiddenMask" 
                   onPickerChange="pickerChange">
  </ai-multi-picker>
  <!-- 年月日選擇 -->
複製程式碼

index.json

{
  "titleBarColor": "#2639A0",
  "usingComponents": {
    "ai-multi-picker": "/components/ai-multi-picker/ai-multi-picker"
  }
}
複製程式碼

index.js

import store from '/store'
import create from '/scripts/westore/create'
import util from '/scripts/util.js'
import config from '/scripts/config.js'

const app = getApp();


create(store, {
  data: {
  },
  onReady() {
  },
  onLoad(query) {
  },
  onShow() {
    dd.hideLoading();
  },
  pickerChange(value, initialValue, timeStamp) {
    let rangePosition = this.data.timeRangeData.rangePosition;
    let timeType = rangePosition == 'startTime' ? 'timebegin' : 'timeend';
    this.store.data.timeRange[timeType].value = value;
    this.update();
    this.setData({
      weekValue: value,
      pickerValue: timeStamp,//時間戳
      initialValue: initialValue,
      [rangePosition]: value,
      timeType: timeType
    });
  },
  //range元件點選彈出picker
  onRangeChangeTime(timeRangeData) {
    let rangePosition = timeRangeData.rangePosition;
    let minOrMaxPicker = rangePosition == 'startTime' ? 'maxPicker' : 'minPicker';
    let minOrmaxDate = rangePosition == 'endTime' ? 'minPicker' : 'maxPicker';
    this.setData({
      pickerValue: new Date(timeRangeData[rangePosition]).getTime(),//時間戳
      weekValue: timeRangeData[rangePosition],
      timeRangeData: timeRangeData,
      maxPicker: minOrMaxPicker == 'maxPicker' ? timeRangeData[minOrMaxPicker] : '',
      minPicker: minOrMaxPicker == 'minPicker' ? timeRangeData[minOrMaxPicker] : '',
      minDate: rangePosition == 'endTime' ? new Date(timeRangeData[minOrmaxDate]).getTime() : '',
      maxDate: rangePosition == 'startTime' ? new Date(timeRangeData[minOrmaxDate]).getTime() : '',
      [`popupShow.bottom`]: true
    })
  },
});
複製程式碼

時間區間點選彈出自定義picker元件,年月日picker元件在時間區間選擇的時候具有開始時間不能晚於結束時間,結束時間不能早於開始時間的功能

著重說下日無限迴圈滾動的問題 請看ai-multi-picker.js中程式碼,大概思路是根據年月獲取days陣列,因為每個月的日陣列長度不同,2月只有28天,2019年12月有31天,所以picker的onChange觸發時,就去計算days的陣列,實時變化,當然滾動日的時候不需要變化;整體思路是在日陣列前後插入相同的日陣列,當滾動到在日第一陣列時,立馬把日的最後一個陣列刪除,並移到最前面,當滾動到日第三陣列時,立馬把日的最前面一個陣列刪除,並移動最後面,當前滾動的下標加上或者減去一個日陣列長度,造成視覺上的錯覺即可; 後續有什麼走不通的問題請評論區留言,謝謝

釘釘小程式自定義年月日日期picker選擇器元件-日無限迴圈滾動

相關文章