小程序实现日历打卡功能

本文实例为大家分享了小程序实现日历打卡功能的具体代码,供大家参考,具体内容如下

一、效果图展示

老惯例,先上效果图

二、实现思路

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页内脚本,渲染“已打卡”的标志(√)

<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&&currentMonth==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】

.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】

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】

<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】

.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】

// 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】

{   "usingComponents": {     "top-title":"../../../components/topTitle/topTitle",     "calendar":"../components/calendar/calendar"   } }

推荐阅读