老惯例,先上效果图
1、日历展示
例如下图中:
2021月7月打卡日历页面,共35个日期数据,上月残余4天+本月31天;
2021月6月打卡日历页面,共35个日期数据,上月残余2天+本月30天+下月残余3天;
2021月5月打卡日历页面,共42个日期数据,上月残余6天+本月31天+下月残余5天。
【结论】打卡日历页面存在展示35个或42个日期数据的情况,35个或42个日期数据=当前显示月所有日期数据+上月残余尾部日期+下月残余头部日期。
计算出每个月的日期天数,获取本月1号是周几,上月残余天数=本月1号的星期X的X数(比如,2021年7月1日是星期四,则上月残余4天),假设 a=35-本月天数-上月残余天数。如果a>=0,则下月残余天数=a;如果a<0,则下月残余天数=7+a (比如2021年5月,35-37=-2;7+(-2)=5)
2、打卡功能
打卡实现的功能:可打卡的日期为今日日期或今日日期之前未打卡过的日期。
如图:今日日期为绿色圆形背景,当前点击日期为橙色圆形背景;可打卡时,打卡按钮背景为蓝色,不可打卡时,打卡背景为灰色;√ 代表已打卡。
通过数据库查询当前已打卡的数据,已打卡的数据需要设置打卡按钮禁用标志。打卡按钮禁用的情况(1)页面初始化时,未点击任何日期(2)当前点击的日期在今天之后(3)当前日期在今天之前但已打卡。
点击打卡,记录打卡日期并保存至数据库。
1、数据库数据
2、日历组件
【calendar.wxml】
代码里使用了WXS页内脚本,渲染“已打卡”的标志(√)
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 |
<view class="calendar"> <view class='tit'> <view class='pre' bindtap='gotoPreMonth'>{{'《'}}</view> <view class='current'>{{currentYear}}年{{currentMonth}}月</view> <view class='next' bindtap='gotoNextMonth'>{{'》'}}</view> </view> <view class='w100P showData'> <view class="week" style='color: #999'>日</view> <view class="week">一</view> <view class="week">二</view> <view class="week">三</view> <view class="week">四</view> <view class="week">五</view> <view class="week" style='color: #999'>六</view> </view> <view class='content'> <view wx:for="{{allArr}}" wx:key="index" class='itemData' data-current="{{item.month == 'current' ? '1' : '0'}}" data-day='{{item.date}}' bindtap='clickDate'> <view class="{{item.month == 'current' ? '' : 'gray'}}" style="height:44px;width:44px;line-height:30px;{{nowYear==currentYear&¤tMonth==nowMonth&&item.date==nowDate?'color:#fff;background:#33D4C5;border-radius:100px':''}};{{item.month == 'current'&&selectedYear==currentYear&&selectedMonth==currentMonth&&item.date==selectedDate?'color:#fff;background:orange;border-radius:100px':''}} "> {{item.date}} <view> <icon wx:if="{{item.month == 'current'&&dataProcess.filterDate(currentPunchCardDate,item.date)}}" class="icon" color="#F44336" type="success_no_circle" size="15"></icon> </view> </view> </view> </view> <view class="btn-wrapper" bindtap="gotoToday"> <button class="btn">回今天</button> </view> <!-- wxs页面内脚本,在渲染层做数据处理 --> <wxs module="dataProcess"> function filterDate(currentPunchCardDate,date){ if(currentPunchCardDate.indexOf(date)!==-1){ return true } } module.exports={ filterDate:filterDate } </wxs> </view> |
【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 |
.calendar { width: 100%; background: #fff; }
.pre, .next { color: #33D4C5; text-align: center; line-height: 20px; }
.calendar .tit { display: flex; justify-content: center; align-items: center; padding: 40rpx 0;
}
.current { font-size: 32rpx; color: #2A2A2A; }
.calendar .tit .current { margin: 0 60rpx; }
.showData { display: flex; justify-content: center; align-items: center; box-sizing: border-box; padding-left: 25rpx; padding-right: 25rpx; }
.showData .week { width: 14%; height: 70rpx; line-height: 70rpx; text-align: center; flex-shrink: 0; font-size: 30rpx; color: #2A2A2A; }
.calendar .content { display: flex; flex-wrap: wrap; box-sizing: border-box; padding-left: 25rpx; padding-right: 25rpx; }
.calendar .content .itemData { width: 14.2%; height: 90rpx; line-height: 90rpx; flex-shrink: 0; font-size: 30rpx; color: #2A2A2A; text-align: center; display: flex; align-items: center; justify-content: center; }
.calendar .content .icon { position: relative; top: -25rpx; }
.calendar .content .gray { color: #999; }
.currentSelected { color: #fff; background: #1CA2FC; border-radius: 100px; }
.calendar .btn-wrapper { text-align: right; background-color: #fff; width: 100%; padding-bottom: 10rpx; }
.calendar .btn-wrapper .btn { border: 1px solid #33D4C5; padding: 5rpx; width: 95rpx; font-size: 21rpx; color: #33D4C5; border-radius: 20rpx; margin-bottom: 15rpx; position: relative; left: calc(50% - 100rpx); } |
【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 |
Component({ /** * 组件的属性列表 */
properties: { currentPunchCardDate: { type: Array, value: [] }, currentYear: { // 当前页面显示的年 type: Number, value: new Date().getFullYear() }, currentMonth: { // 当前页面显示的年月 type: Number, value: new Date().getMonth() + 1 }, nowYear: { // 当前年 type: Number, value: new Date().getFullYear() }, nowMonth: { // 当前月 type: Number, value: new Date().getMonth() + 1 }, nowDate: { // 当前日 type: Number, value: new Date().getDate() },
},
/** * 组件的初始数据 */ data: { currentMonthDateLen: 0, // 当月天数 preMonthDateLen: 0, // 当月中,上月多余天数 allArr: [], // 35个或42个日期数据=当前显示月所有日期数据+上月残余尾部日期+下月残余头部日期 nowDate: null, selectedDate: null, //当前选择日期 selectedMonth: null, //当前选择月 selectedYear: null, //当前选择年 }, // 用observers监听properties的属性值 observers: { 'currentPunchCardDate': function (val) { console.log(val)
} }, // 在组件实例刚刚被创建时执行 created() {}, // 在组件实例进入页面节点树时执行 ready() { this.getAllArr() }, /** * 组件的方法列表 */ methods: { // 获取某年某月天数:下个月1日-本月1日 getDateLen(year, month) { let actualMonth = month - 1; let timeDistance = new Date(year, month) - new Date(year, actualMonth); return timeDistance / (1000 * 60 * 60 * 24); }, // 获取某月1号是周几 getFirstDateWeek(year, month) { // 0-6,0代表周天 return new Date(year, month - 1, 1).getDay() }, // 上月 preMonth(year, month) { if (month == 1) { return { year: --year, month: 12 } } else { return { year: year, month: --month } } }, // 下月 nextMonth(year, month) { if (month == 12) { return { year: ++year, month: 1 } } else { return { year: year, month: ++month } } }, // 获取当月数据,返回数组 getCurrentArr() { let currentMonthDateLen = this.getDateLen(this.data.currentYear, this.data.currentMonth) // 获取当月天数 let currentMonthDateArr = [] // 定义空数组 if (currentMonthDateLen > 0) { for (let i = 1; i <= currentMonthDateLen; i++) { currentMonthDateArr.push({ month: 'current', // 只是为了增加标识,区分上下月 date: i }) } } this.setData({ currentMonthDateLen }) return currentMonthDateArr }, // 获取当月中,上月多余的日期数据,返回数组 getPreArr() { let preMonthDateLen = this.getFirstDateWeek(this.data.currentYear, this.data.currentMonth) // 当月1号是周几 == 上月残余天数) console.log("preMonthDateLen=", preMonthDateLen); let preMonthDateArr = [] // 定义空数组 if (preMonthDateLen > 0) { let { year, month } = this.preMonth(this.data.currentYear, this.data.currentMonth) // 获取上月 年、月 let date = this.getDateLen(year, month) // 获取上月天数 for (let i = 0; i < preMonthDateLen; i++) { preMonthDateArr.unshift({ // 尾部追加 month: 'pre', // 只是为了增加标识,区分当、下月 date: date }) date-- } } this.setData({ preMonthDateLen }) return preMonthDateArr }, // 获取当月中,下月多余的日期数据,返回数组 getNextArr() { let nextMonthDateLen = 35 - this.data.preMonthDateLen - this.data.currentMonthDateLen // 下月多余天数 console.log(" nextMonthDateLen=", nextMonthDateLen); let nextMonthDateArr = [] // 定义空数组 if (nextMonthDateLen > 0) { for (let i = 1; i <= nextMonthDateLen; i++) { nextMonthDateArr.push({ month: 'next', // 只是为了增加标识,区分当、上月 date: i }) } } else if (nextMonthDateLen < 0) { for (let i = 1; i <= (7 + nextMonthDateLen); i++) { nextMonthDateArr.push({ month: 'next', // 只是为了增加标识,区分当、上月 date: i }) }
} return nextMonthDateArr }, // 整合当月所有日期数据=上月残余+本月+下月多余 getAllArr() { let preArr = this.getPreArr() let currentArr = this.getCurrentArr() let nextArr = this.getNextArr() let allArr = [...preArr, ...currentArr, ...nextArr] this.setData({ allArr }) let sendObj = { currentYear: this.data.currentYear, currentMonth: this.data.currentMonth, currentDate: this.data.selectedDate, allArr: this.data.allArr, } // 向父组件发送数据 this.triggerEvent('sendObj', sendObj)
}, // 点击 上月 gotoPreMonth() { let { year, month } = this.preMonth(this.data.currentYear, this.data.currentMonth) this.setData({ currentYear: year, currentMonth: month, }) this.getAllArr() }, // 点击 下月 gotoNextMonth() { let { year, month } = this.nextMonth(this.data.currentYear, this.data.currentMonth) this.setData({ currentYear: year, currentMonth: month, }) this.getAllArr() }, // 点击日期 clickDate(e) { var date = e.currentTarget.dataset.day; var current = e.currentTarget.dataset.current; if (current == 0) { if (date > 6) { // 点击上月日期--去上个月 var { year, month } = this.preMonth(this.data.currentYear, this.data.currentMonth) this.gotoPreMonth() } else { // 点击下月 var { year, month } = this.nextMonth(this.data.currentYear, this.data.currentMonth) this.gotoNextMonth() } } else { var year = this.data.currentYear; var month = this.data.currentMonth; } this.setData({ selectedYear: year, selectedMonth: month, selectedDate: date, }) console.log("当前选择日期", year, "-", month, "-", date); console.log(this.data.selectedDate); wx.nextTick(() => { this.getAllArr() })
}, // 回今天 gotoToday() { this.setData({ currentYear: this.data.nowYear, currentMonth: this.data.nowMonth, }) this.getAllArr() } } }) |
3、打卡及统计
【calendarCard.wxml】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<view class="page-wrapper"> <top-title toptitle="打卡日历" backImgFlag="true"></top-title> <calendar bind:sendObj="getObj" currentPunchCardDate="{{punchCardDateArr}}"></calendar> <view class="btn-wrapper"> <button class="btn" type="primary" disabled="{{ disabledFlag}}" bindtap="punchCard">打 卡</button> </view> <view class="record-wrapper"> <view class="title"> <image class="img" src="{{icon}}"></image> {{name}}打卡统计 </view> <view class="record"> <view class="record-item"> <view class="top"><text class="num">{{monthDays}}</text> 天</view> <view class="bottom">本月坚持天数</view> </view> <view class="record-item"> <view class="top"><text class="num"> {{totalDays}}</text> 天</view> <view class="bottom">总共坚持天数</view> </view> </view> </view> </view> |
【calendarCard.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 |
.page-wrapper { background-color: #fff; height: 100vh; } .page-wrapper .btn-wrapper .btn { width: 95vw; border-radius: 40rpx; height: 80rpx; font-size: 30rpx; background-color: #27d6f5; padding: 20rpx; } .page-wrapper .btn-wrapper .btn[disabled] { background-color: #e7e5e5; } .page-wrapper .record-wrapper { padding: 20rpx; } .page-wrapper .record-wrapper .title { color: #444; font-weight: bold; } .page-wrapper .record-wrapper .title .img { width: 60rpx; height: 60rpx; position: relative; top: 18rpx; } .page-wrapper .record-wrapper .record { display: flex; justify-content: space-around; margin-top: 20rpx; } .page-wrapper .record-wrapper .record .record-item { text-align: center; font-size: 24rpx; color: #a3a3a3; } .page-wrapper .record-wrapper .record .record-item .top { height: 80rpx; line-height: 80rpx; border-bottom: 1px solid #ececec; color: #333; } .page-wrapper .record-wrapper .record .record-item .top .num { font-size: 44rpx; font-weight: bold; color: #F44336; } |
【calendarCard.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 |
// miniprogram/pages/punchCard/calendarCard/calendarCard.js Page({
/** * 页面的初始数据 */ data: { id: null, name: "", icon: "", disabledFlag: true, totalDays:0, monthDays:0, habitInfo: {}, currentDate: null, currentMonth: null, currentYear: null, nowYear: new Date().getFullYear(), nowMonth: new Date().getMonth(), nowDate:new Date().getDate(), punchCardDateArr: [] //用于存放当月打卡日期-日
}, /** * 生命周期函数--监听页面加载 */ onLoad: function (options) { console.log(options); this.setData({ id: options.id, name: options.name, icon: options.icon }) var nowYear = new Date().getFullYear() var nowMonth = new Date().getMonth() wx.nextTick(() => { this.getHabitInfo(nowYear, nowMonth) }) }, // 获取子组件的数据 getObj(e) { console.log("获取子组件的数据", e); this.setData({ currentDate: e.detail.currentDate, currentMonth: e.detail.currentMonth, currentYear: e.detail.currentYear, }) this.getHabitInfo(e.detail.currentYear, e.detail.currentMonth - 1) }, // 获取当月的打卡数据 getHabitInfo(year, month) { // 注意month范围 0-11,0代表1月 const db = wx.cloud.database() db.collection('habitList').where({ _id:this.data.id, }).get().then(res => { // console.log("从数据库获取数据[res]===", res); var dateTimeArr = res.data[0].dateTime var dateArr = [] dateTimeArr.forEach((item) => { if (item.getFullYear() == year && item.getMonth() == month) { dateArr.push(item.getDate()) } }) console.log(year, month,this.data.currentDate); if (!this.data.currentDate ||(year==this.data.nowYear && month>this.data.nowMonth)||(year==this.data.nowYear && month==this.data.nowMonth &&this.data.currentDate>this.data.nowDate) ) { // 打卡按钮禁用的情况(1)页面初始化时,未点击任何日期(2)当前点击的日期在今天之后 var flag = true } else { // 打卡按钮禁用的情况 (3)当前日期已打卡 var flag = dateArr.indexOf(this.data.currentDate) == -1 ? false : true }
this.setData({ habitInfo: res.data[0], punchCardDateArr: dateArr, disabledFlag: flag, totalDays:dateTimeArr.length, monthDays:dateArr.length })
}).catch(err => { console.log(err); }) }, // 点击打卡按钮-打卡 punchCard() { console.log(this.data.currentYear, this.data.currentMonth - 1, this.data.currentDate); var currentTime = new Date(this.data.currentYear, this.data.currentMonth - 1, this.data.currentDate) const db = wx.cloud.database() db.collection('habitList').doc(this.data.id).update({ data: { dateTime:db.command.push(currentTime) }, success: res => { wx.showToast({ title: '打卡成功', }) this.getHabitInfo(this.data.currentYear, this.data.currentMonth - 1) }, fail: err => { wx.showToast({ icon: 'none', title: '新增记录失败' }) console.error('[数据库] [新增记录] 失败:', err) } }) } }) |
【calendarCard.json】
1 2 3 4 5 6 |
{ "usingComponents": { "top-title":"../../../components/topTitle/topTitle", "calendar":"../components/calendar/calendar" } } |