前言
日历是我们开发过程中经常会使用到的一个功能,po主在开发小程序的过程中就遇到一个场景需要使用日历组件。首先上网搜索一番,但是没有找到合适自己的,于是便决定自己写一款小程序日历组件。
先上效果图:

1.功能分析
这款日历组件主要是提供日期选择功能。首先是日期,然后是时分秒。并且提供配置开始时间和结束时间设置,以及周末是否可选的设置项。
总体来说分为两部分:
- 第一页的日期选择
- 第二页的时间选择
2.实现分析:
2.1日期选择:
这个页面首先我们 setMonthData 函数构造一个 7 * 6 的数据结构, 这个数据结构包含当月,以及部分上月和下月的天数。在构造的时候通过isValidDay函数判断当天是否是可以选择的
然后加上 valid 一个标识,以便在渲染的时候用于显示和点击判断。
// 根据条件判断当天日期是否有效
isValidDay(date) {
let {startDate, endDate, needWeek} = this.data;
let valid = false;
if (startDate && endDate) { // 开始日期和结束日期都存在
if (date >= startDate && date < endDate) {
valid = true;
}
} else if (startDate && !endDate) { // 开始日期存在结束日期不存在
if (date >= startDate) {
valid = true;
}
} else if (!startDate && endDate) { // 开始日期不存在结束日期存在
if (date < endDate) {
valid = true;
}
} else {
valid = true;
}
if (!needWeek) {
let weekDay = date.getDay();
if (weekDay === 0 || weekDay === 6) {
valid = false;
}
}
return valid;
}
// 根据传入的日期构造数据
setMonthData() {
const currentDate = this.data.currentDate;
let year = currentDate.getFullYear(), month = currentDate.getMonth();
// 当月所有天数的数据结构
let currentData = [];
// 获取当月1号的星期0~6
let firstDayWeek = new Date(year, month, 1).getDay();
// 天数, 用来标识当前是本月中的哪一天
let dayIndex = 0;
// 第1行
let firstCol = [];
for (let i = 0; i < 7; i++) {
if (i < firstDayWeek) {
let date = new Date(year, month, dayIndex - (firstDayWeek - i) + 1);
let valid = this.isValidDay(date);
firstCol.push({
date: date,
dateStr: date.toString(),
day: date.getDate(),
valid: valid,
currentMonth: false
})
} else {
dayIndex += 1;
let date = new Date(year, month, dayIndex);
let valid = this.isValidDay(date);
firstCol.push({
date: date,
dateStr: date.toString(),
day: dayIndex,
valid: valid,
currentMonth: true
});
}
}
currentData.push(firstCol);
// 第2~4行
for (let i = 0; i < 3; i++) {
let col = [];
for (let j = 0; j < 7; j++) {
dayIndex += 1;
let date = new Date(year, month, dayIndex);
let valid = this.isValidDay(date);
col.push({
date: date,
dateStr: date.toString(),
day: dayIndex,
valid: valid,
currentMonth: true
});
}
currentData.push(col);
}
// 第5行
let lastCol = [];
// 余下一行中本月的天数
let restDay = new Date(year, month + 1, 0).getDate() - dayIndex;
for (let i = 0; i < 7; i++) {
if (i < restDay) {
dayIndex += 1;
let date = new Date(year, month, dayIndex);
let valid = this.isValidDay(date);
lastCol.push({
date: date,
dateStr: date.toString(),
day: dayIndex,
valid: valid,
currentMonth: true
});
} else {
let date = new Date(year, month + 1, i - restDay + 1);
let valid = this.isValidDay(date);
lastCol.push({
date: date,
dateStr: date.toString(),
day: date.getDate(),
valid: valid,
currentMonth: false
});
}
}
currentData.push(lastCol);
let restDay2 = restDay - 7;
// 第6行
let lastCol2 = [];
for (let i = 0; i < 7; i++) {
if (i < restDay2) {
dayIndex += 1;
let date = new Date(year, month, dayIndex);
let valid = this.isValidDay(date);
lastCol2.push({
date: date,
dateStr: date.toString(),
day: dayIndex,
valid: valid,
currentMonth: true
});
} else {
let date = new Date(year, month + 1, i - restDay2 + 1);
let valid = this.isValidDay(date);
lastCol2.push({
date: date,
dateStr: date.toString(),
day: date.getDate(),
valid: valid,
currentMonth: false
});
}
}
currentData.push(lastCol2);
this.setData({
currentData: currentData
});
}
构造完当月数据后我们就可以渲染直接在页面渲染这些数据,渲染的时候需要加一些判断条件,这样我们的日历才能把能把这个月的天和上个月的天以及禁用的天区分开
<view class='date-body' wx:if="{{mode==='date'}}">
<view class='tr'>
<view class='th'><view class="cell">日</view></view>
<view class='th'><view class="cell">一</view></view>
<view class='th'><view class="cell">二</view></view>
<view class='th'><view class="cell">三</view></view>
<view class='th'><view class="cell">四</view></view>
<view class='th'><view class="cell">五</view></view>
<view class='th'><view class="cell">六</view></view>
</view>
<view class="tr"
wx:for="{{currentData}}"
wx:key="{{i}}"
wx:for-item="tr"
wx:for-index="i">
<view class="td"
wx:for="{{tr}}"
wx:key="{{j}}"
wx:for-item="td"
wx:for-index="j"
data-i="{{i}}"
data-j="{{j}}"
bindtap="chooseDate">
<view hover-class="none"
class="cell {{td.currentMonth ? '' : 'otherMonth'}} {{td.valid ? '' : 'disabled'}} {{td.date && td.dateStr === selectedDateStr ? 'cur' : ''}}">
{{td.day}}
</view>
</view>
</view>
</view>
至此这个页面的功能基本完成了,但是顶部还有一些功能箭头,用于切换下月,下一年,以及上月,上一年。
这个我们通过下面的changeDate方法去改变切换后的月份日期,然后重新调用 setMonthData 即可,到此年月日的选择就完成了
changeDate(event) {
let currentDate = this.data.currentDate;
let year = currentDate.getFullYear(), month = currentDate.getMonth();
let type = event.currentTarget.dataset.type;
switch (type) {
case 'year-':
currentDate.setFullYear(year - 1)
break;
case 'year+':
currentDate.setFullYear(year + 1)
break;
case 'month-':
currentDate.setMonth(month - 1)
break;
case 'month+':
currentDate.setMonth(month + 1)
break;
}
this.setCurrentDate(currentDate);
this.setMonthData();
}
2.2日期选择:
时间选择,这里我们直接使用小程序的自带的 picker-view组件,构造好时分秒数据结构传递渲染即可。
hours: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
minutes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
seconds: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
initTimeValue: [0, 0, 0], // 初始化选中时间的时,分,秒
timeValue: [0, 0, 0], // 选中时间的时,分,秒
<view class='time-body' wx:if="{{mode==='time'}}">
<picker-view indicator-class="selectItem" value="{{initTimeValue}}" bindchange="bindChange">
<picker-view-column>
<view wx:for="{{hours}}" wx:key="*this" class="item">{{item}}时</view>
</picker-view-column>
<picker-view-column>
<view wx:for="{{minutes}}" wx:key="*this" class="item">{{item}}分</view>
</picker-view-column>
<picker-view-column>
<view wx:for="{{seconds}}" wx:key="*this" class="item">{{item}}秒</view>
</picker-view-column>
</picker-view>
</view>
到此整个日历组件便完成了~~,
3.代码实现
具体代码我放在github上了,感兴趣的同学可以自取,如有不满足需求的地方可以尽情修改,有问题可以在评论区@我
- 小程序日历组件源代码