尧图精选

微信小程序云开发学习打卡系统源码解析:从倒计时到数据库权限

🕒 发布时间:2026/9/11 17:56:41 📁 来源:尧图网络
简介面向微信小程序学习者与课程设计开发者提供一套基于云开发的日常学习打卡系统完整方案。系统实现登录、自选任务、设定倒计时专注、在线打卡文字图片、查看打卡记录与点赞等功能覆盖小程序前端与云开发后端的典型交互逻辑可支撑论文写作与功能扩展。压缩包共68个文件约721KB含js、json、wxss、wxml等前后端核心代码png界面与数据库截图另附mp4演示视频、doc需求说明和md说明文档便于快速理解项目结构。已有250人学习下载适合正在做课程设计或毕业设计、需要可运行参考项目的小程序开发者。资源目录清晰pages目录分登录、首页、记录、设置等多个页面cloud目录为云函数模块读者可结合演示视频与需求文档快速上手并针对自己的业务场景扩展计时规则、积分排行等功能。1. 一个带倒计时的日常学习打卡系统为什么值得拆很多人做课程设计或毕设时选打卡类题目最后做成一个签到按钮加记录列表答辩时没东西可讲。这套基于微信小程序的日常学习打卡系统不一样的地方在于它把专注计时和打卡记录强绑在一起登录后自选任务、设定时长倒计时结束才能填文字、传图片完成打卡之后还能看历史记录、点赞互动。每个环节都有数据可查论文里能展开的技术点就多了——状态流转、云开发、数据库权限、并发点赞都能单独成节。工程包里带了源码、需求文档、数据库截图和演示视频工程内部代号 focus-clock整体走云开发架构不需要自建服务器。适合两类人一是要交课程设计或毕业设计的学生二是想快速起一个云开发小程序、需要参考完整工程结构的开发者。这篇就按代码组织、倒计时状态流转、数据读写、部署排错四个层次把源码拆开讲照着能跑通也能讲清楚。2. 云开发架构与工程骨架先搞清楚代码是怎么组织的拿到工程文件夹一眼扫过去有 app.js、app.json、project.config.json、sitemap.json还有 cloud 目录pages 下面按功能拆了若干页面。这是微信小程序的标准工程布局但它没有用自建服务器而是走了云开发——数据库、存储、云函数都在微信侧托管免去备案和服务器运维对课程设计来说是最省事的选型答辩时也能讲明白无服务器后端这个架构。2.1 为什么选云开发而不是自建后端云开发TCB的核心能力是三块云数据库、云存储、云函数。客户端通过wx.cloud.database()直接读写数据库图片走wx.cloud.uploadFile传到云存储涉及 openid 鉴权的逻辑丢给云函数。我一般会跟学生说除非你的项目需要回调外部 API、需要定时任务否则云开发完全够用。这个工程的 cloud 目录里就有 getOpenID 云函数说明作者至少用云函数解决了用户身份识别这是云开发项目里最标准的切入点。入口在 app.js冷启动时要先初始化云环境App({ onLaunch() { if (!wx.cloud) { console.error(请使用 2.2.3 或以上的基础库以使用云能力); } else { wx.cloud.init({ env: your-env-id, traceUser: true }); } } });env填的是云开发控制台里的环境 ID不是环境名称traceUser: true会在控制台的用户管理里记录访问来源方便后面看用户列表。如果你本机调试一直报环境找不到九成是这个 ID 复制错了。另外 require 云能力前先判断wx.cloud是否存在是为了兼容旧版基础库这个兜底判断建议保留。2.2 pages 目录与路由注册小程序的路由不像 Vue Router 那样集中管理而是在 app.json 的 pages 数组里按顺序注册。这个工程 pages 下有 login、index、about、posts、register、setting、logs、version 等页面分工大致如下页面职责关键交互login登录与身份获取调用云函数 getOpenIDindex任务列表与选择读取任务点击进入计时posts打卡记录流查看记录点赞logs个人打卡日志按时间倒序展示register任务登记新增自定义任务setting参数设置默认专注时长等about / version项目说明页课程设计说明、版本信息以 login 开头的注册顺序意味着小程序冷启动后先进登录页。如果你想让 index 做首页就要把 index 移到数组第一项或者在 index 的 onLoad 里做未登录跳转。很多新手页面文件建好了但忘记注册进数组编译直接报 page is not found问题就出在这里。{ pages: [ pages/login/login, pages/index/index, pages/posts/posts, pages/register/register, pages/setting/setting, pages/logs/logs, pages/about/about, pages/version/version ], window: { navigationBarTitleText: 学习打卡, navigationBarBackgroundColor: #ffffff } }window 配置里的 navigationBarTitleText 是顶栏标题navigationBarBackgroundColor 是顶栏颜色。如果你觉得默认顶栏高度和胶囊按钮对不齐可以在页面 json 里配navigationStyle: custom自己画导航但要注意自定义导航后要手动避开状态栏否则会顶到最上面。2.3 sitemap.json、样式与静态资源sitemap.json 决定哪些页面可以被微信搜索索引到默认规则是全部允许{ desc: 关于本文件的更多信息请参考文档, rules: [ { action: allow, page: * } ] }如果你不想让 register 这类内部页面被索引把 action 改成 disallow、page 指向具体路径即可。这个文件不影响编译运行开发者工具控制台偶尔会提示 sitemap 警告不用管。工程里的 weui-cell.wxss 是从 WeUI 组件库抽出来的单元格样式列表和表单复用它比自己手写 cell 的边框、间距要稳这是一个值得保留的做法——小程序里手写 1px 分割线经常出现对不齐的问题。image 目录下那些 shezhi、jilu、wechatHL 命名的 png 是设置、记录、登录等入口的图标资源替换时保持同名覆盖即可。3. 专注倒计时的状态流转从选任务到打卡成功这个系统的核心不在打卡按钮而在计时和打卡之间的强绑定不完成倒计时就没有打卡入口。拆代码时最值得看的也是这段逻辑——用户选完任务后页面怎么进入倒计时倒计时结束前为什么不开放打卡中途退出后状态怎么恢复。理解了状态流转整条主流程就通了。3.1 任务与计时状态设计先把任务和打卡记录拆成两个数据维度。任务只描述做什么打卡记录描述某次专注的结果。一次完整的专注过程有四个状态idle空闲用户还没选任务focusing倒计时进行中finished计时结束可以填写打卡内容checked打卡已提交记录落库实现上用数字表示状态比如 status 取值 0/1/2/3比多个布尔变量干净得多。后续如果要加暂停功能只需在 focusing 里再拆一个 pause 子状态改动面很小。这是答辩时能讲清楚的第一个设计点用状态机而不是散落的 if 来控制页面行为。3.2 倒计时页面的实现核心倒计时逻辑在页面 js 里常见做法是 setInterval 每秒递减剩余秒数const TOTAL_SECONDS 25 * 60; // 25 分钟专注 let remain TOTAL_SECONDS; let timer null; startCountdown() { this.setData({ status: 1 }); timer setInterval(() { remain--; if (remain 0) { clearInterval(timer); this.setData({ status: 2 }); // 进入 finished开放打卡表单 return; } this.setData({ remain }); }, 1000); },setInterval 的间隔是 1000ms但必须清楚它并不精确小程序切后台后定时器会被挂起回到前台时 remain 会明显偏大倒计时就变慢了。常见做法是记录开始时间戳每次渲染时用当前时间反推剩余秒数const endAt Date.now() TOTAL_SECONDS * 1000; function refreshRemain() { const remain Math.max(0, Math.round((endAt - Date.now()) / 1000)); if (remain 0) { clearInterval(timer); this.setData({ status: 2 }); } else { this.setData({ remain }); } } timer setInterval(refreshRemain, 1000);这样即使定时器被系统延迟执行显示值也不会偏移因为始终以endAt - Date.now()为准。时长参数可以放到 setting 页让用户配置 15/25/45 分钟三档用wx.setStorageSync(focusMinutes, 25)存本地页面加载时读取并给默认值兜底const minutes wx.getStorageSync(focusMinutes) || 25;3.3 计时结束后的打卡表单计时进入 finished 状态后页面出现表单区域包含文本输入和图片选择。图片选择用wx.chooseMedia这是基础库 2.10.0 以后推荐替代wx.chooseImage的接口chooseImage() { wx.chooseMedia({ count: 1, mediaType: [image], sourceType: [album, camera], success: (res) { this.setData({ imagePath: res.tempFiles[0].tempFilePath }); } }); }count 限制可选数量为 1mediaType 只允许图片sourceType 同时开放相册和相机。拿到临时路径后要上传到云存储才能得到可长期访问的 fileIDasync uploadImage() { const ext this.data.imagePath.match(/\.(\w)$/)[1]; const cloudPath checkin/${this.data.openid}_${Date.now()}.${ext}; const res await wx.cloud.uploadFile({ cloudPath, filePath: this.data.imagePath }); return res.fileID; }cloudPath 必须保证全局唯一否则不同用户上传同名文件会互相覆盖。我的习惯是把 openid 加时间戳拼进去。上传成功后返回的 fileID 可以直接塞给image组件的 src不需要自己拼域名。注意wx.chooseMedia需要真机调试才能完整测相机路径开发者工具里只能走相册。3.4 打卡记录落库表单校验通过后向云数据库的 checkins 集合写入一条记录const db wx.cloud.database(); await db.collection(checkins).add({ data: { taskName: this.data.taskName, content: this.data.content, imageFileID: this.data.imageFileID, duration: TOTAL_SECONDS, openid: this.data.openid, createTime: db.serverDate(), likeCount: 0 } });db.serverDate()生成的是数据库服务端时间比客户端本地时间可靠避免用户手机时间不准导致排序错乱。duration 存的是本次专注秒数后面统计每日学习时长、算日均活跃都会用到。到这一步选任务 → 倒计时 → 填内容传图 → 落库的主链路就完整跑通了。4. 打卡记录流与点赞云数据库的读写设计上一章把写数据的链路打通了这一章说读打卡记录怎么展示成信息流点赞这种高频小写操作怎么做才不踩坑。posts 页面就是记录流logs 是个人维度两者本质都是对 checkins 集合的查询区别只在筛选条件一个查全部一个按 openid 过滤。4.1 集合结构与字段约定结合数据库截图和源码推断这个项目至少有 users、tasks、checkins 三个集合。一次打卡会产生一条 checkins 记录字段设计如下集合关键字段说明usersopenid, nickname, avatar, totalMinutes用户维度聚合数据tasksname, creatorOpenid, defaultMinutes可自定义的学习任务checkinstaskName, content, imageFileID, duration, openid, createTime, likeCount打卡主记录一个关键设计是 checkins 里冗余了 taskName 而不是只存 taskId。原因是打卡记录一旦生成就不该被任务改名影响而且云数据库没有 join冗余字段在展示时少一次关联查询。这是云开发项目的标准做法读时冗余写入时一次落全。4.2 getOpenID 云函数与身份识别客户端直接读写数据库必须知道当前用户是谁。openid 是用户在小程序内的唯一标识但出于安全考虑openid 不能由客户端伪造必须通过云函数从微信上下文里取// cloud/getOpenID/index.js const cloud require(wx-server-sdk); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); exports.main async () { const wxContext cloud.getWXContext(); return { openid: wxContext.OPENID, appid: wxContext.APPID, unionid: wxContext.UNIONID }; };客户端调用const res await wx.cloud.callFunction({ name: getOpenID }); this.setData({ openid: res.result.openid });这里必须用DYNAMIC_CURRENT_ENV而不是写死环境 ID否则换一个云环境部署云函数时就要改代码。云函数是冷启动的getOpenID 这种轻量逻辑每次调用在几十毫秒内不需要担心性能。拿到 openid 后所有是不是我发的记录我能不能改这条的判断都以它为准。4.3 记录流查询与分页posts 页面按时间倒序展示记录。云数据库单次get默认最多返回 20 条所以分页是必须的。不要用 skip 分页——数据量上去后会越来越慢。我一般用游标分页以最后一条记录的 createTime 作为下次查询的起点const db wx.cloud.database(); const pageSize 10; async loadNextPage() { const query db.collection(checkins) .orderBy(createTime, desc) .limit(pageSize); const res this.data.pageCursor ? await query .where({ createTime: db.command.lt(this.data.pageCursor) }) .get() : await query.get(); this.setData({ records: this.data.records.concat(res.data), pageCursor: res.data.length ? res.data[res.data.length - 1].createTime : null }); }游标字段必须和排序字段一致否则会出现重复或漏数据。db.command.lt是小于操作符配合 createTime 倒序正好组成取更早的记录这个语义。records 拼接而不是覆盖保证下拉加载时前面已渲染的数据不丢失。下拉触底时调用一次loadNextPagepageCursor 为 null 说明没有更多数据可以停止请求。4.4 点赞的并发安全点赞是典型的读-改-写操作。如果写成先 get 再 set 把 likeCount 加一两个人同时点赞时会把其中一次自增吞掉。云数据库支持原子操作符正确写法是const _ db.command; await db.collection(checkins) .doc(recordId) .update({ data: { likeCount: _.inc(1) } });_.inc(1)是数据库服务端原子自增不需要先读后写天然避免并发丢失更新。如果还要防止同一个用户重复点赞在 checkins 里维护一个 likeUsers 数组await db.collection(checkins).doc(recordId).update({ data: { likeUsers: _.addToSet(openid), likeCount: _.inc(1) } });_.addToSet保证同一 openid 在数组里只出现一次配合前端按钮的点赞状态判断可以做到幂等。需要注意单个数组元素超过 1000 后会有性能问题课程设计场景完全够用如果要产品化应该把点赞拆成独立的 likes 集合。前端判断当前用户是否点过赞用this.data.record.likeUsers.includes(openid)即可。提示如果按 createTime 倒序分页时提示查询需要索引去云开发控制台 → 数据库 → checkins 集合 → 索引管理给 createTime 字段单独建一个降序索引即可。这是云数据库和自建 SQL 的最大区别隐式索引不够用得显式建。5. 上手指南部署、权限与三个必然遇到的坑拿到源码先别急着改业务把环境跑起来是第一步。用微信开发者工具导入工程目录在 app.js 里把env换成你自己的云环境 ID。没开云开发的话先点工具顶部的云开发按钮创建一个环境——免费额度对课程设计数据量绰绰有余。演示视频里从头到尾过了一遍第一次跑可以对照着看。5.1 数据库权限设置云开发数据库默认权限是仅创建者可读写这意味着 posts 页面读不到别人创建的打卡记录记录流会是一片空白。这个系统需要所有人可读、仅创建者可写在控制台把 checkins 集合权限改为所有用户可读仅创建者可读写。users 集合建议保持仅创建者可读写避免 openid 被遍历。用自定义规则可以写成{ read: true, write: doc.openid auth.openid }read 为 true 表示所有登录用户可读write 限定了只能改自己创建的文档。注意云函数操作数据库不受这个权限限制所以管理端逻辑都走云函数是更安全的姿势但课程设计里客户端直连够用。5.2 三个高频报错第一个是env not foundapp.js 里wx.cloud.init的环境 ID 和控制台实际环境 ID 不一致或者本地调试时选择了不使用云开发。去控制台复制环境 ID 再检查一遍。第二个是wx.chooseMedia is not a function基础库版本低于 2.10.0。在开发者工具详情 → 本地设置里把调试基础库调高即可真机预览时也要确认手机微信版本够新。第三个是图片显示空白上传成功但image渲染不出内容。检查是不是把 fileID 当成临时路径用了fileID 直接给 image 的 src 是可以的但如果云存储跨环境访问需要确认两个环境是同一个账号下的否则会鉴权失败。另外开发者工具里偶尔有 fileID 缓存不刷新的情况清缓存重编译就好。5.3 一个值得试的性能优化与验证技巧新手最容易忽视的是 setData 的调用频率。倒计时页面每秒 setData 一次如果页面切到后台定时器还在跑会白白消耗电量。可以用页面生命周期把定时器暂停onHide里 clearIntervalonShow里重新用 endAt 计算剩余并恢复。这个优化点写进论文是能讲清楚渲染开销与生命周期关系的具体案例。验证时有个小技巧把手机系统时间改乱几分钟再去看打卡记录的排序是否仍然正确。如果时间被改动了排序还能保持稳定说明 serverDate 的使用是到位的如果排序乱了说明某处用了客户端时间——这个排查方法比翻代码找 Date 调用点快得多。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →