釘釘小程式有四個場景,一直在用的是企業內部應用開發. 釘釘小程式幾乎和微信小程式一樣,闊以說是搬了微信小程式過來,釘釘一些原生元件樣式和功能上並不能滿足我們的需求,比如釘釘小程式的picker原生元件: <picker mode="{{item.pickerData?'selector':'date'}}"..... mode為date時就是日期選擇器,ios和安卓展現形式並不一致 ios展現形式:
安卓展現形式: 可以看出來ios和安卓的日期選擇器展示形式並不一樣,並且這個原生picker元件的時分不能去除,當然選擇一個日期後我們使用了substring去掉了時分的展示,但是不能去掉彈窗裡面時分的顯示,可以看出來展現形式是比較醜的,應需求我們只能自定義一個年月日的picker日期選擇器。先給大家看下自定義元件最後呈現的樣式(ios和安卓展現樣式一致):
並且日的滾動可以無限迴圈,意思是向上滾動到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的陣列,實時變化,當然滾動日的時候不需要變化;整體思路是在日陣列前後插入相同的日陣列,當滾動到在日第一陣列時,立馬把日的最後一個陣列刪除,並移到最前面,當滾動到日第三陣列時,立馬把日的最前面一個陣列刪除,並移動最後面,當前滾動的下標加上或者減去一個日陣列長度,造成視覺上的錯覺即可; 後續有什麼走不通的問題請評論區留言,謝謝