产假计算器这个小程序,是我花了一个周末从想法到落地的小项目,核心玩法很简单:输入末次月经或预产期,勾选几个条件,立刻算出产假起止日期和剩余天数。第一版同时发布到了微信小程序、抖音小程序、快手小程序,三个平台都开了流量主,靠看广告变现,后来把代码整理好放到了 GitHub 上开源。今天就把这条完整链路拆开讲清楚,从需求定位、技术选型、广告接入到上架审核和开源工程化,一条线写下来,给准备做小工具类小程序、想搞懂流量主接入、或者正打算试着开源项目的朋友做参考。
1. 项目定位与需求拆解
1.1 为什么偏偏做一个“产假计算器”
先聊需求。准妈妈最关心的一类是“我到底能休多少天”“从哪天开始休”,HR 和行政最怕的是被员工问“我这个情况按政策怎么算”。产假计算器正好卡在这个高频答疑点上:输入日期、选择附加条件,几十秒出结果。它不像社区或资讯应用需要长期留存,但用户一旦要用,就是真需求,愿意停下来认真操作。
工具型小程序还有一个好处:需求边界清晰。不需要账号体系、不需要后端重逻辑、不需要内容运营。第一版我甚至没有接后端,所有计算全在本地完成。这意味着可以很轻地跑起来,也能很快复制到多个平台。
不要想着第一版就做成“孕期全能助手”。加入孕期百科、产检提醒、在线咨询,复杂度会指数级上升,审核风险也变大。我的原则是:垂直场景、单点突破,把“算产假”这一件事做到极致。
1.2 “微信+抖音+快手”三平台分发的用户场景
很多开发者做小程序只盯微信,其实抖音和快手的生态价值很容易被低估。
- 微信端:核心是搜索和朋友群转发。公司同事之间互相转发,宝妈群里传播,熟人信任链让工具类小程序天然好传播。
- 抖音端:用户刷到产假相关短视频后,点击关联小程序直接算,属于“视频种草、小程序承接”的典型场景。
- 快手端:老铁经济下的信任感更强,家庭群、亲友群分享率高,很多用户愿意把结果截图发群里帮别人算。
三端共用一套业务逻辑就够了。由于我刻意不做账号体系,用户数据只存在本地,不需要服务端同步,跨平台复制成本很低。这也是把目标定为“三端同时上线”的关键前提。
2. 技术选型与核心逻辑设计
2.1 用 uni-app 一套代码跑三端
选型时我认真对比过三条路:原生分别写三套、Taro 跨端、uni-app 跨端。
原生写三套基本不现实,同样的页面要维护三个工程,广告组件、存储 API、导航栏适配全部要各写一遍,对个人开发者来说是巨大的维护成本。Taro 我也试过,React 语法写起来顺手,但当时对抖音小程序和快手小程序的支持还不够稳定,踩坑成本高。
最终选了 uni-app。原因有三个:
- 它对微信小程序、抖音小程序、快手小程序的支持比较成熟,条件编译机制可以让我针对平台差异代码做精细控制。
- Vue 语法上手快,周边插件和开源模板多,出问题容易搜到解决方案。
- HBuilderX 内置“发行到各小程序平台”的能力,一个项目能直接输出三端代码包,不用手动搬文件。
开发工具层面,我会同时打开微信开发者工具、抖音开发者工具、快手开发者工具,每次改完代码后用 HBuilderX 重新发行到对应平台,在真机上验证。注意这几个工具之间没有联动,需要手动刷新,流程上要有点耐心。
2.2 产假计算逻辑怎么建模
计算逻辑本身不复杂,但容易写死。我强烈建议把规则和代码分离,不要在前端把数字写死。
我用一个leaveRule配置对象来承载规则参数:
// config/leaveRule.js // 注意:上线前请把 0 替换为你所在地区的真实政策参数 // 也可以由后端接口下发,改了规则不用发版 export const LEAVE_RULE = { baseDays: 0, // 基础产假天数 prenatalDays: 0, // 预产期前可以休假的天数 extraDystociaDays: 0, // 难产/剖宫产额外天数 extraMultipleDays: 0, // 每多一胎额外天数 startOffsetFromEdd: 0 // 起始日相对预产期的偏移天数,-15表示预产期前15天 }实际计算结果时,核心函数大概是这个逻辑:
import dayjs from 'dayjs' export function calcMaternityLeave(lmp, options = {}) { const rule = { ...LEAVE_RULE, ...options.rule } const lmpDay = dayjs(lmp) if (!lmpDay.isValid()) { return null } // 预产期 = 末次月经第一天 + 280 天 const edd = lmpDay.add(280, 'day') // 产假起始日 = 预产期 + 可配置偏移 const startDate = edd.add(rule.startOffsetFromEdd, 'day') // 总天数 = 基础天数 + 附加天数 const extraDystocia = options.isDystocia ? rule.extraDystociaDays : 0 const extraMultiple = (Math.max((options.babyCount || 1) - 1, 0)) * rule.extraMultipleDays const totalDays = rule.baseDays + extraDystocia + extraMultiple // 结束日要减 1,因为总天数包含了起始日当天 const endDate = startDate.add(totalDays - 1, 'day') return { startDate: startDate.format('YYYY-MM-DD'), endDate: endDate.format('YYYY-MM-DD'), totalDays } }为什么要把配置拆出来?因为各地的产假天数和附加规则差异很大,而且政策可能有调整。把规则做成配置后,后面如果规则变了,要么改一份 JSON 再发版,要么直接从接口拉,不需要动计算逻辑,降低出 bug 的概率。
2.3 日期计算的边界与测试
日期计算最大的坑是边界条件:闰年、跨月、跨年、2 月 29 日、12 月 31 日。这些地方最容易算错。
我建议用 dayjs 而不是 moment。dayjs 体积小得多,API 风格和 moment 几乎一样,在工具类小程序里能省不少包体积。
测试用例我提前列了一张表,覆盖典型场景:
| 末次月经日期 | 场景说明 | 预产期计算结果(280天后) |
|---|---|---|
| 2024-01-01 | 闰年中的普通日期 | 2024-10-08 |
| 2023-12-31 | 跨年 | 2024-10-07 |
| 2024-02-29 | 闰年 2 月 29 日 | 2024-12-05 |
| 2025-01-31 | 跨月且跨小月 | 2025-11-07 |
上面只是普通预产期推算的技术示例,不构成任何医学建议。实际业务里还要考虑用户可能输入预产期而不是末次月经,所以我在页面里提供了两种输入方式,最终都归一化成“预产期”再来算。
3. 广告变现与流量主接入
3.1 流量主开通:先看门槛再发力
三个平台对流量主开通都设了门槛,一般和累计独立访客数(UV)挂钩,具体数值请以各平台后台最新规则为准。它们的通用流程是:
- 注册并认证小程序开发者。
- 发布小程序并通过审核,积累一定的真实 UV。
- 达到门槛后,在后台申请开通流量主。
- 开通后创建广告位,拿到
adUnitId。 - 将广告位 ID 填进代码,发版上线。
微信小程序一般是在小程序后台左侧菜单的“流量主”里申请,很多团队卡在累计 UV 指标上。我的做法是:开发完成后先在同事群、朋友群、宝妈群里做小范围分享,让大家真的使用、真的转发,而不是去刷量。刷量在小程序风控体系下非常危险,轻则流量主功能被限制,重则整个账号被处理,得不偿失。
抖音小程序和快手小程序的流量主开通条件类似,也需要达到平台要求的 UV 后,在“变现”“推广与广告”等菜单中申请。两个平台都可以创建激励视频和 Banner 广告位,代码位 ID 的用法逻辑和微信大同小异。
3.2 广告位怎么设计不赶客
小工具做广告,最怕的是用户刚算完结果,迎面就是一堆弹窗和横幅,直接劝退。我的广告位策略是:
- 底部 Banner:放在结果页最底部,作为常驻广告,高度固定,不能遮挡主要操作按钮。
- 激励视频:用户主动点击“完整保存计算报告”“分享给朋友”时才弹出,完整观看后发放奖励,不强制、不诱导误点。
- 插屏广告:第一版先不上,工具类页面操作路径太短,插屏容易打断心情,体验风险大。等收益模型跑通后再 A/B 测一下要不要加。
核心原则是:先给用户价值,再谈广告。用户看到计算结果,已经获得了核心服务,此时再插入广告的抵触情绪会小很多。
3.3 三端广告代码接入示例
uni-app 里可以用条件编译处理三个平台的广告 API 差异。比如激励视频的创建,封装一个工具函数:
// utils/ad.js export function createRewardedVideoAd(adUnitId) { // #ifdef MP-WEIXIN return wx.createRewardedVideoAd({ adUnitId }) // #endif // #ifdef MP-TOUTIAO return tt.createRewardedVideoAd({ adUnitId }) // #endif // #ifdef MP-KUAISHOU return ks.createRewardedVideoAd({ adUnitId }) // #endif }调用时监听关闭事件,判断用户是否完整看完:
const ad = createRewardedVideoAd(adUnitId) ad.onClose((res) => { if (res && res.isEnded) { unlockFullReport() // 完整看完,解锁完整报告 } else { toast('完整观看视频后才能保存报告哦') } })Banner 广告更简单,三个平台基本都是<ad unit-id="xxx">这种写法,在 uni-app 中同样可以用条件编译分别填不同的广告位 ID。
还有一点很重要:广告拉取不一定每次都成功。用户点击“保存报告”时,如果广告拉取失败,必须做兜底,不能直接让功能不可用。我的兜底策略是:广告加载失败超过 3 秒,直接放行,让用户使用功能,尽量不因为广告毁了核心体验。
3.4 收益预期管理
流量主收入不是做出来就能躺着赚钱的。工具类小程序 eCPM 通常不高,广告收入大致等于“曝光量 × eCPM ÷ 1000”,一个小体量工具每月可能只有几十到几百块,别抱暴富预期。
我更看重的是这条路本身的验证价值。通过这个项目,你能把“开发 → 上线 → 变现 → 开源”的完整链路跑通,之后再做任何垂直工具类小程序,都能直接复用这套模板。
4. 开源工程化与代码分发
4.1 开源前先把仓库收拾干净
代码开源不是把整个工程往 GitHub 一推就完事。一个合格的仓库至少要处理好几个点:
- 敏感信息必须移除,包括微信/抖音/快手的 AppSecret、广告位 ID、请求域名和内部接口地址。这些信息一旦泄露,轻则被薅广告费,重则账号被风控。
- 用
.gitignore过滤掉不该提交的文件,比如:
node_modules/ dist/ unpackage/ .env src/config/private.js .env.local- 目录结构要让陌生人一眼看懂。我最终整理出来的结构大致是:
leave-calculator/ ├── src/ │ ├── pages/ │ │ ├── index/ │ │ └── result/ │ ├── utils/ │ │ ├── calc.js │ │ └── ad.js │ ├── config/ │ │ └── leaveRule.js │ └── manifest.json ├── .gitignore ├── LICENSE ├── README.md └── package.json项目上线后我先开了私有仓库,等所有敏感信息都抽离干净后,再切换成公开仓库。这是最稳的流程,强烈建议不要先公开再清理,因为 Git 历史里的泄露很难彻底抹掉。
4.2 README 是门面
开源项目的 README 决定了别人会不会用、会不会顺手 star、甚至会不会提 PR。我的 README 包含了这几块:
- 项目简介:一两句话说明这个计算器能做什么。
- 效果展示:放一张结果页截图或录屏 GIF,直观得多。
- 支持平台矩阵:用表格列出微信、抖音、快手分别支持到哪个功能。
- 快速开始:
npm i、导入 HBuilderX、填 AppID、配置广告位、运行。 - 参与贡献:说明提 issue、提 PR 的规范。
- 免责声明:计算结果仅供参考,请以实际医嘱和政策为准。
GitHub 的 Topics 也不要浪费,可以打上wechat-miniprogram、douyin-miniapp、kuaishou-miniapp、mini-program、ad-monetization这类标签,方便被检索。开源之后,我收到了不少 issue,有人指出日期计算的边界 bug,还有人问能不能新增本地的政策参数,这些都是真实反馈,对项目完善很有帮助。
4.3 开源和流量主收益冲突吗
很多人担心开源了代码,别人复制拿去上线,会抢自己流量。实际影响很小。流量主的收益核心在于账号积累、运营推广和平台分发,代码只是其中最不稀缺的部分。每个小程序账号是独立的,别人复制代码也复制不了你的用户和口碑。
而且开源能带来额外的价值:技术社区关注度、简历加分、潜在的协作机会。我还基于这个开源项目接到了一个小需求,帮对方做了一份定制规则版本,这反而是闭源状态下不会出现的收入。
5. 三端上架与审核避坑
5.1 三个平台审核要求差异
三个平台的审核侧重点不完全一样,但工具类小程序整体比较容易过。
- 微信小程序:要求小程序备案,类目选择要准确,比如“工具 > 信息查询”。后台需要配置隐私保护指引,页面里也要有用户协议和隐私政策入口。
- 抖音小程序:审核时会真机打开计算页面,确认基本功能可用。抖音对“诱导分享”“诱导关注”等行为比较敏感,文案里不要出现“分享后才能算”“转发解锁”这类措辞。
- 快手小程序:同样看重页面功能完整性和广告行为合理性,避免出现明显的“诱导点击广告”设计。
工具类目通常不需要额外资质,但涉及“孕期”这种偏医疗健康的词,我建议在页面底部加一句免责声明:计算结果仅供参考,不构成医学意见,请以实际医嘱和政策为准。这句话能挡掉大量审核风险。
5.2 隐私政策和合规声明怎么写
因为项目没有账号体系、不收集个人信息,我就可以把隐私政策做得很简单。但“简单”不等于“没有”。三个平台都会要求小程序配置隐私政策,我在“关于”页面放了完整文本,并确保用户能一眼看到入口。
合规声明方面,除了医疗免责,还要给广告行为做说明:小程序内展示的广告由平台提供,用户可自主选择是否观看激励视频,不影响核心计算功能。
5.3 审核被拒的真实案例
我在上架过程中实际踩过的坑,整理成三个典型 case:
- Case 1:Banner 遮挡按钮被拒。第一版把 Banner 放在页面底部固定定位,结果在部分机型上把“重新计算”按钮挡住了。解决方式是给底部操作区预留安全距离,Banner 高度固定,并加
safe-area-inset-bottom适配。 - Case 2:文案出现绝对化用语。简介里写了“最准确的产假计算器”,审核直接打回,提示涉嫌夸大宣传。改成“结果仅供参考”之后才通过。
- Case 3:抖音端广告拉不出来。广告位是新建的,但没有在后台先提交审核,真机测试时经常拉取失败。虽然核心功能是好的,但审核人员打开页面看到广告区域空白,体验不好。正确操作是提前把广告位审核过了再发版。
6. 常见问题与排查技巧实录
6.1 广告组件调不起来怎么办
排查顺序按这个来基本不会漏:
- 确认
adUnitId是否对应正确的平台,三端的广告位 ID 不能混用。 - 到平台后台确认广告位状态,是不是“已通过”或“生效中”。
- 看开发者工具控制台的报错信息,记下错误码去搜。
- 一定要真机测试,开发者工具里广告组件和真机表现经常不一致。
如果后台显示广告位正常但前端拉取失败,先检查当前小程序的 AppID 是否和广告位所属账号一致。很多人会用测试号开发,测试号里广告位大概率拉不出来。
6.2 三端样式兼容
跨端最容易出问题的就是自定义导航栏。微信、抖音、快手的胶囊按钮位置不同,需要动态获取:
const rect = uni.getMenuButtonBoundingClientRect() // 返回 { top, bottom, left, right, width, height }拿到胶囊位置后,把自定义导航栏的高度和左右留白按照 rect 动态设置,这样三端才能保持统一。另外,rpx 在不同平台下的换算比例可能略有差异,涉及绝对定位的样式要多真机验证。
6.3 计算逻辑的边界 bug 清单
这个项目的核心计算逻辑出现过两个印象深刻的 bug:
一是日期格式化问题。dayjs默认格式化是YYYY-MM-DD,但如果用户时区设置异常,个别机型会在跨日时产生偏差。解决方式是输入输出都用平台提供的picker组件,类型统一为日期字符串,避免手输导致的格式污染。
二是“总天数是否减 1”的语义问题。从 1 号休到 3 号,如果天数写 3,结束日要算成 3 号,而这实际上是“3 - 1 + 1 = 3”天。很多新手会把结束日直接加总天数,导致多算一天。这个逻辑我加了单测,也建议你加到 CI 里,后续改规则不容易回归。
如果你也想跑一遍“开发 → 多端上线 → 流量主变现 → 开源”的完整闭环,我最大的建议是:别做大而全的东西,找一个你身边真实存在的垂直需求,小切口、快上线、再迭代。产假计算器只是其中一个例子,你每天工作中那些小到不好意思提的麻烦,往往就是最好的切入点。
最后再分享一个重要教训:广告位 ID 和 AppSecret 千万记得放进.gitignore,我因为这个疏忽在开源仓库里留过一次敏感配置,虽然发现后马上删了并换了 key,但过程相当被动。开源是好事,前提是把家门收拾干净。