开发微信小程序过程中,有个需求需要用到
日期时间筛选器
,查看微信官方文档后,发现官方文档的picker筛选器只能单独支持日期或者是时间,所以为了实现需求自己参考企业微信封装了个日期时间筛选器组件。
写在前面!!! 由于较多朋友有源码需求,我抽了个时间整理了一下代码,并对一年前的代码进行了升级和优化,并放在我github上,有需要的自取:
微信小程序日期时间选择器
原理
筛选器的实现,我参考的是小程序官方方式,通过半屏弹窗(mp-half-screen-dialog)结合picker_view进行日期、时间的选择,最终在选择时间后,通过事件触发返回一个change事件,其中参数值为毫秒级时间戳。需要注意的是,由于组件样式需要,对mp-half-screen-dialog做了一定的样式改造,并且为了不影响全局的mp-half-screen-dialog样式,使用指定的extClass进行样式控制,样式具体实现代码在app.wxss中。
实现
1.弹窗的显隐:
在组件的
properties
中传入一个
show
字段,用于控制弹窗的显隐;默认值为
false
。
2.开始时间,时间戳:
毫秒级时间戳,非必填,默认为,距现在一年前的时间。
3.结束时间,时间戳:
毫秒级时间戳,非必填,默认为,距现在一年后的时间。
具体思路
:整体思路是监听弹窗的显隐(show),当弹窗显示时,获取对应配置项(
类型、年、月
)计算对应筛选器的范围,并初始化默认日期时间为
当前年月日时分
。
以下是效果图以及具体代码实现:
wxml代码
<!--components/date-time-picker.wxml-->
<mp-half-screen-dialog show="{{ show }}" maskClosable="{{ false }}" closabled="{{ false }}" extClass="date-time-dialog" catchtouchmove="preventTouchMove" bindclose="bindclose">
<view slot="desc" class="date-time-container">
<view class="date-time-body">
<!-- 日期 -->
<view class="selector-panel selector-panel-date">
<picker-view indicator-style="height: 55px;" mode="selector" value="{{ [dateIndex] }}" class="selector-picker" bindchange="dateChange" bindpickstart="bindpickstart" bindpickend="bindpickend">
<picker-view-column>
<view wx:for="{{ dateArr }}" wx:key="index" class="selector-item selector-item-date">{{ item.name }}</view>
</picker-view-column>
</picker-view>
</view>
<!-- 小时 -->
<view class="selector-panel">
<picker-view indicator-style="height: 55px;" mode="selector" value="{{ [hourValue] }}" class="selector-picker" bindchange="hourChange" bindpickstart="bindpickstart" bindpickend="bindpickend">
<picker-view-column>
<view wx:for="{{ hourArr }}" wx:key="index" class="selector-item">{{ item }}</view>
</picker-view-column>
</picker-view>
</view>
<!-- 分钟 -->
<view class="selector-panel">
<picker-view indicator-style="height: 55px;" mode="selector" value="{{ [minValue] }}" class="selector-picker" bindchange="minChange" bindpickstart="bindpickstart" bindpickend="bindpickend">
<picker-view-column>
<view wx:for="{{ minArr }}" wx:key="index" class="selector-item">{{ item }}</view>
</picker-view-column>
</picker-view>
</view>
</view>
<view class="date-time-oper">
<button type="default" class="weui-btn oper-btn" bindtap="bindclose">取消</button>
<button type="primary" class="weui-btn oper-btn" bindtap="handleSubmit" disabled="{{ disabled }}">确定</button>
</view>
</view>
</mp-half-screen-dialog>
js代码
const utils = require('../utils/util')
const yearTimestamp = 365 * 24 * 60 * 60 * 1000 // 一年
Component({
/**
* 组件的属性列表
*/
properties: {
show: {
type: Boolean,
value: false,
observer: '_showChange'
},
// 开始时间,时间戳
startTime: {
type: Number,
value: +new Date() - yearTimestamp // 默认,一年前
},
// 结束时间,时间戳
endTime: {
type: Number,
value: +new Date() + yearTimestamp // 默认,一年后
}
},
/**
* 组件的初始数据
*/
data: {
disabled: false, // button disable态
dateIndex: null,
hourValue: '',
minValue: '',
dateArr: [],
hourArr: [],
minArr: [],
activeTime: null // 抛出的时间戳
},
/**
* 组件的方法列表
*/
methods: {
_showChange (e) {
if (e) {
this.getDateTimeData()
this.initData()
}
},
handleSubmit () {
this.bindclose()
this.triggerEvent('change', { value: this.data.activeTime })
},
/**
* 处理日期时间初始值
*/
initData () {
if (!this.data.activeTime) {
const now = +new Date()
if (now >= this.data.startTime && now <= this.data.endTime) {
const index = this.data.dateArr.map(v => v.name).indexOf(utils.formatTime(now, '{m}月{d}日周{a}'))
let tmp = this.data.dateArr
tmp[index].name = '今天'
this.setData({
dateIndex: index,
hourValue: utils.formatTime(now, '{h}'),
minValue: utils.formatTime(now, '{i}'),
dateArr: tmp,
activeTime: now
})
}
}
},
/**
* 获取日期时间数据,思路:获取固定的小时、分钟数组,将开始时间、结束时间进行年月天对比,取其差值,从而形成对应日期数组
*/
getDateTimeData () {
const mins = []
const hours = []
let dates = []
// 获取小时、分钟数组
for (let i = 0; i < 60; i++) {
mins.push(i.toString().length < 2 ? '0' + i : i.toString())
}
for (let j = 0; j < 24; j++) {
hours.push(j.toString().length < 2 ? '0' + j : j.toString())
}
// 开始时间需小于结束时间
if (this.data.startTime >= this.data.endTime) {
console.log('error_params_not_valid')
return
}
// 获取日期数组
dates = this.getDateData()
this.setData({
hourArr: hours,
minArr: mins,
dateArr: dates
})
},
getDateData () {
const startDate = new Date(this.data.startTime)
const endDate = new Date(this.data.endTime)
const nowYear = new Date().getFullYear()
const startYear = startDate.getFullYear()
const endYear = endDate.getFullYear()
const startMonth = startDate.getMonth()
const endMonth = endDate.getMonth()
const startDay = startDate.getDate()
const endDay = endDate.getDate()
const dateArr = []
// 处理逻辑:判断年限是否相同;若同年则判断是否同月,同月则直接添加日期;不同月则从开始时间月份处理,同时处理月日边界问题;以此类推...
let time = 0
// 同年
if (startYear === endYear) {
// 同月
if (startMonth === endMonth) {
for (let day = startDay; day <= endDay; day++) {
time = new Date(startYear, startMonth, day)
dateArr.push({ name: startYear === nowYear ? utils.formatTime(time, '{m}月{d}日周{a}') : utils.formatTime(time, '{y}年{m}月{d}日周{a}'), value: time })
}
} else {
// 不同月
let tmpDay = startDay
let lastMonth = endMonth
for (let month = startMonth; month <= lastMonth; month++) {
// 默认获取的月份方法得到月份值是0-11,该方法获取天数的月份值是1-12
const days = month === lastMonth ? endDay : new Date(startYear, month + 1, 0).getDate()
for (let day = tmpDay; day <= days; day++) {
time = new Date(startYear, month, day)
dateArr.push({ name: startYear === nowYear ? utils.formatTime(time, '{m}月{d}日周{a}') : utils.formatTime(time, '{y}年{m}月{d}日周{a}'), value: time })
// 处理边界(日)
day === days && (tmpDay = 1)
}
}
}
} else {
// 不同年
let tmpDay = startDay
let tmpMonth = startMonth
let lastMonth = 11
for (let year = startYear; year <= endYear; year++) {
for (let month = tmpMonth; month <= lastMonth; month++) {
// 默认获取的月份方法得到月份值是0-11,该方法获取天数的月份值是1-12
const days = (month === lastMonth && year === endYear) ? endDay : new Date(startYear, month + 1, 0).getDate()
for (let day = tmpDay; day <= days; day++) {
time = +new Date(year, month, day)
dateArr.push({ name: year === nowYear ? utils.formatTime(time, '{m}月{d}日周{a}') : utils.formatTime(time, '{y}年{m}月{d}日周{a}'), value: time })
// 处理边界(日)
day === days && (tmpDay = 1)
}
// 处理边界(月)
month === lastMonth && (tmpMonth = 0)
month === lastMonth && year === endYear - 1 && (lastMonth = endMonth)
}
}
}
return dateArr
},
dateChange (e) {
const day = this.data.dateArr[e.detail.value[0]]
let time = day.value + (Number(this.data.hourValue) * 3600 + Number(this.data.minValue) * 60) * 1000
this.setData({
dateIndex: e.detail.value[0],
activeTime: time
})
},
hourChange (e) {
const hour = Number(this.data.hourArr[e.detail.value[0]])
let time = this.data.dateArr[this.data.dateIndex].value + (hour * 3600 + Number(this.data.minValue) * 60) * 1000
this.setData({
hourValue: this.data.hourArr[e.detail.value[0]],
activeTime: time
})
},
minChange (e) {
const min = Number(this.data.minArr[e.detail.value[0]])
let time = this.data.dateArr[this.data.dateIndex].value + (Number(this.data.hourValue) * 3600 + min * 60) * 1000
this.setData({
minValue: this.data.minArr[e.detail.value[0]],
activeTime: time
})
},
bindclose () {
this.setData({
show: false
})
},
bindpickstart () {
this.setData({
disabled: true
})
},
bindpickend () {
this.setData({
disabled: false
})
},
preventTouchMove () {
// 阻止半屏状态下 页面滑动
}
}
})
wxss代码
.date-time-container {
min-height: 350px !important;
display: flex;
flex-direction: column;
}
.date-time-body {
width: 100% !important;
display: flex;
flex: auto;
}
.date-time-oper {
flex: none;
display: flex;
justify-content: space-between;
align-items: center;
}
.selector-panel {
width: 80px;
display: flex;
align-items: center;
}
.selector-panel-date {
flex: auto;
}
.selector-picker {
width: 100%;
height: 80%;
font-size: 24px;
}
.selector-item {
line-height: 50px !important;
text-align: center;
}
.selector-item-date{
font-size: 16px;
}
.oper-btn {
width: 45% !important;
height: 40px !important;
}