目录
页面效果
代码实现
js
Component({
/**
* 组件的属性列表
*/
properties: {
pickerShow: {
type: Boolean,
},
config: Object,
},
/**
* 组件的初始数据
*/
data: {
pickerReady: false,
// pickerShow:true
// limitStartTime: new Date().getTime()-1000*60*60*24*30,
// limitEndTime: new Date().getTime(),
// yearStart:2000,
// yearEnd:2100
},
detached: function () {
this.setData({
pickerReady: false,
});
},
attached: function () {
this.readConfig();
this.initPick(this.data.config || null);
this.setData({
startValue: this.data.startValue,
endValue: this.data.endValue,
pickerReady: true,
});
},
ready: function () {},
/**
* 组件的方法列表
*/
methods: {
/**
* 读取并处理配置中的时间限制信息
* 此函数用于初始化或更新时间选择器的限制范围,以及配置中的其他时间相关参数
*/
readConfig() {
// 获取当前时间的时间戳,最大选的范围
let limitEndTime = new Date().getTime();
// 计算30天前的时间戳,最小可选择的范围
let limitStartTime = new Date().getTime() - 1000 * 60 * 60 * 24 * 30;
// 检查是否存在配置信息
if (this.data.config) {
let conf = this.data.config;
// 如果配置中指定了日期限制,当为数字n时,范围是当前时间的最近n天
if (typeof conf.dateLimit == "number") {
limitStartTime =
new Date().getTime() - 1000 * 60 * 60 * 24 * conf.dateLimit;
}
// 如果配置中指定了最小可选,则将其转换为时间戳
if (conf.limitStartTime) {
limitStartTime = new Date(
conf.limitStartTime.replace(/-/g, "/")
).getTime();
}
// 如果配置中指定了最大可选,则将其转换为时间戳
if (conf.limitEndTime) {
limitEndTime = new Date(
conf.limitEndTime.replace(/-/g, "/")
).getTime();
}
// 设置数据,包括年份范围、结束日期标志、日期限制和时间列的显示状态
this.setData({
yearStart: conf.yearStart || 2000, // 默认开始年份为2000
yearEnd: conf.yearEnd || 2100, // 默认结束年份为2100
endDate: conf.endDate || false, // 默认不启用结束日期
dateLimit: conf.dateLimit || false, // 默认不设置日期限制
hourColumn:
conf.column == "hour" ||
conf.column == "minute" ||
conf.column == "second", // 根据配置决定是否显示小时列
minColumn: conf.column == "minute" || conf.column == "second", // 根据配置决定是否显示分钟列
secColumn: conf.column == "second", // 根据配置决定是否显示秒列
});
}
// 将时间限制的时间戳格式化数组方便循环
let limitStartTimeArr = formatTime(limitStartTime);
let limitEndTimeArr = formatTime(limitEndTime);
// 更新数据,包括时间限制的时间戳和格式化后的时间数组
this.setData({
limitStartTime, // 开始时间限制
limitStartTimeArr, // 开始时间限制数组
limitEndTime, // 结束时间限制
limitEndTimeArr, //结束时间限制数组
});
},
//滚动开始
handlePickStart: function (e) {
this.setData({
isPicking: true,
});
},
//滚动结束
handlePickEnd: function (e) {
this.setData({
isPicking: false,
});
},
onConfirm: function () {
//滚动未结束时不能确认
if (this.data.isPicking) {
return;
}
let startTime = new Date(this.data.startPickTime.replace(/-/g, "/"));
let endTime = new Date(this.data.endPickTime.replace(/-/g, "/"));
if (startTime <= endTime || !this.data.endDate) {
this.setData({
startTime,
endTime,
});
let startArr = formatTime(startTime).arr;
let endArr = formatTime(endTime).arr;
let format0 = function (num) {
return num < 10 ? "0" + num : num;
};
let startTimeBack =
startArr[0] +
"-" +
format0(startArr[1]) +
"-" +
format0(startArr[2]) +
" " +
(this.data.hourColumn ? format0(startArr[3]) : "00") +
":" +
(this.data.minColumn ? format0(startArr[4]) : "00") +
":" +
(this.data.secColumn ? format0(startArr[5]) : "00");
let endTimeBack =
endArr[0] +
"-" +
format0(endArr[1]) +
"-" +
format0(endArr[2]) +
" " +
(this.data.hourColumn ? format0(endArr[3]) : "00") +
":" +
(this.data.minColumn ? format0(endArr[4]) : "00") +
":" +
(this.data.secColumn ? format0(endArr[5]) : "00");
let time = {
startTime: startTimeBack,
endTime: endTimeBack,
};
//触发自定义事件
this.triggerEvent("setPickerTime", time);
this.triggerEvent("hidePicker", {});
} else {
wx.showToast({
icon: "none",
title: "时间不合理",
});
}
},
hideModal: function () {
this.triggerEvent("hidePicker", {});
},
changeStartDateTime: function (e) {
let val = e.detail.value;
this.compareTime(val, "start");
},
changeEndDateTime: function (e) {
let val = e.detail.value;
this.compareTime(val, "end");
},
/**
* 比较时间是否在指定范围内
* @param {Array} val_ 时间数组,包含年、月、日、时、分、秒的数值
* @param {string} type 类型指示符,"start"表示开始时间,"end"表示结束时间
* @description 该函数用于比较给定的时间是否在系统或用户设定的时间范围内
* 根据比较结果,设置开始时间或结束时间
*/
compareTime(val_, type) {
// 将时间数组中的每个元素转换为字符串
const val = val_.map((it) => it.toString());
// 获取小时、分钟、秒的字符串表示,如果不存在则默认为"00"
let h = val[3] ? this.data.HourList[val[3]] : "00";
let m = val[4] ? this.data.MinuteList[val[4]] : "00";
let s = val[5] ? this.data.SecondList[val[5]] : "00";
// 构造完整的时间字符串
let time =
this.data.YearList[val[0]] +
"-" +
this.data.MonthList[val[1]] +
"-" +
this.data.DayList[val[2]] +
" " +
h +
":" +
m +
":" +
s;
// 获取系统或用户设定的开始和结束时间
let start = this.data.limitStartTime;
let end = this.data.limitEndTime;
// 将输入的时间字符串转换为时间戳
let timeNum = new Date(time.replace(/-/g, "/")).getTime();
// 用于存储限制日期的各部分
let year, month, day, hour, min, sec, limitDate;
let tempArr = [];
// 根据不同的条件计算限制日期
if (!this.data.dateLimit) {
// 如果没有设定日期限制,则直接使用输入的日期
limitDate = [
this.data.YearList[val[0]],
this.data.MonthList[val[1]],
this.data.DayList[val[2]],
this.data.HourList[val[3]],
this.data.MinuteList[val[4]],
this.data.SecondList[val[5]],
];
} else if (
type == "start" &&
timeNum > new Date(this.data.endPickTime.replace(/-/g, "/")) &&
this.data.config.endDate
) {
// 如果是开始时间且输入时间晚于结束选择时间,则使用结束选择时间
limitDate = formatTime(this.data.endPickTime).arr;
} else if (
type == "end" &&
timeNum < new Date(this.data.startPickTime.replace(/-/g, "/"))
) {
// 如果是结束时间且输入时间早于开始选择时间,则使用开始选择时间
limitDate = formatTime(this.data.startPickTime).arr;
} else if (timeNum < start) {
// 如果输入时间早于系统或用户设定的开始时间,则使用设定的开始时间
limitDate = this.data.limitStartTimeArr.arr;
} else if (timeNum > end) {
// 如果输入时间晚于系统或用户设定的结束时间,则使用设定的结束时间
limitDate = this.data.limitEndTimeArr.arr;
} else {
// 如果输入时间在系统或用户设定的范围内,则直接使用输入的日期
limitDate = [
this.data.YearList[val[0]],
this.data.MonthList[val[1]],
this.data.DayList[val[2]],
this.data.HourList[val[3]],
this.data.MinuteList[val[4]],
this.data.SecondList[val[5]],
];
}
// 将限制日期的各部分分配给相应的变量
year = limitDate[0];
month = limitDate[1];
day = limitDate[2];
hour = limitDate[3];
min = limitDate[4];
sec = limitDate[5];
// 根据类型指示符设置开始时间或结束时间
if (type == "start") {
this.setStartDate(year, month, day, hour, min, sec);
} else if (type == "end") {
this.setEndDate(year, month, day, hour, min, sec);
}
},
/**
* 获取指定月份的天数
*
* 此函数根据给定的年份和月份,返回该月份的天数。特别地,对于二月份,
* 该函数考虑了闰年的情况,确保返回准确的天数。
*
* @param {number} year - 指定的年份,用于计算二月份的天数是否为29天(闰年)
* @param {number} month - 指定的月份,用于确定该月的天数
* @returns {number} 返回指定月份的天数
*/
getDays: function (year, month) {
// 定义一个数组,包含除二月外各月份的天数
let daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
// 如果是二月份
if (month === 2) {
// 判断是否为闰年,如果是闰年则返回29天,否则返回28天
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0
? 29
: 28;
} else {
// 非二月份则直接返回对应月份的天数
return daysInMonth[month - 1];
}
},
/**
* 初始化选择器
* @param {Object} initData - 初始化数据对象
* 初始化startTime和endTime,如果传入的initData对象中包含initStartTime和initEndTime属性,
* 则使用这些属性值作为时间选择器的初始值,否则使用当前时间作为默认值
*/
initPick: function (initData) {
// 初始化开始时间选择器默认时间(默认为当前时间)
const date = initData.initStartTime
? new Date(initData.initStartTime.replace(/-/g, "/"))
: new Date();
// 初始化结束时间选择器默认时间 (默认为当前时间)
const endDate = initData.initEndTime
? new Date(initData.initEndTime.replace(/-/g, "/"))
: new Date();
const startDate = date;
// 从date中提取年月日时分秒信息
const startYear = date.getFullYear();
const startMonth = date.getMonth() + 1;
const startDay = date.getDate();
const startHour = date.getHours();
const startMinute = date.getMinutes();
const startSecond = date.getSeconds();
// 从endDate中提取年月日时分秒信息
const endYear = endDate.getFullYear();
const endMonth = endDate.getMonth() + 1;
const endDay = endDate.getDate();
const endHour = endDate.getHours();
const endMinute = endDate.getMinutes();
const endSecond = endDate.getSeconds();
// 定义各个时间单位的列表
let YearList = [];
let MonthList = [];
let DayList = [];
let HourList = [];
let MinuteList = [];
let SecondList = [];
// 设置年份列表,范围从开始年份到结束年份
for (let i = this.data.yearStart; i <= this.data.yearEnd; i++) {
YearList.push(i);
}
// 设置月份列表,从1月到12月
for (let i = 1; i <= 12; i++) {
MonthList.push(i);
}
// 设置日期列表,从1日到31日
for (let i = 1; i <= 31; i++) {
DayList.push(i);
}
// 设置小时列表,从00到23
for (let i = 0; i <= 23; i++) {
if (0 <= i && i < 10) {
i = "0" + i;
}
HourList.push(i);
}
// 设置分钟和秒列表,从00到59
for (let i = 0; i <= 59; i++) {
if (0 <= i && i < 10) {
i = "0" + i;
}
MinuteList.push(i);
SecondList.push(i);
}
// 将构建好的时间列表数据绑定到组件的数据对象中
this.setData({
YearList,
MonthList,
DayList,
HourList,
MinuteList,
SecondList,
});
// 设置开始日期和结束日期
this.setStartDate(
startYear,
startMonth,
startDay,
startHour,
startMinute,
startSecond
);
this.setEndDate(endYear, endMonth, endDay, endHour, endMinute, endSecond);
// 注释掉的setTimeout可能用于解决某些异步问题或界面更新,但此处未实际使用
// setTimeout(() => {
// this.setStartDate(nowYear, nowMonth, nowDay, nowHour, nowMinute)
// this.setEndDate(nowYear, nowMonth, nowDay, nowHour, nowMinute)
// }, 0);
},
/**
* 设置选择器日期数组
* 根据给定的年、月、日、时、分、秒来设置日期选择器的数组
* @param {string} type - 类型指示符,可以是"start"或"end"
* @param {number} year - 指定的年份
* @param {number} month - 指定的月份
* @param {number} day - 指定的日期
* @param {number} hour - 指定的小时
* @param {number} minute - 指定的分钟
* @param {number} second - 指定的秒数
* @returns {Object} 返回一个对象,包含各个日期组件在选择器中的索引
*/
setPickerDateArr(type, year, month, day, hour, minute, second) {
// 初始化各个日期组件的索引变量
let yearIdx = 0;
let monthIdx = 0;
let dayIdx = 0;
let hourIdx = 0;
let minuteIdx = 0;
let secondIdx = 0;
// 查找年份在年份列表中的索引
this.data.YearList.map((v, idx) => {
if (parseInt(v) === year) {
yearIdx = idx;
}
});
// 查找月份在月份列表中的索引
this.data.MonthList.map((v, idx) => {
if (parseInt(v) === month) {
monthIdx = idx;
}
});
// 重新设置日期列表 (根据月份计算天数)
let DayList = [];
for (let i = 1; i <= this.getDays(year, month); i++) {
DayList.push(i);
}
// 查找日期在日期列表中的索引
DayList.map((v, idx) => {
if (parseInt(v) === day) {
dayIdx = idx;
}
});
// 根据类型更新开始或结束的日期列表
if (type == "start") {
this.setData({ startDayList: DayList });
} else if (type == "end") {
this.setData({ endDayList: DayList });
}
// 查找小时在小时列表中的索引
this.data.HourList.map((v, idx) => {
if (parseInt(v) === parseInt(hour)) {
hourIdx = idx;
}
});
// 查找分钟在分钟列表中的索引
this.data.MinuteList.map((v, idx) => {
if (parseInt(v) === parseInt(minute)) {
minuteIdx = idx;
}
});
// 查找秒数在秒数列表中的索引
this.data.SecondList.map((v, idx) => {
if (parseInt(v) === parseInt(second)) {
secondIdx = idx;
}
});
// 返回包含所有日期组件索引的对象
return {
yearIdx,
monthIdx,
dayIdx,
hourIdx,
minuteIdx,
secondIdx,
};
},
/**
* 设置开始日期函数
*
* 此函数的作用是根据传入的年、月、日、时、分、秒参数,生成一个可用于选择器的日期数组,
* 并更新组件的数据,以在界面上显示选择的开始日期。它首先调用了一个辅助函数来处理日期数组的生成,
* 然后使用setData方法更新了多个与日期选择器相关的数据属性,包括年、月、时、分、秒的列表,
* 以及选择器的默认值和显示的日期格式字符串。
*
* @param {number} year - 传入的年份
* @param {number} month - 传入的月份,注意JavaScript中月份是从0开始的,所以这里传入的值需要减1
* @param {number} day - 传入的日
* @param {number} hour - 传入的小时
* @param {number} minute - 传入的分钟
* @param {number} second - 传入的秒
*/
setStartDate: function (year, month, day, hour, minute, second) {
// 调用辅助函数处理日期数组
let pickerDateArr = this.setPickerDateArr(
"start",
year,
month,
day,
hour,
minute,
second
);
// 更新界面数据,设置日期选择器的列表和默认选中值
this.setData({
startYearList: this.data.YearList, // 年份列表
startMonthList: this.data.MonthList, // 月份列表
// startDayList: this.data.DayList,
startHourList: this.data.HourList, // 小时列表
startMinuteList: this.data.MinuteList, // 分钟列表
startSecondList: this.data.SecondList, // 秒钟列表
startValue: [
// 设置日期选择器的默认选中值
pickerDateArr.yearIdx,
pickerDateArr.monthIdx,
pickerDateArr.dayIdx,
pickerDateArr.hourIdx,
pickerDateArr.minuteIdx,
pickerDateArr.secondIdx,
],
// 构造日期时间字符串用于界面显示
startPickTime:
this.data.YearList[pickerDateArr.yearIdx] +
"-" +
this.data.MonthList[pickerDateArr.monthIdx] +
"-" +
this.data.DayList[pickerDateArr.dayIdx] +
" " +
this.data.HourList[pickerDateArr.hourIdx] +
":" +
this.data.MinuteList[pickerDateArr.minuteIdx] +
":" +
this.data.SecondList[pickerDateArr.secondIdx],
});
},
setEndDate: function (year, month, day, hour, minute, second) {
let pickerDateArr = this.setPickerDateArr(
"end",
year,
month,
day,
hour,
minute,
second
);
this.setData({
endYearList: this.data.YearList,
endMonthList: this.data.MonthList,
// endDayList: this.data.DayList,
endHourList: this.data.HourList,
endMinuteList: this.data.MinuteList,
endSecondList: this.data.SecondList,
endValue: [
pickerDateArr.yearIdx,
pickerDateArr.monthIdx,
pickerDateArr.dayIdx,
pickerDateArr.hourIdx,
pickerDateArr.minuteIdx,
pickerDateArr.secondIdx,
],
endPickTime:
this.data.YearList[pickerDateArr.yearIdx] +
"-" +
this.data.MonthList[pickerDateArr.monthIdx] +
"-" +
this.data.DayList[pickerDateArr.dayIdx] +
" " +
this.data.HourList[pickerDateArr.hourIdx] +
":" +
this.data.MinuteList[pickerDateArr.minuteIdx] +
":" +
this.data.SecondList[pickerDateArr.secondIdx],
});
},
},
});
function formatTime(date) {
if (typeof date == "string" || "number") {
try {
date = date.replace(/-/g, "/"); //兼容ios
} catch (error) {}
date = new Date(date);
}
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
return {
str:
[year, month, day].map(formatNumber).join("-") +
" " +
[hour, minute, second].map(formatNumber).join(":"),
arr: [year, month, day, hour, minute, second],
};
}
function formatNumber(n) {
n = n.toString();
return n[1] ? n : "0" + n;
}
wxml
<!-- components/timePicker/timePicker.wxml -->
<!-- 自定义时间筛选器 -->
<view wx:if="{{pickerShow&&pickerReady}}">
<view class="picker-container {{pickerShow?'show_picker':'hide_picker'}}">
<view class="btn-box">
<view class="pick_btn" bind:tap="hideModal">取消</view>
<view class='pick_btn' style="color: #19f" bind:tap="onConfirm">确定</view>
</view>
<view>
<picker-view class='sensorTypePicker' indicator-style='height: 35px;' bindchange="changeStartDateTime" value="{{startValue}}" style="height: {{endDate?'120px':'250px'}};" bindpickstart="handlePickStart" bindpickend="handlePickEnd" data-type="start">
<picker-view-column style="min-width: 70px;flex-shrink: 0">
<view class='picker-item' wx:for="{{startYearList}}" wx:key='*this'>{{item}}年</view>
</picker-view-column>
<picker-view-column>
<view class='picker-item' wx:for="{{startMonthList}}" wx:key='*this'>{{item}}月</view>
</picker-view-column>
<picker-view-column>
<view class='picker-item' wx:for="{{startDayList}}" wx:key='*this'>{{item}}日</view>
</picker-view-column>
<picker-view-column hidden="{{!hourColumn}}">
<view class='picker-item' wx:for="{{startHourList}}" wx:key='*this'>{{item}}时</view>
</picker-view-column>
<picker-view-column hidden="{{!minColumn}}">
<view class='picker-item' wx:for="{{startMinuteList}}" wx:key='*this'>{{item}}分</view>
</picker-view-column>
<picker-view-column hidden="{{!secColumn}}">
<view class='picker-item' wx:for="{{startSecondList}}" wx:key='*this'>{{item}}秒</view>
</picker-view-column>
</picker-view>
</view>
<view wx:if="{{endDate}}">
<view class='to' style='margin-top: 4px;margin-bottom: 4px;'>至</view>
<picker-view class='sensorTypePicker' indicator-style='height: 35px;' bindchange="changeEndDateTime" bindpickstart="handlePickStart" bindpickend="handlePickEnd" value="{{endValue}}">
<picker-view-column style="min-width: 70px;flex-shrink: 0">
<view class='picker-item' wx:for="{{endYearList}}" wx:key='*this' style="min-width: 70px;">
{{item}}年
</view>
</picker-view-column>
<picker-view-column>
<view class='picker-item' wx:for="{{endMonthList}}" wx:key='*this'>{{item}}月</view>
</picker-view-column>
<picker-view-column>
<view class='picker-item' wx:for="{{endDayList}}" wx:key='*this'>{{item}}日</view>
</picker-view-column>
<picker-view-column hidden="{{!hourColumn}}">
<view class='picker-item' wx:for="{{endHourList}}" wx:key='*this'>{{item}}时</view>
</picker-view-column>
<picker-view-column hidden="{{!minColumn}}">
<view class='picker-item' wx:for="{{endMinuteList}}" wx:key='*this'>{{item}}分</view>
</picker-view-column>
<picker-view-column hidden="{{!secColumn}}">
<view class='picker-item' wx:for="{{startSecondList}}" wx:key='*this'>{{item}}秒</view>
</picker-view-column>
</picker-view>
</view>
<!-- <view class='sure' bindtap="onConfirm">确定</view> -->
</view>
<!-- 遮罩 -->
<view class="sensorType-screen" bind:tap="hideModal" />
</view>
wxss
/* components/timePicker/timePicker.wxss */
.picker-item{
line-height: 50px;
display: flex;
justify-content: center;
align-items: center;
}
/* 自定义时间 */
.picker-container {
display: flex;
flex-direction: column;
/* justify-content: center; */
align-items: center;
width: 100%;
overflow: hidden;
position: fixed;
bottom: 0rpx;
left: 0;
height: 0;
transition: height 0.5s;
z-index: 2000;
background: white;
border-top: 1px solid #EFEFF4;
}
.sensorType-screen{
width: 100vw;
/* height:400rpx; */
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background:#000;
opacity: 0.7;
overflow: hidden;
z-index: 1999;
color: #fff;
}
.sensorTypePicker{
width: 690rpx;
height: 120px;
/* padding: 45px 0; */
}
.picker-item{
line-height: 50px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
/* overflow: hidden; */
}
.box{
padding: 0 10px;
}
/* 至 */
.to{
width:100%;
display: flex;
justify-content: center;align-items: center;
color:rgb(138,138,138);
/* font-size:30rpx; */
}
/* 确定 */
.sure{
width:100%;
height:45px;
border-top: 1px solid #EFEFF4;
display: flex;justify-content: center;align-items: center;
color: rgb(36,123,255);
font-size:16px;
}
.btn-box{
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid #eee;
}
.pick_btn{
padding: 7px 15px;
color: #ccc;
/* background-color: #159; */
}
.show_picker{
height: 320px;
}
.hide_picker{
height: 0;
}
json
{
"component": true,
"usingComponents": {}
}
注意事项
微信小程序自定义时间选择器,支持多种自定义功能。
在开发工具中浏览: https://developers.weixin.qq.com/s/N9EdArmQ7a6j
复制链接在浏览器中打开
配置项
pickerConfig: {
endDate: true, // 是否需要结束时间,为true时显示开始时间和结束时间两个picker
column: "second", //可选的最小时间范围day、hour、minute、secend
dateLimit: true, //是否现在时间可选范围,false时可选任意时间;当为数字n时,范围是当前时间的最近n天
initStartTime:'2019-01-01 12:32:44', //picker初始时间,默认当前时间
initEndTime: "2019-12-01 12:32:44", //picker初始结束时间,默认当前时间
limitStartTime: "2015-05-06 12:32:44", //最小可选时间
limitEndTime: "2055-05-06 12:32:44" //最大可选时间
}
其他限制条件可修改组件中的compareTime函数定义
endDate: true
endDate: false
pickerReady 为了保证加载顺序初始化正常 Skyline 模式下初始化会触发change 导出初始化异常
未解决 用户快速滚动会造成页面卡死 有思路的大佬可以留言解决方案