☰
从“死了吗”到极简打卡:基于云开发的微信小程序完整实践
2026/10/9 11:53:59 网站建设 项目流程

昨天半夜,我终于把这个小程序的 v1.0 提交了审核。从冒出念头到看到官方审核页面,前后折腾了差不多两周。项目本身不复杂,但它源自一个特别小的洞察:我重度用过一段时间“死了吗”这个产品,发现它虽然看起来像是个猎奇向的页面,真正留住我的却是那种“每天打开得到一个确定性答案”的体验。

这次我没打算复刻它,而是把它最核心的逻辑——每天打开、确认一个状态、得到确定结果——搬到了打卡签到场景里。目标很简单:让用户每天只需要做一次“确认”动作,而不是被一整套积分、排行榜、勋章体系绑架。这篇文章我会把整个项目从思路拆解、技术选型、页面设计、云函数实现到审核踩坑完完整整写出来,如果你正想做一个轻量级的微信小程序,又不想上来就买服务器、配域名、折腾备案,这篇应该能帮你省下不少弯路。

1. 别急着打开IDE:先搞清楚“死了吗”到底做对了什么

1.1 它不是一个猎奇产品,而是一个“每日确认器”

很多人在聊“死了吗”时,第一反应都是“这玩意儿有什么好玩的”。但它能长期存活、保持稳定访问,靠的其实不是猎奇,而是极其精准的心理设计。

它的使用路径是这样的:打开页面,看到一个名字,页面给出一个状态——活着还是去世,然后结束。整个过程不超过五秒,不需要注册,不需要理解复杂的规则,不需要做任何决策。这玩意儿本质上是一个“每日确认器”,用户每天来,只为了获得一个确定性的答案。人天生需要“确定性”,特别是对在乎的人、在乎的事,哪怕只是一个“状态正常”的信号,也能带来心理上的安定感。

打卡签到类产品的问题恰恰相反。绝大多数打卡应用把自己做成了“管理后台”,又是打卡次数、又是连续天数、又是等级勋章,用户打开之后要做三四个选择才能完成一个动作。这本身就违背了打卡的初衷——打卡应该是一瞬间的确认,而不是一场小型的产品培训。所以我在设计这个小程序时定了一条铁律:用户打开首页后,最多三步以内必须完成打卡。

1.2 怎么把“确认还活着”移植成“确认今天的坚持”

把“确认还活着”换成“确认今天的坚持”之后,场景就打开了。我最先落地的三个方向是:个人习惯打卡(喝水、阅读、运动)、健康记录(睡眠时间、体重、吃药)、纪念日签到(分手了每天提醒自己也算一种打卡)。

核心交互被压缩成一句话:用户创建一个目标之后,这页面对他来说只有一个问题——“今天做了吗?”没有花里胡哨的输入框,没有复杂的配置项,就一个大的按钮:打卡。打完变成“今日已完成”,这个状态在午夜 0 点自动重置。

这里我特意借鉴了“死了吗”的另一个细节:它在用户没有进行任何操作时,也会返回当前状态。所以我的首页在没有打卡时,显示的不是“快去打卡”这种催促文案,而是“今天还没打卡”的客观描述。这种中性的表达会让用户觉得自己是被提醒,而不是被逼迫,心理阻力会小很多。用产品术语套个近乎:不做负向激励,只做状态呈现。这是整个项目最核心的设计决策,后面所有页面都是围绕它展开的。

2. 技术选型:原生微信小程序 + 云开发是我唯一想推荐的组合

2.1 原生、uniapp、Taro,我为什么没选后两个

项目确定要做微信小程序时,摆在我面前的无非三条路:原生小程序、uniapp(Vue 语法)、Taro(React 语法)。我把它们的特征按自己的实际需求列了一张表:

方案上手门槛多端能力小程序包体风险适合场景
原生低仅微信低,资源可精确控制只做微信端、想快速上线
uniapp中H5/App/各小程序中,打包后主包很容易超 2MB明确要多端投放
Taro较高H5/各小程序中团队以 React 技术栈为主

说实话,如果目标是“多端通吃”,uniapp 的吸引力确实不小。但我在网上看到了太多类似的报错:source size 2612kb exceed max limit 2mb。这不是危言耸听,uniapp 打包后的主包体积控制是很多新手第一个拦路虎。你还没开始写业务代码,光是框架运行时、公共组件库打包出来就已经逼近 2MB 上线了,后面再塞点图片、页面,直接爆掉,只能被迫搞分包、搞异步组件。对于一个个人项目来说,这些成本和复杂度是完全没必要的。

Taro 同理,它适合团队协作,但不适合个人快速验证想法。个人开发者的首要任务是缩短“想法到上线”的路径,而不是追求技术架构的宏大叙事。所以最后我选了原生微信小程序 + 云开发,整个工程里图片基本都放在云存储上,加上代码本身,主包控制在 900KB 左右,非常轻松。

2.2 云开发省掉了整个后端

这里得重点夸一下云开发,它把传统小程序开发里最痛苦的一环——后端服务——整个省略掉了。

传统模式是什么样?你需要一台服务器,需要一个域名,需要对域名做 ICP 备案,需要自己部署后端代码,需要处理鉴权、数据库、日志、监控。光是“备案”这两个字,就能劝退一大批个人开发者。而云开发提供了一套微信生态内的后端能力:云函数(相当于后端接口)、云数据库(文档型数据库)、云存储(文件存储)都是开箱即用的。

我印象最深的一点是:云函数里可以直接通过cloud.getWXContext()拿到当前用户的 OPENID,不需要自己实现登录态校验、不需要管理 session_key、不需要维护 token。这就是微信给开发者的原生鉴权能力,省掉了自己写code2session接口的功夫。在传统架构里,你至少要准备一个用户表、一个登录接口、一个 token 拦截器,现在这些全部内置了。

成本方面,个人开发者的访问量根本用不完云开发的免费额度。刚开始时我给自己的预期是“一个月几块钱”,结果上线两周,费用为 0。项目做到后面你会发现,省下的不只是钱,还有大量的部署、维护时间,这些时间拿去打磨交互和解决真实用户问题,价值高得多。

2.3 登录方案:先用 openid,别一上来就搞手机号

登录这块我想单独拧出来说。很多课程和热词里都在讲“微信小程序登录获取手机号”,但实际做项目时,个人开发者对小程序的“获取手机号”能力往往存在一个误解:这个能力不是你想用就能用的。

获取手机号需要小程序完成企业主体认证,并且通过相应类目的权限申请。个人主体小程序根本开不了这个权限。所以我的第一个建议是:你的第一个版本,不要做手机号登录。用wx.login换取 OPENID 就够了,OPENID 是用户在某个小程序内的唯一标识,天然适合当业务主键。

需要提醒的是,OPENID 不要直接拿来当用户展示 ID 用。我在用户表里额外存了一个uid字段,用于前端页面展示和分享链接的标识。这样即使将来切换到新的登录方式(比如真的接入了手机号),用户数据表的结构改动也最小。登录流程简化之后,用户在首页根本感受不到“登录”的存在,这比任何登录引导都舒服。

3. 产品拆解:三个页面把“打卡”这件事做到最少

3.1 首页信息流:今天只回答一个问题

首页是整个小程序的核心,我把它简化到了极致。顶部是一句时间问候和日期,今天是几号、星期几;中间是一张状态卡,展示当前目标的名字、今日打卡状态、连续坚持天数;底部是一个大按钮,没打卡时是鲜亮的主题色,点击后立刻变成“今日已完成”的灰色状态;再往下是最近几天的打卡记录流。

这个首页设计参考了“死了吗”的信息密度——人打开页面,视线扫过一遍,只需要判断一个信息:今天我完成了吗?你看不到规则说明,看不到复杂的模块,每一个元素都在为唯一的“打卡动作”服务。

打招呼这句话也别小看。我用wx.setNavigationBarTitle动态修改顶部标题——用户今天已完成时标题显示“今日已完成”,未打卡时显示“今天还没打卡”。这个小细节成本极低,但它让顶部导航栏也成为状态的一部分,用户哪怕不往下看内容,瞟一眼也知道今天的进度。

3.2 目标页与统计页:连续记录才是留存命脉

从首页点进“打卡详情”,能看到这个目标更完整的记录:月历视图(每个有打卡记录的日期会标上圆点)、总打卡天数、当前连续天数、最长连续天数。在页面顶部还有一个“新建打卡目标”的入口,点进去只需要填两样东西——目标名称和一个 emoji 图标,字段越少,用户创建目标的动力就越足。

这里的设计逻辑是:用户看似在“打卡”,其实在积累一条从过去延伸到现在的时间线。连续天数比总天数更能带来黏性,因为断签的“损失感”会让人不甘心。但我不做补卡、不卖复活卡,断签就断签,第二天重新开始连续。这也模仿了“死了吗”那种“不干预结果”的产品态度。

数据层其实非常简单,两张表:goals存目标(用户ID、目标名、图标、状态、创建时间),checkins存打卡记录(目标ID、用户ID、日期字符串、创建时间)。关键是checkins里每条记录用目标ID_日期字符串作为唯一主键,这样同一个目标在同一个自然日只能有一条记录,天然防重复打卡。

4. 打卡逻辑与云函数实现:这些细节最容易翻车

4.1 服务器时区、重复打卡与唯一键设计

打卡业务的硬性逻辑有几条:不能重复打卡、日期必须按东八区计算、连续天数要能清零重算。我在第一个版本里就踩了时区的坑。

微信云函数的运行环境使用 UTC 时间,直接用new Date().toISOString().slice(0, 10)拿日期,在北京时间晚上 23 点就会拿到“明天”的日期,严重错乱。正确做法是先补上 8 小时的时差再做格式化:

const now = new Date(Date.now() + 8 * 3600 * 1000) const dateStr = now.toISOString().slice(0, 10)

这是东八区日期字符串的标准获取方式,核心逻辑是把当前时间当作 UTC+8 来“校准”,再用toISOString()输出。

重复打卡的防重设计,我用了一个取巧的方式:不先查再插(先查再插存在并发竞态),而是直接把唯一键当作_id。云数据库的_id天然唯一,插入时重复会抛错,利用这个特性就完成了原子化的防重:

const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() exports.main = async (event) => { const { OPENID } = cloud.getWXContext() const { goalId } = event // 东八区日期 const now = new Date(Date.now() + 8 * 3600 * 1000) const dateStr = now.toISOString().slice(0, 10) // 唯一主键:goalId + 日期,天然防重复 const docId = `${goalId}_${dateStr}` try { await db.collection('checkins').add({ data: { _id: docId, goalId, userId: OPENID, date: dateStr, createdAt: Date.now() } }) } catch (e) { return { code: 1, msg: '今天已经打过卡了' } } // 更新目标表数据,比如总数、最后打卡日期 await db.collection('goals').doc(goalId).update({ data: { lastCheckinDate: dateStr, totalCount: db.command.inc(1) } }) return { code: 0, msg: '打卡成功' } }

连续天数的计算我没有在打卡时实时全表扫描,而是单独存了一个lastCheckinDate和totalCount,统计页需要时只查询最近三十天的记录,在前端做连续性判定。个人项目的访问量完全撑得住这种轻量查询,没必要为了“架构优雅”而提前引入复杂的聚合任务。

4.2 订阅消息:普通小程序做不了长期提醒,我的替代方案

做完基础打卡功能后,我第一个想到的是“用户今天忘了打卡怎么办”,于是去研究订阅消息。然后就撞到了微信的规则:普通小程序只能使用一次性订阅消息,用户每授权一次,你才能给他发一条。而且授权动作需要用户在页面里主动点击“允许”按钮,不能静默获取。

真正能实现“长期、自动、每天推送”的长期订阅消息,只对特定类目开放,比如政务、医疗、金融、教育这类有明确公共服务属性的场景。一个纯个人开发者的“打卡签到”小程序,想申请长期订阅基本没戏。我在这里建议所有同类项目别浪费时间,直接放弃幻想。

那怎么提醒用户?我做了三手替代方案。第一,打卡成功后页面会出现“明天记得回来”的轻提示,引导用户直接把小程序添加到我的小程序;第二,我申请了一个同名的服务号,通过服务号的模板消息做每日提醒,前提是用户在服务号里主动绑定;第三,首页在晚上八点后增加了一个状态条——“今天还没打卡”会被放大显示,虽然这只能等用户主动打开才看得到,但它确实提高了我自己每天回访的次数。坦率讲,目前没有完美的主动触达方案,这是平台规则决定的,不是产品设计能绕过去的,接受它反而能让你把精力放到真正能控制的事情上。

4.3 动态标题和自定义导航栏:小问题大坑

这里记录两个开发过程中费了点劲的小细节,排查起来都不难,但不知道的话会卡很久。

一个是动态标题。小程序顶部导航栏默认显示的是页面 title,但我希望首页标题跟随打卡状态变化。直接在onShow里调用就行:

wx.setNavigationBarTitle({ title: isDone ? '今日已完成' : '今天还没打卡' })

难点在于isDone这个状态怎么来。我是每次打卡成功后在本地缓存里写一个todayDone: true,然后在页面onShow时读取,再结合当天的日期字符串判断是否需要重置。这里有个关键点:缓存重置不能等到用户打开小程序才做,最好在打卡动作发生时就顺带把todayDone写对,否则跨天之后用户看到的是昨天的状态,体验很糟糕。

另一个是自定义导航栏。为什么需要自定义?因为默认导航栏一旦设置了动态标题,右侧胶囊按钮和标题文字的间距在部分机型上会显得很挤,而且默认导航栏的背景色和首页想要的大色块效果不协调。自定义导航栏的第一步就是算准顶部高度。业界的通用公式是:

const menuRect = wx.getMenuButtonBoundingClientRect() const { statusBarHeight } = wx.getSystemInfoSync() const navBarHeight = (menuRect.top - statusBarHeight) * 2 + menuRect.height

navBarHeight就是自定义导航栏内容区的高度,整个顶部容器高度用statusBarHeight + navBarHeight。如果你把自定义导航栏做出来了但按钮位置歪了,九成是这两个值没用对。

5. 联调、打包、审核:一路踩坑实录

5.1 抓包联调的正确姿势

我的开发过程中遇到过一个很诡异的现象:在微信开发者工具里打卡一切正常,但真机上偶尔会报错,提示云函数返回超时。页面日志又看不出问题,这时候只能上抓包工具看真实网络请求。

网上流传的各种抓包教程方法大同小异,核心操作是三步:电脑上打开抓包工具并开启 SSL 解密,把手机 Wi-Fi 的 HTTP 代理指向电脑,手机浏览器下载安装对应的 CA 证书,之后 HTTPS 请求的明文就能在工具里看到了。工具的话,Charles 是老牌选择,界面直观;Reqable、Proxypin 这些近几年也做得越来越顺手,安装证书的流程更简单。

但我要特别提醒:抓包只应该用在自己开发、自己拥有的小程序上。试图截取别人小程序的通信数据,在隐私合规上风险极大,尤其是涉及用户手机号、支付信息这些敏感数据,完全没有必要去碰。如果你和我一样只是排查自己的云函数请求,其实还有一个更省事的路径——微信开发者工具的“真机调试”面板,直接看 Network 标签页,不需要搭任何代理环境。坦白讲,我后来绝大多数问题都靠这个面板解决了,抓包工具只是作为一种兜底手段。

5.2 2MB包体限制和首页图片优化

小程序主包限制是 2MB,超过就没法上传。很多新人第一版就会撞上这个墙,最常见的报错是source size 2612kb exceed max limit 2mb。

我的处理方式是先做减法,再做分包。第一步,把所有大于 20KB 的本地图片全部迁移到云存储,引用时用云存储的 fileID 或者临时 URL 地址,并在小程序后台配置下载域名白名单;第二步,把不常用的页面拆进分包,比如统计详情页、目标管理页、关于页,都放到subpackages里。主包只保留首页和核心打卡逻辑,最终体积直接降到 900KB。第三步,检查 node_modules,一个不到 2000 行的小程序,真的不需要引一堆第三方库。

还有一个被很多人忽略的点:swiper图片为什么会出现空白。这是热词里我又一次见到的高频问题,原因是swiper组件默认高度是 150px,但里面的图片如果设置了过大的容器高度,或者图片本身没有加载完成,就会出现底部大片空白。解决方案是给swiper显式设定一个合适的高度,图片设置mode="aspectFill"并让宽高撑满容器:

.swiper-banner { height: 200px; } .swiper-banner image { width: 100%; height: 100%; }

这样基本可以避免图片尺寸计算带来的空白问题。在真机上还要注意,云存储图片首次加载有网络延迟,给image加一个默认背景色或者加载占位图,观感会好很多。

5.3 审核类目与认证那些事

提交审核前,我特意花了半天研究类目。打卡签到类小程序我最终选择的是“工具 > 效率”这个类目,从功能描述上是吻合的。这里要注意:个人主体的小程序能选的类目非常有限,支付、社交、直播、医疗健康这些基本都与企业主体绑定。如果在开发前你就知道自己要做电商、要做付费会员,建议一开始就用企业主体去注册认证,免得后面又要迁移主体、改名,代价极大。

认证方面,企业和个人都可以做认证,企业主体认证每年要交一笔年审费用,个人认证的流程和费用在官方后台都有明确说明,不同时期的政策不太一样,我不打算在这里给一个可能过期的具体数字,你以官方后台的实时提示为准。重点是想清楚:如果只是个人练手、验证创意,个人主体完全够用;如果目标是商业运营,那企业主体迟早要办。

审核驳回最常见的三个原因我也记录一下:没有隐私政策、页面里有诱导分享或诱导关注的内容、页面实际功能与所选类目不符。隐私政策一定要提前写,并且要在“我的”页面留下入口。不要等到被驳回再去补,因为驳回一次要多等一天审核周期,现在审核排队时间一长,成本远比写两段隐私声明高。

6. 上线两周后的一些真话

6.1 真实数据带来的反思

把小程序发到朋友圈体验版之后,我拉了十几个朋友做了个小范围的灰度体验。两周数据里最让我意外的不是总打卡人数,而是两个行为特征:第一,创建目标后第二天还能回访打卡的比例相当高,但第一次创建目标本身的完成率很低——很多用户点开首页看到“创建目标”按钮就不动了。这说明“创建目标”仍然是一个存在门槛的动作,后面我计划把首个目标的创建流程缩成三步以内,默认给一个推荐目标,先打卡再改名。

第二,断签之后重新打卡的人远比想象中多。原本担心“连续天数断了就没人回来”,结果发现中老年人用户群根本不在乎连续天数,他们在乎的是“我这个月打了几次”。这给我提了个醒:数据维度要区分“连续”和“累计”,连续天数照顾仪式感,累计天数才照顾真正的成就感,两个都要在首页放出来。

还有一个数据层面的大坑:我最初没有做任何埋点,上线后才意识到根本不知道用户在哪一步流失。你现在如果要做类似项目,一定要从第一天就接一个简单的埋点方案——哪怕只是云函数里按操作名称计数,也别等上线后拍脑袋。没有数据支撑的迭代,基本等同于闭眼改需求。

6.2 如果你也想抄这个思路

最后分享几点可以“直接抄作业”的经验。

第一,不要在功能上堆料。这个项目能顺利上线,最大的原因是我克制住了“再加一个排行榜”“再加一个提醒”“再加一个头像框”的冲动。打卡工具的核心价值就是“让用户今天用最短路径完成一次确认”。所有的争议设计,上线之后用数据说话。

第二,云函数日志是你最好的调试工具。在云开发控制台里,每个云函数的每次调用都有日志,参数、返回值、异常堆栈清清楚楚。出现线上问题,先看日志,比任何抓包都好使。

第三,给项目留一点扩展余地。我现在这版已经很收敛了,但表结构上还是预留了goalId关联,后续可以扩展目标组、好友监督、数据导出,甚至接 AI 做每周打卡报告。云开发数据库改表结构成本很低,但一开始把字段拆得清晰些,后面会舒服很多。

第四,如果实在拿不准“这个交互会不会太复杂”,就把原型发给你妈用。我妈第一次打开“死了吗”就问了我一句话:“这页面是什么意思?看一眼就知道了。”我当时心想,这就对了。让一个第一次见面的人 10 秒内看懂你在做什么,比任何用户访谈都有效。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询