纯手写时间间隔组件
需求:小程序中可以根据时间段进行选择开始时间和结束时间,如:当前时间是09:00,
则我可以从9点开始选择时间,每半个小时为间隔,那么下一个时间就算9:30,10:00,依次类推
就像element-ui中有个时间选择器就可以根据自己设置的时间间隔去选择,可以0.5小时,1小时或者2.5小时的间隔进行选择
由于公司小程序用的是vant-ui来开发的,我去找vant的时间组件,发现没有这样的,所以决定自己开发一下这个组件
!!!注意:
由于公司用的是vant,所以弹窗按钮用的都是我们用vant自己封装的组件,
在这里就不展示弹窗组件还有按钮了,弹窗和按钮就得大家自己用自己的框架去显示了
看效果
间隔一小时 提前30分钟和推迟30分钟 当前时间10:51
创建 time-interval组件 如果你想全局使用则在app.json的usingComponents中添加你得组件地址
"usingComponents": {"time-interval": "/components/time-interval/time-interval"}
创建好后就开始编码 HTML部分
< import src = " ../../static/templates/template.wxml" > </ import>
< wxs src = " ./util.wxs" module = " computed" />
< custom-popup
customStyle = " width: 100%;max-height: calc(100% - 148rpx); overflow: hidden; border-radius: 24rpx 24rpx 0 0;"
hidden = " {{!isShowTime}}" isShowPopup = " {{isShowTime}}" title = " {{title}}"
isCloseOnClickOverlay = " {{isCloseOnClickOverlay}}" bindhidePopup = " hidePopup" bind: titleTap= " titleTap" >
< view class = " w-time_container" >
< view class = " w-t_left" data-type = " startOptions" bind: touchstart= " onTouchStart" catch: touchmove= " onTouchMove"
bind: touchend= " onTouchEnd" bind: touchcancel= " onTouchEnd" >
< view
style = " { { computed.wrapperStyle( { offset : startOptions.offset, itemHeight, visibleItemCount, duration : startOptions.duration } ) } } " >
< view class = " w-time_row {{index===startOptions.currentIndex?'w-check_box':''}}"
style = " height: { { itemHeight } } px" wx: for= " {{timeList}}" wx: key= " index" data-item = " {{item}}"
data-index = " {{ index }}" data-type = " startOptions" bind: tap= " onClickItem" >
{{item.time}}
</ view>
</ view>
</ view>
< view class = " w-t_right" data-type = " endOptions" bind: touchstart= " onTouchStart" catch: touchmove= " onTouchMove"
bind: touchend= " onTouchEnd" bind: touchcancel= " onTouchEnd" >
< view
style = " { { computed.wrapperStyle( { offset : endOptions.offset, itemHeight, visibleItemCount, duration : endOptions.duration } ) } } " >
< view
class = " w-time_row {{index===endOptions.currentIndex?'w-check_box':''}} {{startTime.timeStamp>=item.timeStamp?'w-disabled':''}}"
wx: for= " {{timeList}}" wx: key= " index" data-item = " {{item}}" data-index = " {{ index }}"
data-type = " endOptions" bind: tap= " {{startTime.timeStamp>=item.timeStamp?'':'onClickItem'}}" >
{{item.time}}
</ view>
</ view>
</ view>
< view class = " w-pick_mask" style = " background-size : 100% 40%; " > </ view>
</ view>
< template is = " iconBtn" data = " {{btnText: '确定', btnTap: 'confirmTime'}}" > </ template>
</ custom-popup>
js部分
function getCurrentDate ( param = 's' , target = '' ) {
var now = target ? new Date ( toIosDate ( target) ) : new Date ( ) ;
var year = now. getFullYear ( ) ;
var month = now. getMonth ( ) ;
var date = now. getDate ( ) ;
var day = now. getDay ( ) ;
var hour = now. getHours ( ) ;
var minu = now. getMinutes ( ) ;
var sec = now. getSeconds ( ) ;
month = month + 1 ;
if ( month < 10 ) month = "0" + month;
if ( date < 10 ) date = "0" + date;
if ( hour < 10 ) hour = "0" + hour;
if ( minu < 10 ) minu = "0" + minu;
if ( sec < 10 ) sec = "0" + sec;
const arr = {
'Y' : year,
'M' : year + "-" + month,
'D' : year + "-" + month + "-" + date,
'h' : year + "-" + month + "-" + date + " " + hour,
'm' : year + "-" + month + "-" + date + " " + hour + ":" + minu,
's' : year + "-" + month + "-" + date + " " + hour + ":" + minu + ":" + sec
}
return {
year,
month,
day : date,
hour,
minu,
sec,
date : arr[ param]
} ;
}
function range ( num, min, max ) {
return Math. min ( Math. max ( num, min) , max) ;
}
function isObj ( x ) {
const type = typeof x;
return x !== null && ( type === 'object' || type === 'function' ) ;
}
const DEFAULT_DURATION = 200 ;
let app = getApp ( ) ;
let isIOS = / ios / ig . test ( app. globalData. systemInfo. system) ;
function toIosDate ( date ) {
return isIOS ? date. replace ( / - / g , '/' ) : date
}
Component ( {
options : {
addGlobalClass : true ,
styleIsolation : "apply-shared"
} ,
properties : {
isShowTime : {
type : Boolean,
value : false ,
observer : 'initArr'
} ,
title : {
type : String,
value : '选择时间'
} ,
isCloseOnClickOverlay : {
type : Boolean,
value : false
} ,
intervalTime : {
type : Number,
value : 1 / 60 ,
} ,
hasDelay : {
type : Number,
value : 0 ,
} ,
curDate : {
type : String,
valeu : getCurrentDate ( 'D' ) . date
} ,
visibleItemCount : {
type : Number,
value : 6
} ,
itemHeight : {
type : Number,
value : 44
}
} ,
data : {
startTime : { } ,
endTime : { } ,
timeList : [ ] ,
startOptions : {
startY : 0 ,
startOffset : 0 ,
duration : 0 ,
offset : 0 ,
currentIndex : 0
} ,
endOptions : {
startY : 0 ,
startOffset : 0 ,
duration : 0 ,
offset : 0 ,
currentIndex : 0
}
} ,
methods : {
initArr ( nv, ov ) {
if ( nv) {
const {
date,
hour,
minu,
} = getCurrentDate ( 'D' )
const w_date = this . properties. curDate ? ( date === this . properties. curDate ? '' : ` ${ this . properties. curDate} 00:00 ` ) : ` ${ date} ${ hour} : ${ minu} `
this . setData ( {
timeList : this . createTime ( getCurrentDate ( 'D' , w_date) , this . properties. intervalTime)
} )
this . setIndex ( 0 , 'startOptions' ) ;
this . setIndex ( 1 , 'endOptions' ) ;
}
} ,
createTime ( target, h = 1 ) {
const {
hour,
minu,
date
} = target
const e_date = this . getRecentDate ( 1 , this . properties. curDate)
let arr = [ ]
let startStamp = new Date ( toIosDate ( date + ` ${ hour} : ${ minu} ` ) ) . getTime ( )
if ( Math. abs ( this . properties. hasDelay) > 0 ) {
startStamp = startStamp + this . properties. hasDelay* 60 * 1000
}
const endStamp = new Date ( toIosDate ( e_date + ' 00:00' ) ) . getTime ( )
for ( let i = startStamp; i < endStamp; i += ( h * 60 * 60 * 1000 ) ) {
const res = this . formatDate ( i)
arr. push ( {
time : res. time,
date : res. date,
dateM : res. dateM,
timeStamp : i,
disabled : false
} )
}
return arr
} ,
confirmTime ( ) {
if ( ! this . data. endTime. timeStamp) {
return wx. showToast ( {
title : '请选择结束时间' ,
icon : 'none' ,
} )
}
this . triggerEvent ( 'chooseInterver' , {
startTime : this . data. startTime,
endTime : this . data. endTime
} )
} ,
formatDate ( target ) {
const date = new Date ( target)
let year = date. getFullYear ( ) ;
let months = date. getMonth ( ) + 1 ;
let month = ( months < 10 ? '0' + months : months) . toString ( ) ;
let day = date. getDate ( ) < 10 ? '0' + date. getDate ( ) : date. getDate ( ) ;
let hours = date. getHours ( ) < 10 ? '0' + date. getHours ( ) : date. getHours ( ) ;
let min = date. getMinutes ( ) < 10 ? '0' + date. getMinutes ( ) : date. getMinutes ( ) ;
let sec = date. getSeconds ( ) < 10 ? '0' + date. getSeconds ( ) : date. getSeconds ( ) ;
return {
year : year. toString ( ) ,
month,
day,
hours,
min,
sec,
time : hours + ":" + min,
date : year + "-" + month + "-" + day + " " + hours + ":" + min,
dateM : year + "-" + month + "-" + day
}
} ,
getRecentDate ( day, target ) {
var date1 = target ? new Date ( toIosDate ( target) ) : new Date ( ) ,
time1 = date1. getFullYear ( ) + "-" + ( date1. getMonth ( ) + 1 ) + "-" + date1. getDate ( ) ;
var date2 = new Date ( date1) ;
date2. setDate ( date1. getDate ( ) + day) ;
const y = date2. getFullYear ( ) ;
const m = ( date2. getMonth ( ) + 1 ) > 9 ? ( date2. getMonth ( ) + 1 ) : '0' + ( date2. getMonth ( ) + 1 )
const d = date2. getDate ( ) > 9 ? date2. getDate ( ) : '0' + date2. getDate ( )
let h = date2. getHours ( ) < 10 ? '0' + date2. getHours ( ) : date2. getHours ( ) ;
let n = date2. getMinutes ( ) < 10 ? '0' + date2. getMinutes ( ) : date2. getMinutes ( ) ;
let s = date2. getSeconds ( ) < 10 ? '0' + date2. getSeconds ( ) : date2. getSeconds ( ) ;
return y + "-" + m + "-" + d;
} ,
hidePopup ( ) {
this . setData ( {
[ ` startOptions.startY ` ] : 0 ,
[ ` startOptions.startOffset ` ] : 0 ,
[ ` startOptions.duration ` ] : 0 ,
[ ` startOptions.currentIndex ` ] : 0 ,
[ ` endOptions.startY ` ] : 0 ,
[ ` endOptions.startOffset ` ] : 0 ,
[ ` endOptions.duration ` ] : 0 ,
[ ` endOptions.currentIndex ` ] : 0 ,
} )
this . triggerEvent ( 'close' )
} ,
titleTap ( ) {
this . triggerEvent ( 'titleTap' )
} ,
getCount ( ) {
return this . data. timeList. length;
} ,
onTouchStart ( event ) {
const options = event. currentTarget. dataset. type
this . setData ( {
[ ` ${ options} .startY ` ] : event. touches[ 0 ] . clientY,
[ ` ${ options} .startOffset ` ] : this . data[ options] . offset,
[ ` ${ options} .duration ` ] : 0 ,
} ) ;
} ,
onTouchMove ( event ) {
const options = event. currentTarget. dataset. type
const deltaY = event. touches[ 0 ] . clientY - this . data[ options] . startY;
this . setData ( {
[ ` ${ options} .offset ` ] : range (
this . data[ options] . startOffset + deltaY,
- ( this . getCount ( ) * this . properties. itemHeight) ,
this . properties. itemHeight
) ,
} ) ;
} ,
onTouchEnd ( event ) {
const options = event. currentTarget. dataset. type
if ( this . data[ options] . offset !== this . data[ options] . startOffset) {
this . setData ( {
[ ` ${ options} .duration ` ] : DEFAULT_DURATION
} ) ;
const index = range (
Math. round ( - this . data[ options] . offset / this . data. itemHeight) ,
0 ,
this . getCount ( ) - 1
) ;
this . setIndex ( index, options) ;
}
} ,
onClickItem ( event ) {
const options = event. currentTarget. dataset. type
const {
index
} = event. currentTarget. dataset;
this . setIndex ( index, options) ;
} ,
setIndex ( index, options ) {
const {
data
} = this ;
index = this . adjustIndex ( index) || 0 ;
const offset = - index * this . properties. itemHeight;
if ( index !== data[ options] . currentIndex) {
this . setData ( {
[ ` ${ options} .offset ` ] : offset,
[ ` ${ options} .currentIndex ` ] : index,
[ ` ${ options=== 'startOptions' ? 'startTime' : 'endTime' } ` ] : this . data. timeList[ index]
} )
if ( options === 'endOptions' ) {
if ( this . data. startTime. timeStamp < this . data. timeList[ index] . timeStamp) {
this . setData ( {
endTime : this . data. timeList[ index]
} )
} else {
this . setData ( {
endTime : { }
} )
}
} else {
this . setData ( {
endTime : { }
} )
}
} else {
this . setData ( {
[ ` ${ options} .offset ` ] : offset,
[ ` ${ options=== 'startOptions' ? 'startTime' : 'endTime' } ` ] : this . data. timeList[ index]
} ) ;
}
} ,
adjustIndex ( index ) {
const count = this . getCount ( ) ;
index = range ( index, 0 , count) ;
for ( let i = index; i < count; i++ ) {
if ( ! this . isDisabled ( this . data. timeList[ i] ) ) return i;
}
for ( let i = index - 1 ; i >= 0 ; i-- ) {
if ( ! this . isDisabled ( this . data. timeList[ i] ) ) return i;
}
} ,
isDisabled ( option ) {
return isObj ( option) && option. disabled;
} ,
}
} )
css 部分
.w-time_container {
position : relative;
height : 600rpx;
width : 100%;
padding : 8rpx;
box-sizing : border-box;
display : flex;
}
.w-t_left,
.w-t_right {
width : 50%;
height : 100%;
display : flex;
padding : 48rpx 0;
flex-direction : column;
align-items : center;
box-sizing : border-box;
overflow : hidden;
}
.w-pick_mask {
position : absolute;
top : 0;
left : 0;
z-index : 1;
width : 100%;
height : 100%;
background-image : linear-gradient ( 180deg, hsla ( 0, 0%, 100%, 0.9) , hsla ( 0, 0%, 100%, 0.4) ) , linear-gradient ( 0deg, hsla ( 0, 0%, 100%, 0.9) , hsla ( 0, 0%, 100%, 0.4) ) ;
background-repeat : no-repeat;
background-position : top, bottom;
transform : translateZ ( 0) ;
pointer-events : none;
}
.w-time_row {
width : 100%;
display : flex;
align-items : center;
justify-content : center;
font-size : 32rpx;
font-weight : 600;
box-sizing : border-box;
}
.w-check_box {
color : #264AFF;
}
.w-disabled {
color : #0A1B33B3;
}
工具类util.wxs
function addUnit ( value ) {
if ( value == null ) {
return undefined ;
}
var REGEXP1 = getRegExp ( '^-?\d+(\.\d+)?$' ) ;
return REGEXP1 . test ( '' + value) ? value + 'px' : value;
}
var REGEXP2 = getRegExp ( '{|}|"' , 'g' ) ;
function keys ( obj ) {
return JSON . stringify ( obj)
. replace ( REGEXP2 , '' )
. split ( ',' )
. map ( function ( item ) {
return item. split ( ':' ) [ 0 ] ;
} ) ;
}
function kebabCase ( word ) {
var newWord = word
. replace ( getRegExp ( "[A-Z]" , 'g' ) , function ( i ) {
return '-' + i;
} )
. toLowerCase ( )
return newWord;
}
function isArray ( array ) {
return array && array. constructor === 'Array' ;
}
function style ( styles ) {
if ( isArray ( styles) ) {
return styles
. filter ( function ( item ) {
return item != null && item !== '' ;
} )
. map ( function ( item ) {
return style ( item) ;
} )
. join ( ';' ) ;
}
if ( 'Object' === styles. constructor) {
return keys ( styles)
. filter ( function ( key ) {
return styles[ key] != null && styles[ key] !== '' ;
} )
. map ( function ( key ) {
return [ kebabCase ( key) , [ styles[ key] ] ] . join ( ':' ) ;
} )
. join ( ';' ) ;
}
return styles;
}
function wrapperStyle ( data ) {
var offset = addUnit (
data. offset + ( data. itemHeight * ( data. visibleItemCount - 1 ) ) / 2
) ;
return style ( {
transition : 'transform ' + data. duration + 'ms' ,
'line-height' : addUnit ( data. itemHeight) ,
transform : 'translate3d(0, ' + offset + ', 0)' ,
} ) ;
}
module. exports = {
wrapperStyle : wrapperStyle,
} ;
Attributes
参数 说明 类型 可选值 默认值 intervalTime 间隔时间(单位小时) Number 1/60(一分钟);30/60(30分钟); 1(1小时);2.5(两个半小时) 1/60 hasDelay 是否是推迟还是提前多少分钟 提前则大于0,推迟则小于0,且不能大于一天 Number 10(提前10分钟);-10(推迟10分钟) 0 curDate 选择的日期,默认当天的日期 String 2023-02-11或 2023-02-11 09:00:00 当天时间
Events
参数 说明 类型 参数 返回值 chooseInterver 选择时间结果 Function - {starTime:resultTime,endTime:resultTime}
resultTime
prop 说明 date 日期+时分 2023-09-13 09:33 dateM 日期没有时分 2023-09-13 time 选择的时分 09:33 timeStamp 选择date的时间戳 1694568780000
看过vant源码的会发现 我这里会有一部分vant的代码,确实是,我用了一下vant的时间选择器的样式代码,没办法时间紧迫写的样式达不到那种丝滑,只能凑合用