Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

效果图

组件介绍

原生小程序编写,简单轻便,拿来即用。
推荐从代码托管地址获取代码,后续会更新功能: github地址 | gitee地址

代码部分(这里可能不是最新的,推荐去gitee克隆代码)

calendar.wxml

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!--component/calendar/calendar.wxml-->
<view class="calendar">
<view class="title">
<view class="header-wrap">
<view class="flex">
<view class="title">{{title}}</view>
<view class="month">
<block wx:if="{{title}}">
(
</block>
{{selectDay.year}}年{{selectDay.month}}月
<block wx:if="{{title}}">
)
</block>
</view>
</view>
<block wx:if="{{goNow}}">
<view wx:if="{{open && !(nowDay.year==selectDay.year&&nowDay.month==selectDay.month&&nowDay.day==selectDay.day)}}" class="today" bindtap="switchNowDate">
今日
</view>
</block>
</view>
</view>

<!-- 日历头部 -->
<view class="flex-around calendar-week">
<view class="view"></view>
<view class="view"></view>
<view class="view"></view>
<view class="view"></view>
<view class="view"></view>
<view class="view"></view>
<view class="view"></view>
</view>
<!-- 日历主体 -->
<swiper class="swiper" style="height:{{swiperHeight}}rpx" bindchange="swiperChange" circular="{{true}}" current="{{swiperCurrent}}" duration="{{swiperDuration}}">
<swiper-item wx:for="{{[dateList0, dateList1, dateList2]}}" wx:for-index="listIndex" wx:for-item="listItem" wx:key="listIndex">
<view class="flex-start flex-wrap calendar-main" style="height:{{listItem.length/7*82}}rpx">
<view wx:for="{{listItem}}" wx:key="dateList" class="day">
<view class="bg {{item.month === selectDay.month?spotMap['y'+item.year+'m'+item.month+'d'+item.day]?spotMap['y'+item.year+'m'+item.month+'d'+item.day]:'':''}} {{(item.year === nowDay.year && item.month === nowDay.month && item.day === nowDay.day) ? 'now' : ''}} {{(item.year === selectDay.year && item.month === selectDay.month) ? (item.day === selectDay.day && oldCurrent === listIndex ?'select':''): 'other-month'}}" catchtap="selectChange" data-day="{{item.day}}" data-year="{{item.year}}" data-month="{{item.month}}">
{{item.day}}
</view>
</view>
</view>
</swiper-item>
</swiper>
<view catchtap="openChange" class="flex list-open">
<view class="icon {{open?'fold':'unfold'}}"></view>
</view>
</view>

calendar.js

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
27
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
// component/calendar/calendar.js
Component({
/**
* 组件的属性列表
*/
properties: {
spotMap: { //标点的日期
type: Object,
value: {}
},
defaultTime: { //标记的日期,默认为今日
type: String,
value: ''
},
title: { //标题
type: String,
value: ''
},
goNow: { // 是否有快速回到今天的功能
type: Boolean,
value: true,
}
},

/**
* 组件的初始数据
*/
data: {
selectDay: {}, //选中时间
nowDay: {}, //现在时间
open: false,
swiperCurrent: 1, //选中时间
oldCurrent: 1, //之前选中时间
dateList0: [], //0位置的日历数组
dateList1: [], //1位置的日历数组
dateList2: [], //2位置的日历数组
swiperDuration: 500,
swiperHeight: 0,
backChange: false, //跳过change切换
},

/**
* 组件的方法列表
*/
methods: {
swiperChange(e) { // 日历滑动时触发的方法
if (this.data.backChange) {
this.setData({
backChange: false
})
return
}
//计算第三个索引
let rest = 3 - e.detail.current - this.data.oldCurrent
let dif = e.detail.current - this.data.oldCurrent
let date
if (dif === -2 || (dif > 0 && dif !== 2)) { //向右划的情况,日期增加
if (this.data.open) {
date = new Date(this.data.selectDay.year, this.data.selectDay.month)
this.setMonth(date.getFullYear(), date.getMonth() + 1, undefined)
this.getIndexList({
setYear: this.data.selectDay.year,
setMonth: this.data.selectDay.month,
dateIndex: rest
})
} else {
date = new Date(this.data.selectDay.year, this.data.selectDay.month - 1, this.data.selectDay.day + 7)
this.setMonth(date.getFullYear(), date.getMonth() + 1, date.getDate())
this.getIndexList({
setYear: this.data.selectDay.year,
setMonth: this.data.selectDay.month - 1,
setDay: this.data.selectDay.day + 7,
dateIndex: rest
})
}
} else { //向左划的情况,日期减少
if (this.data.open) {
date = new Date(this.data.selectDay.year, this.data.selectDay.month - 2)
this.setMonth(date.getFullYear(), date.getMonth() + 1, undefined)
this.getIndexList({
setYear: this.data.selectDay.year,
setMonth: this.data.selectDay.month - 2,
dateIndex: rest
})
} else {
date = new Date(this.data.selectDay.year, this.data.selectDay.month - 1, this.data.selectDay.day - 7)
this.setMonth(date.getFullYear(), date.getMonth() + 1, date.getDate())
this.getIndexList({
setYear: this.data.selectDay.year,
setMonth: this.data.selectDay.month - 1,
setDay: this.data.selectDay.day - 7,
dateIndex: rest
})
}
}
this.setData({
oldCurrent: e.detail.current
})
this.setSwiperHeight(e.detail.current)
},
setSwiperHeight(index) { // 根据指定位置数组的大小计算长度
this.setData({
swiperHeight: this.data[`dateList${index}`].length / 7 * 82 + 18
})
},
//更新指定的索引和月份的列表
getIndexList({
setYear,
setMonth,
setDay = void 0,
dateIndex
}) {
let appointMonth
if (setDay)
appointMonth = new Date(setYear, setMonth, setDay)
else
appointMonth = new Date(setYear, setMonth)
let listName = `dateList${dateIndex}`
this.setData({
[listName]: this.dateInit({
setYear: appointMonth.getFullYear(),
setMonth: appointMonth.getMonth() + 1,
setDay: appointMonth.getDate(),
hasBack: true
}),
})
},
//设置月份
setMonth(setYear, setMonth, setDay) {
const day = Math.min(new Date(setYear, setMonth, 0).getDate(), this.data.selectDay.day)
if (this.data.selectDay.year !== setYear || this.data.selectDay.month !== setMonth) {
const data = {
selectDay: {
year: setYear,
month: setMonth,
day: setDay ? setDay : day
},
}
if (!setDay) {
data.open = true
}
this.setData(data, () => {
this.triggerEvent("selectDay", this.data.selectDay)
})
} else {
const data = {
selectDay: {
year: setYear,
month: setMonth,
day: setDay ? setDay : day
},
}
this.setData(data, () => {
this.triggerEvent("selectDay", this.data.selectDay)
})
}
},
//展开收起
openChange() {
this.setData({
open: !this.data.open
})
this.triggerEvent("aaa", {
a: 0
})
// 更新数据
const selectDate = new Date(this.data.selectDay.year, this.data.selectDay.month - 1, this.data.selectDay.day)
if (this.data.oldCurrent === 0) {
this.updateList(selectDate, -1, 2)
this.updateList(selectDate, 0, 0)
this.updateList(selectDate, 1, 1)
} else if (this.data.oldCurrent === 1) {
this.updateList(selectDate, -1, 0)
this.updateList(selectDate, 0, 1)
this.updateList(selectDate, 1, 2)
} else if (this.data.oldCurrent === 2) {
this.updateList(selectDate, -1, 1)
this.updateList(selectDate, 0, 2)
this.updateList(selectDate, 1, 0)
}
this.setSwiperHeight(this.data.oldCurrent)
},
// 选中并切换今日日期
switchNowDate() {
const now = new Date()
const selectDate = new Date(this.data.selectDay.year, this.data.selectDay.month - 1, this.data.selectDay.day)
let dateDiff = (selectDate.getFullYear() - now.getFullYear()) * 12 + (selectDate.getMonth() - now.getMonth())
let diff = dateDiff === 0 ? 0 : dateDiff > 0 ? -1 : 1
const diffSum = (x) => (3 + (x % 3)) % 3
if (this.data.oldCurrent === 0) {
this.updateList(now, -1, diffSum(2 + diff))
this.updateList(now, 0, diffSum(0 + diff))
this.updateList(now, 1, diffSum(1 + diff))
} else if (this.data.oldCurrent === 1) {
this.updateList(now, -1, diffSum(0 + diff))
this.updateList(now, 0, diffSum(1 + diff))
this.updateList(now, 1, diffSum(2 + diff))
} else if (this.data.oldCurrent === 2) {
this.updateList(now, -1, diffSum(1 + diff))
this.updateList(now, 0, diffSum(2 + diff))
this.updateList(now, 1, diffSum(0 + diff))
}
this.setData({
swiperCurrent: diffSum(this.data.oldCurrent + diff),
oldCurrent: diffSum(this.data.oldCurrent + diff),
backChange: dateDiff !== 0,
})
this.setData({
selectDay: {
year: now.getFullYear(),
month: now.getMonth() + 1,
day: now.getDate()
}
}, () => {
this.triggerEvent("selectDay", this.data.selectDay)
})
this.setSwiperHeight(this.data.oldCurrent)
},
//日历主体的渲染方法
dateInit({
setYear,
setMonth,
setDay = this.data.selectDay.day,
hasBack = false
} = {
setYear: this.data.selectDay.year,
setMonth: this.data.selectDay.month,
setDay: this.data.selectDay.day,
hasBack: false
}) {
let dateList = []; //需要遍历的日历数组数据
let now = new Date(setYear, setMonth - 1) //当前月份的1号
let startWeek = now.getDay(); //目标月1号对应的星期
let resetStartWeek = startWeek == 0 ? 6 : startWeek - 1; //重新定义星期将星期天替换为6其余-1
let dayNum = new Date(setYear, setMonth, 0).getDate() //当前月有多少天
let forNum = Math.ceil((resetStartWeek + dayNum) / 7) * 7 //当前月跨越的周数
let selectDay = setDay ? setDay : this.data.selectDay.day
this.triggerEvent("getDateList", {
setYear: now.getFullYear(),
setMonth: now.getMonth() + 1
})
if (this.data.open) {
//展开状态,需要渲染完整的月份
for (let i = 0; i < forNum; i++) {
const now2 = new Date(now)
now2.setDate(i - resetStartWeek + 1)
let obj = {};
obj = {
day: now2.getDate(),
month: now2.getMonth() + 1,
year: now2.getFullYear()
};
dateList[i] = obj;
}
} else {
//非展开状态,只需要渲染当前周
for (let i = 0; i < 7; i++) {
const now2 = new Date(now)
//当前周的7天
now2.setDate(Math.ceil((selectDay + (startWeek - 1)) / 7) * 7 - 6 - (startWeek - 1) + i)
let obj = {};
obj = {
day: now2.getDate(),
month: now2.getMonth() + 1,
year: now2.getFullYear()
};
dateList[i] = obj;
}
}
if (hasBack) {
return dateList
}
this.setData({
dateList1: dateList
})
},
//一天被点击时
selectChange(e) {
const year = e.currentTarget.dataset.year
const month = e.currentTarget.dataset.month
const day = e.currentTarget.dataset.day
const selectDay = {
year: year,
month: month,
day: day,
}
if (this.data.open && (this.data.selectDay.year !== year || this.data.selectDay.month !== month)) {
if ((year * 12 + month) > (this.data.selectDay.year * 12 + this.data.selectDay.month)) { // 下个月
if (this.data.oldCurrent == 2)
this.setData({
swiperCurrent: 0
})
else
this.setData({
swiperCurrent: this.data.oldCurrent + 1
})
} else { // 点击上个月
if (this.data.oldCurrent == 0)
this.setData({
swiperCurrent: 2
})
else
this.setData({
swiperCurrent: this.data.oldCurrent - 1
})
}
this.setData({
['selectDay.day']: day
}, () => {
this.triggerEvent("selectDay", this.data.selectDay)
})
} else if (this.data.selectDay.day !== day) {
this.setData({
selectDay: selectDay
}, () => {
this.triggerEvent("selectDay", this.data.selectDay)
})
}
},
updateList(date, offset, index) {
if (this.data.open) { //打开状态
const setDate = new Date(date.getFullYear(), date.getMonth() + offset * 1) //取得当前日期的上个月日期
this.getIndexList({
setYear: setDate.getFullYear(),
setMonth: setDate.getMonth(),
dateIndex: index
})
} else {
const setDate = new Date(date.getFullYear(), date.getMonth(), date.getDate() + offset * 7) //取得当前日期的七天后的日期
this.getIndexList({
setYear: setDate.getFullYear(),
setMonth: setDate.getMonth(),
setDay: setDate.getDate(),
dateIndex: index
})
}
},
},
lifetimes: {
attached() {
let now = this.data.defaultTime ? new Date(this.data.defaultTime) : new Date()
let selectDay = {
year: now.getFullYear(),
month: now.getMonth() + 1,
day: now.getDate()
}
this.setData({
nowDay: {
year: now.getFullYear(),
month: now.getMonth() + 1,
day: now.getDate()
}
})
this.setMonth(selectDay.year, selectDay.month, selectDay.day)
this.updateList(now, -1, 0)
this.updateList(now, 0, 1)
this.updateList(now, 1, 2)
this.setSwiperHeight(1)
}
},
observers: {}
})

calendar.json

1
2
3
4
{
"component": true,
"usingComponents": {}
}

calendar.wxss

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
27
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
/* component/calendar/calendar.wxss */

.icon {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACcUlEQVRYhe2WvWtUURDFf8dNVk1ETViMX4WNna22CopELBQUkYASgzFFUPBPsLGSgAqJGDRGBEUsRA0KIthaip2I/4CEoIR8JyP33lldl7fuR1AL9xSP3Xfvmzlz7pl5jyaa+O+hq0NDqDEV8sBGYA3wDZirP4TFhxvFApAD1gPz5TGqF2Xx2pK5puwA9uuPPUA30A48Bd43Ukg2gWpQTHwe2Iax5GRuA6//BoFuwQDQaUF6xaPYBFzCYryXf4yAoAc44We/INjgJvzqGwYwWg2e1Rqzogmt9MwTjgGngBXvgLB8Axh1HyiuiV7BwVoJ1KpAqPqMu70dtFbYNWDC7SqwfoMp0r9BLHbHi2qBa2nDk0A/aAmUF9oscR1pAikJIR4g3RXqUFIoxD0nOLQqAoLjwFlgOuyVKCDGve3KMY54BOqMrBTVDSQON0rgqLfajJCF5IJ7SmdeifBIICgISgQSbUCPYH+9BI4Ag8BidLzY4VXf+V01TmIM9BhU8Gc7gNOCfVn7s0y4N1SuNNuXhbYDDwU3qyX/QUIMm7Eo6HNj7gQueKe8KyfQ4guh2gPAZW+zWdAW4Al1JC+RYjTMBKA3xYpdcZE0O974rlxIXvDeDhv6gF1Kg6UrnrcYqTv5TwwLpi0VFRTd6oTmQB/AckUPrMS5LjpR9MU6n+2rSV7EfRQH1orSwOoC2w0WlQ+XZZ9qs/57CnQFLMnk47A4FdXYx8MYMJl8ECN9hPgSyxdNGM7qC3DLibyt9Ea3rHtmFddK8DzlURvYZ1c5GrA4ueZBr5S8EPp3JjNgyc061QgvsE9gk/4x0+qKN9FEE/8QwHd9qo6ectzgFAAAAABJRU5ErkJggg==");
background-size: 100% auto;
width: 32rpx;
height: 32rpx;
}

.flex {
display: flex;
justify-content: space-between;
align-items: center;
}

.swiper {
transition: height 0.3s;
}

.header-wrap {
display: flex;
justify-content: space-between;
align-items: center;
}

.today {
width: 88rpx;
height: 42rpx;
background: #F3F4F4;
border-radius: 22rpx;
font-size: 24rpx;
line-height: 42rpx;
color: #868D8D;
text-align: center;
margin-right: 6rpx;
}

.today:active {
background: #dfdfdf;
color: #5f6464;
}

.direction-column {
flex-direction: column;
}

.flex1 {
flex: 1;
}

.flex-center {
display: flex;
justify-content: center;
align-items: center;
}

.flex-start {
display: flex;
justify-content: flex-start;
align-items: center;
}

.flex-end {
display: flex;
justify-content: flex-end;
align-items: center;
}

.flex-around {
display: flex;
justify-content: space-around;
align-items: center;
}

.flex-wrap {
flex-wrap: wrap;
}

.align-start {
align-items: flex-start;
}

.align-end {
align-items: flex-end;
}

.align-stretch {
align-items: stretch;
}

.calendar {
font-family: "PingFang SC", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", Helvetica, Arial, "Hiragino Sans GB", "Source Han Sans", "Noto Sans CJK Sc", "Microsoft YaHei", "Microsoft Jhenghei", sans-serif;
}

.calendar .title {
padding: 10rpx 16rpx 10rpx 20rpx;
line-height: 60rpx;
font-size: 32rpx;
font-weight: 600;
color: #1C2525;
line-height: 44px;
letter-spacing:1px;
}

.calendar .title .year-month {
margin-right: 20rpx;
}

.calendar .title .icon {
padding: 0 16rpx;
font-size: 32rpx;
color: #999;
}

.calendar .title .open {
background-color: #f6f6f6;
color: #999;
font-size: 22rpx;
line-height: 36rpx;
border-radius: 18rpx;
padding: 0 14rpx;
}

.list-open {
position: relative;
justify-content: center;
}

.list-open .icon::after {
content: '';
position: absolute;
top: 16rpx;
right: 60rpx;
display: block;
width: 278rpx;
height: 0rpx;
border-bottom: 2rpx solid rgba(214, 219, 219, 0.68);
}

.list-open .icon::before {
content: '';
position: absolute;
top: 16rpx;
left: 60rpx;
display: block;
width: 278rpx;
height: 0rpx;
border-bottom: 2rpx solid rgba(214, 219, 219, 0.68);
}

.fold {
transform: rotate(0deg);
}

.unfold {
transform: rotate(180deg);
}

.calendar .calendar-week {
line-height: 40rpx;
padding: 0 25rpx;
font-size: 28rpx;
color: #999;
}

.calendar .calendar-week .view {
width: 100rpx;
text-align: center;
}

.calendar .calendar-main {
padding: 18rpx 25rpx 0rpx;
transition: height 0.3s;
align-content: flex-start;
overflow: hidden;
}

.calendar .calendar-main .day {
position: relative;
width: 100rpx;
color: #666;
text-align: center;
height: 82rpx;
}

.calendar .calendar-main .day .bg {
height: 66rpx;
line-height: 66rpx;
font-size: 28rpx;
color: #333;
}

.calendar .calendar-main .day .now {
width: 66rpx;
border-radius: 50%;
text-align: center;
color: #0EC0B8;
background: rgba(14, 192, 184, 0.2);
margin: 0 auto;
}

.calendar .calendar-main .day .select {
width: 66rpx;
border-radius: 50%;
text-align: center;
color: #fff;
background: #0EC0B8;
margin: 0 auto;
}

.calendar .calendar-main .day .spot::after {
position: absolute;
content: "";
display: block;
width: 8rpx;
height: 8rpx;
bottom: 22rpx;
background: #0EC0B8;
border-radius: 50%;
left: 0;
right: 0;
margin: auto;
}

.calendar .calendar-main .day .deep-spot::after {
position: absolute;
content: "";
display: block;
width: 8rpx;
height: 8rpx;
bottom: 22rpx;
background: #FF7416;
border-radius: 50%;
left: 0;
right: 0;
margin: auto;
}

.calendar .calendar-main .day .other-month {
color: #ccc;
}

.header-wrap .month {
font-size: 28rpx;
color: #929797;
line-height: 40rpx;
}