钉钉小程序有四个场景,一直在用的是企业内部应用开发. 钉钉小程序几乎和微信小程序一样,阔以说是搬了微信小程序过来,钉钉一些原生组件样式和功能上并不能满足我们的需求,比如钉钉小程序的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 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的数组,实时变化,当然滚动日的时候不需要变化;整体思路是在日数组前后插入相同的日数组,当滚动到在日第一数组时,立马把日的最后一个数组删除,并移到最前面,当滚动到日第三数组时,立马把日的最前面一个数组删除,并移动最后面,当前滚动的下标加上或者减去一个日数组长度,造成视觉上的错觉即可; 后续有什么走不通的问题请评论区留言,谢谢