公司动态
微信小程序打卡签到案例源码解析:从解压到真机预览全流程
简介本资源是一套完整的微信小程序‘易打卡签到’功能实战源码面向小程序开发初学者与移动应用开发者聚焦用户身份管理、签到逻辑实现、前后端数据交互等核心场景助力快速掌握小程序工程化开发流程。压缩包共81个文件含11个JS文件承载页面逻辑与网络请求、9个WXML结构层、10个WXSS样式层、9个JSON页面与全局配置、40张PNG资源图及1个GIF动效图整体仅1.51MB轻量易导入。已有891人学习下载适合通过真实项目理解pages目录组织、app.js全局状态管理、utils工具封装及request网络模块调用等关键实践。源码结构规范涵盖签到页、记录页等典型功能模块并集成时间校验、本地缓存、用户信息获取等常用API调用范例可直接运行调试或二次开发。 很多刚接触微信小程序的朋友拿到一个「微信小程序开发-易打卡签到案例源码.zip」这种项目包第一反应就是解压、导入、跑起来结果往往会在中间某个环节卡住。这个zip包我前后复现过不止一次从解压、导入到真机预览踩过不少坑今天把整个流程和源码里值得参考的思路一次性写清楚希望对正在找打卡签到小程序素材、或者想复刻一个类似产品的人有帮助。这套案例源码解决的核心问题一句话就能概括用微信小程序实现日常打卡签到包括时间记录、定位打卡、历史记录展示、连续打卡统计这些基础能力。你既可以拿它当毕设、练手项目也可以作为企业内部考勤、班级签到、活动打卡的二次开发底子。下面所有内容都是我在实际跑通这个项目过程中的记录从项目结构讲起一直到最后的问题排查尽量把每个让我卡住的点都掰开揉碎说清楚。1. 案例整体设计与技术选型思路1.1 打卡签到需求拆解一个最小可用产品要哪些模块一个真正能用的打卡小程序至少要覆盖四个场景用户进入后有身份标识能区分是谁在打打卡动作本身要记录时间戳和可选的定位信息打完卡之后有反馈而不是按钮按了没反应最后还要有历史记录和统计让用户能回头看到自己的坚持天数。这四个场景落到页面上通常就是「首页展示」「打卡操作页」「历史记录页」「个人中心页」四块。案例源码里基本也是这个结构只是有的叫法不同有的把首页和打卡页合并了有的把统计做成日历挂在打卡页顶部。我建议你先别急着改代码把「谁在什么时间什么地点打了卡、这个记录存哪里、怎么展示」这三件事理清楚后面所有代码都能看懂。尤其是存储结构它决定了你后面改统计逻辑、加多用户支持时的改动量这一步想清楚能省很多返工时间。1.2 技术选型为什么这么搭原生框架加云开发市面上的微信小程序开发方案有原生、uni-app、Taro、mpvue等好几种。这套打卡案例源码用的是原生小程序框架这是最贴近微信底层能力的方案没有框架层的隔阂遇到问题你能直接搜到官方文档的答案。对于打卡这种轻量业务用原生写反而比套框架更清晰页面少、逻辑简单没必要引入编译链增加复杂度。数据存储这块案例源码大概率走了「本地缓存云开发」的组合。本地缓存用wx.setStorageSync存数据零配置就能跑起来适合做demo演示云开发则是一套完整的Serverless方案包含云函数、云数据库、云存储不需要自己买服务器、配域名、搞HTTPS证书就能上线真实项目。如果用传统的自建后端你得面对服务器运维、接口鉴权、CORS等一系列问题对起步阶段来说负担偏重。我把三种存储方案的差异整理在下面方便你根据自己场景选方案优点缺点适合场景本地缓存零配置、离线可用、无需服务器换设备数据丢失、无法多端同步个人demo、学习练手微信云开发免运维、自带鉴权、有免费额度和微信生态绑定、存在冷启动个人项目、小团队内部工具自建后端接口灵活、数据自主可控、可迁移要服务器、域名、证书、鉴权正式商用、需要数据自主可控从我的实际体验来看如果你是第一次接触小程序开发先跑通本地缓存版本理解数据是怎么存怎么读的再切换到云开发版本体验会顺畅很多。上来就直接上云开发遇到环境变量、集合权限、云函数部署这些问题时容易一头雾水。1.3 代码组织思路页面、工具、云函数各管一摊源码的目录结构不算复杂核心思路是「页面只管交互、工具函数管逻辑、云函数管服务端」。pages下每个文件夹是一个页面公共方法抽到utils里云函数按功能单独建目录。这种分层方式特别适合二次开发你想改打卡逻辑不用翻页面渲染代码想换UI也不会把数据层弄乱。有一个细节容易被忽略很多案例源码里会把「日期格式化」和「距离计算」之类的函数放在utils里复用而不是每个页面复制粘贴一份。我见过不少新手项目同样的时间格式化逻辑写了七八遍改个格式要全局搜索替换非常痛苦。拿到的源码里如果已经有utils目录说明作者是有意识做复用的你在改造时也尽量保持这个习惯。另外如果你打算长期维护这个项目建议在源码基础上补一个README把页面路径、数据集合、云函数调用关系记下来一个月后回头看源码会轻松很多。2. 源码核心细节解析从全局配置到打卡逻辑2.1 项目目录结构拿到zip后先看这些文件解压后你会看到类似这样的结构├── app.js # 小程序入口初始化云开发 ├── app.json # 全局配置页面注册、导航栏、tabBar ├── app.wxss # 全局样式 ├── project.config.json # 开发者工具项目配置 ├── pages/ │ ├── index/ # 首页 │ ├── clock/ # 打卡页 │ ├── record/ # 历史记录页 │ └── mine/ # 个人中心页 ├── components/ # 可复用组件 ├── utils/ │ └── date.js # 日期工具函数 └── cloudfunctions/ # 云函数目录 └── clockIn/ # 打卡相关云函数拿到源码先不要急着跑按顺序做三件事打开app.json看注册了哪些页面和tabBar打开project.config.json看appid和项目名称打开utils/date.js看日期处理逻辑。这三件事做完你对整个项目的框架就有底了。这里要特别提醒项目配置文件里的appid一定不是你的导入时要么改成自己的AppID要么在开发者工具里选择「测试号」模式否则编译会报错或者预览时无法使用云开发。很多朋友反馈导入后白屏、接口报错排查到最后都是这一步没做对。2.2 app.json 全局配置页面注册与导航栏设置app.json 是微信小程序的门面下面这些都是打卡类项目的常见配置{ pages: [ pages/index/index, pages/clock/clock, pages/record/record, pages/mine/mine ], window: { navigationBarBackgroundColor: #4A90D9, navigationBarTitleText: 易打卡签到, navigationBarTextStyle: white }, tabBar: { color: #999999, selectedColor: #4A90D9, list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/clock/clock, text: 打卡 }, { pagePath: pages/mine/mine, text: 我的 } ] }, cloud: true }pages数组的第一个元素是小程序启动后的默认首页。很多人改完代码白屏就是因为把新页面加到pages数组后面却忘了把默认首页路径调换。window里的navigationBarTitleText就是顶部导航栏的标题文字背景色和文字颜色分别由另外两个字段控制。如果你想把标题改成「今日打卡」直接改navigationBarTitleText就行注意这个字段是写在全局配置里的会作用于所有页面如果只想改某个页面的标题需要在该页面的json文件里单独配置。tabBar配置了底部的标签这里有两个硬性要求tabBar的pagePath必须在pages数组中注册过list数组最少2项最多5项。如果你想加一个「统计」页需要先在pages数组里注册pages/statistics/statistics再在tabBar里追加对应的pagePath少一步都会导致编译失败。2.3 打卡核心逻辑存储结构、日期处理与防重复打卡功能最核心的一个问题怎么判断今天已经打过卡了比较常见的做法是用「日期字符串作为key」存记录当天的打卡状态直接用当天日期去查比如2025-05-11这个key存在就说明这一天打过卡。// pages/clock/clock.js 核心打卡逻辑 const utils require(../../utils/date.js); Page({ data: { today: , clockedIn: false, todayRecord: null }, onLoad() { const today utils.formatDate(new Date()); const records wx.getStorageSync(clockRecords) || {}; this.setData({ today, clockedIn: !!records[today], todayRecord: records[today] || null }); }, clockIn() { if (this.data.clockedIn) { wx.showToast({ title: 今天已经打过卡了, icon: none }); return; } const records wx.getStorageSync(clockRecords) || {}; const today this.data.today; records[today] { time: new Date().toLocaleTimeString(), location: this.data.location || 未获取定位 }; wx.setStorageSync(clockRecords, records); this.setData({ clockedIn: true, todayRecord: records[today] }); wx.showToast({ title: 打卡成功, icon: success }); } });这段逻辑里有几个点值得展开说。第一用wx.getStorageSync读到的数据先给一个空对象兜底避免第一次运行时读到undefined导致records[today]直接报错这是新手最容易踩的坑。第二打卡成功后更新本地缓存和页面数据要同步做只更新data不写缓存下次进入页面数据会丢只写缓存不更新data用户看不到即时反馈。第三防重复打卡的判断要在写入之前做而且写入后要立刻把clockedIn置为true否则用户连续点击打卡按钮会在极短时间内写入多条记录。如果是用了云开发的版本记录会写到云数据库的clock_records集合里查询条件基于当前用户的openid。云函数示例大概长这样// cloudfunctions/clockIn/index.js const cloud require(wx-server-sdk); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }); exports.main async (event) { const { OPENID } cloud.getWXContext(); const db cloud.database(); const today event.date; const hasClocked await db.collection(clock_records) .where({ openid: OPENID, date: today }).get(); if (hasClocked.data.length 0) { return { success: false, message: 重复打卡 }; } await db.collection(clock_records).add({ data: { openid: OPENID, date: today, time: event.time } }); return { success: true }; };云开发的好处是openid由微信自动注入不需要自己实现登录鉴权。但要注意云函数默认有冷启动第一次调用可能慢一点打卡这类低频操作影响不大。实际体验下来云开发更适合多人场景因为数据天然集中在一个数据库里管理员想查看所有人的打卡情况也很方便而本地缓存方案只适合单机演示。2.4 日历视图与连续打卡统计的实现打卡类小程序基本都会带日历视图用来展示一个月里哪些天打了卡。日历渲染的核心是两件事算当月总天数算1号是星期几。// utils/date.js 日历相关 function getMonthDays(year, month) { return new Date(year, month, 0).getDate(); } function getFirstDayOfWeek(year, month) { return new Date(year, month - 1, 1).getDay(); } function buildCalendar(year, month) { const days getMonthDays(year, month); const firstDay getFirstDayOfWeek(year, month); const cells []; for (let i 0; i firstDay; i) cells.push(null); for (let d 1; d days; d) cells.push(d); while (cells.length % 7 ! 0) cells.push(null); return cells; }注意new Date(year, month, 0)里的month是0-indexed所以传2表示3月得到的是3月的天数。很多新手在这个地方算错日期就是因为月份没有减一。buildCalendar返回的是一个数组前面用null占住1号之前的空白末尾补null让总长度是7的倍数这样渲染出来才能整齐排成每行7列。连续打卡天数统计常规做法是遍历存储里最近N天的记录从今天往前数遇到断档就停止function getContinuousDays(records, baseDate) { let count 0; const d new Date(baseDate); for (let i 0; i 365; i) { const key formatDate(d); if (records[key]) { count; d.setDate(d.getDate() - 1); } else { break; } } return count; }这个逻辑比较直观但它把「今天是否打卡」也算进去了。如果今天还没打连续天数会从昨天开始重新算实际业务上很多打卡产品希望「今天没打不打断连续记录只是今天显示未打卡」。这块要根据产品需求调整源码的原始逻辑不一定符合你的场景改的时候要看清。我建议在改统计逻辑前先列出你对「连续打卡」的定义是包含今天的连续还是截止到昨天的连续不同定义会直接影响你页面顶部数字的展示。3. 从zip到跑通完整实操步骤与配置清单3.1 解压源码包Windows/Mac/Linux三种姿势先解决最基础的问题怎么把这个zip包解开。Windows下右键「全部解压缩」就行但我建议多留个心眼如果压缩包是从网盘下载的下载后大小和页面标注不一致很可能是下载中断这时候解压大概率会报「文件已损坏」之类的错误。优先重新下载一遍而不是反复尝试修复因为zip结构一旦损坏修复成功率很低。Mac下双击zip会默认解压到当前目录。如果提示「无法打开」先检查是不是系统安全设置拦了来源不明的文件到「系统设置-隐私与安全性」里允许一下即可。另一个常见问题是从Windows传到Mac的zip包文件名在解压后变成乱码这是因为两边的文件名编码不一致Windows默认GBKMac默认UTF-8如果源码里包含中文文件名可以试试支持编码转换的解压工具。Linux服务器上最常用的是unzip命令unzip 微信小程序开发-易打卡签到案例源码.zip如果你是在服务器上下载的压缩包解压前先确认zip是否安装apt install unzip或者yum install unzip。如果中文文件名乱码可以加个编码参数unzip -O GBK。另外我在实际开发中经常遇到一个报错file is not a zip file。出现这个提示绝大多数情况是文件后缀虽然叫zip实际却不是标准zip格式或者文件下载不完整。用file命令看一眼真实类型file 文件名.zip如果输出里不是Zip archive说明文件本身有问题跟解压工具无关去重新下载才对。3.2 导入微信开发者工具AppID、目录、基础库的选择解压完成后打开微信开发者工具点击「导入项目」目录选择解压后包含project.config.json的那一层AppID选择「测试号」或者填写你自己的小程序AppID。这一步很多人搞错选了外层文件夹工具会提示找不到project.config.json选了内层某个页面文件夹导入后页面注册关系全乱。正确做法是先确认project.config.json所在的目录层级再导入那一层。基础库版本建议选「最新稳定版」别为了追求新功能选开发版稳定性优先。如果导入后编译报错第一件要做的事是看工具右上角的「编译」日志错误信息会直接告诉你哪个文件的第几行有问题。不要慌着删代码大多数报错都是路径问题引用了不存在的组件、页面文件缺失、或者云开发环境ID没配置这类问题的处理我在第四节详细说。导入成功后你能在模拟器里看到小程序界面。如果点打卡按钮没反应八成是本地缓存逻辑或者云开发环境的问题先在console面板看有没有红色报错。红色报错不可怕可怕的是没有任何报错但功能不正常这时候建议在一个关键函数入口加console.log调试逐步缩小范围。3.3 云开发环境初始化数据库集合与云函数部署如果源码用到了云开发在模拟器里只点按钮是不够的还需要三步开通云开发得到一个环境ID在云开发控制台创建需要的数据库集合把cloudfunctions下的云函数上传部署。云开发环境ID在哪里填通常是在app.js的wx.cloud.init里写有的源码写死了环境ID有的用DYNAMIC_CURRENT_ENV自动指向当前环境。如果你在创建云开发环境后环境ID和源码里写的不一致要把app.js里的配置改成你自己的否则调用云函数会报「环境不存在」。数据库集合名也要和代码对得上。比如源码里集合名是clock_records你在云开发控制台建了clockRecord查询肯定查不到数据。云函数的部署方式是在开发者工具里右键云函数文件夹选择「上传并部署云端安装依赖」而不是「上传所有文件」前者会在云端自动安装node_modules依赖后者容易导致云函数运行时报找不到模块。部署完成后建议在云开发控制台手动运行一次云函数用测试参数走一遍确认数据库读写都正常再回到小程序里联调。3.4 真机预览与发布前检查模拟器跑通之后别急着说完工很多问题只要一到真机就暴露。点工具栏的「预览」会生成一个二维码手机微信扫码即可在真机上跑。真机和模拟器最大的差异有两个一是定位权限模拟器可以手动模拟位置真机必须用户授权而且小程序需要在app.json里声明「位置信息用途说明」不然调用wx.getLocation会直接失败二是HTTPS域名校验开发阶段默认「不校验合法域名」可以正常请求但上线后必须在小程序后台配置request合法域名云开发则不需要配置域名这点用云开发能省不少事。发布前的检查清单我列一下基础库最低版本设置是否合理很多用户手机微信版本较旧基础库版本过高会导致部分用户打不开首次进入的隐私授权弹窗是否正常展示尤其是涉及定位时隐私协议要在小程序后台「用户隐私保护指引」里配置打卡数据的存储位置和备份方式本地缓存模式会丢数据云开发更稳但要注意数据库导出权限tabBar图标和文字是否在真机上变形真机和模拟器的渲染差异可能不小图标尺寸建议按官方规范84px左右输出4. 常见问题与排查技巧实录4.1 压缩包解压报错的几种场景zip解压相关的报错在我接触这个案例的朋友里出现频率很高出现最多的两类错误是file is not a zip file和invalid zip archive: could not find EOCD。先解释一下EOCD是什么。zip文件的结构分三部分数据区、中央目录区、文件尾EOCDEnd of Central Directory recordEOCD相当于整个压缩包的索引表解压工具必须先读它。could not find EOCD说明索引表不存在基本原因只有一个文件被截断了最常见于下载中途断网、网盘文件损坏、或者从聊天软件传输时被特殊处理过。解决办法不是反复解压而是重新获得一份完整文件。我实测过网盘下载的文件校验不对99%是下载环节的问题而不是运气问题。另一种情况是文件扩展名是zip但实际是rar、7z或者干脆是HTML文件这种情况会频繁出现file is not a zip file。先用file命令确认真实格式再决定用什么工具解压。在Mac上用系统自带的归档实用工具打不开的试试Keka或者The Unarchiver兼容性会好很多。关于zip密码多说一句合规的案例源码包基本不会加密如果你拿到一个需要密码的压缩包先联系发给你的人确认密码别去网上找所谓的「密码移除」工具这类工具捆绑恶意程序的风险极高为解压一个demo包去冒这个险完全不值得。如果同一个源码包在多个平台分发建议优先从官方渠道下载避免拿到被二次打包的版本。4.2 编译白屏、页面空白与tabBar不显示导入后白屏是最常见的问题之一。先分清楚是「编译日志有报错」还是「编译通过但页面空白」。有报错按报错路径排查没报错但白屏大概率是某个接口在onLoad里报错导致setData提前终止或者云开发环境没初始化成功。打开调试器的console面板看有没有Uncaught错误定位到具体文件后会清晰很多。我遇到过一种很隐蔽的情况页面路径大小写不一致。小程序在某些系统上对路径敏感pages/Clock/clock和你实际目录pages/clock/clock不匹配编译能过但进入页面就白屏改掉大小写就好了。建议目录命名统一用小写省得后续跨平台部署时踩坑。tabBar不显示先检查app.json里的tabBar字段是否配置正确。一个容易忽略的坑是如果页面是用wx.navigateTo跳转进去的那个页面本身不会显示tabBar只有tabBar里配置的页面才有底部栏。另外tabBar的pagePath必须是pages数组里注册过的路径两个数组的拼写要完全一致差一个字母都能导致整个tabBar消失。4.3 导航栏高度、单选框样式与z-index层级有朋友问微信小程序顶部导航栏高度怎么适配。官方提供的主流程是wx.getMenuButtonBoundingClientRect获取胶囊按钮的位置再结合系统状态栏高度计算出自定义导航栏的占位高度const menu wx.getMenuButtonBoundingClientRect(); const system wx.getSystemInfoSync(); const navBarHeight (menu.top - system.statusBarHeight) * 2 menu.height;这里乘2是因为胶囊按钮上下留白通常各占一半两个留白加按钮高度就是整个导航栏的高度。如果你只是用默认导航栏完全不用管这个改改navigationBarTitleText就够。但如果你要做沉浸式导航栏这个公式一定要记下来尤其是iPhone刘海屏和灵动岛机型的适配公式里的值在不同机型上会自动变化不要写死。打卡类型选择如果用了原生radio组件样式很难调整真机上容易出现选中态不明显、点击区域过小的问题。我的经验是简单的二选一或三选一直接用自绘的view加选中态字段就够。比如你要做「上班打卡」「下班打卡」「外勤打卡」三个选项用一个format字段记录当前选中的类型点击时setData更新比原生radio稳定也更好看。z-index层级问题也很典型页面里有个弹窗结果被底部tabBar或者其他元素盖住。小程序的z-index和浏览器逻辑基本一致但要注意position为static的元素z-index不生效fixed定位的元素层级天然高于普通流内元素。弹窗一定要同时设置position: fixed和较高的z-index比如9999同时确认没有父容器创建了新的层叠上下文。如果弹窗组件被内置在某个自定义组件里还要看自定义组件的isolated样式隔离是否影响了层级传递。4.4 定位打卡与地图组件的实际使用打卡场景里定位是重头戏。wx.getLocation获取到经纬度之后判断是否在指定范围时距离计算一般用Haversine公式精度够且实现简单function getDistance(lat1, lng1, lat2, lng2) { const rad d d * Math.PI / 180; const R 6371000; const dLat rad(lat2 - lat1); const dLng rad(lng2 - lng1); const a Math.sin(dLat / 2) ** 2 Math.cos(rad(lat1)) * Math.cos(rad(lat2)) * Math.sin(dLng / 2) ** 2; return Math.round(2 * R * Math.asin(Math.sqrt(a))); }R取6371000米是地球平均半径结果单位是米。比如公司坐标和用户坐标算出来小于200就意味着用户在200米范围内可以允许打卡。这个阈值设置多少取决于你的实际场景办公室打卡建议100到300米太严格用户在公司隔壁打卡会失败太宽松会出现远程打卡漏洞。有朋友问微信小程序能不能用天地图做地图组件。原生map组件默认使用的底图数据来自腾讯地图如果你想换成天地图严格来说并不是在map组件里改一个provider就行的更多是两种思路一种是申请天地图的开发者key通过web-view嵌入天地图Web API页面绕开原生map组件的限制但web-view会带来页面跳转和交互上的割裂感另一种是继续用原生map组件只是给marker加自定义图标底图仍然用腾讯地图。对打卡场景来说通常只需要显示一个位置点原生map配合marker完全够用没必要为了底图来源去折腾web-view。定位权限这块再强调一遍微信小程序从基础库2.x开始调用位置接口前必须做权限声明和用户授权。在app.json里加permission字段说明用途在代码里先调用wx.getSetting检查授权状态被拒绝后引导用户去设置页打开这套流程别省。真机上模拟的位置和实际位置如果差距过大要考虑是不是手机GPS没有开启或者是在室内楼层信号不佳导致精度下降。实际调试时可以用wx.chooseLocation代替getLocation做一次手动选点能显著提高定位准确性。调试网络接口时建议优先使用开发者工具自带的Network面板能看到每个请求的状态码、耗时和返回内容排查接口问题比抓包工具更直接。真机调试场景下如果确实需要看完整请求链路可以借助抓包工具分析HTTPS请求在小程序后台把调试域名加到白名单里。这里多说一句抓包工具只能分析自己设备上小程序的网络请求需要在小程序代码里手动配置代理或证书官方开发者工具的「真机调试」功能其实已经覆盖了大部分联调需求抓包更多是备用方案。写到这里整个从zip到可运行的流程算是完整走了一遍。我个人在实际操作中的体会是这种案例源码的价值不在「开箱即用」而在于它把打卡业务里最成熟的方案都摆出来了本地缓存、日期计算、定位判断、云函数防重复每一块都值得反复琢磨。你把它跑通之后完全可以继续扩展加一个统计图表页面、接一个后端接口支持多端同步、或者把单人的打卡改成多人的打卡圈。拿到的代码只是一个起点改造成自己真正需要的产品才是这趟折腾最大的收获。最后再分享一个小技巧改完任何一块逻辑先用开发者工具的「清缓存」功能清掉缓存再编译一次很多诡异的问题都是旧缓存惹的祸。本文还有配套的精品资源点击获取