Skip to content
EchoHeimPublic

About

A WeChat Mini Program that answers "what should I eat": dish library, filter-based random picker, meal calendar with daily mood notes, synced across devices with WeChat Cloud Development.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

今天吃什么

一个帮你决定「这一顿吃什么」的微信小程序。

收录自己的菜品,按餐次、口味、预算圈定范围,摇一次骰子定下来;吃完记一餐、写一句当天心情。日历把每一餐和心情放进同一张日卡,往回翻能看见自己这大半年是怎么吃的。另有一个独立的日期提醒模块,支持公历 / 农历与循环规则,可选接入微信订阅消息做真正的到点推送。

技术形态是原生小程序 + Skyline 渲染 + 微信云开发,无 npm 依赖、无构建步骤,导入微信开发者工具即可编译运行。

深入一点的文档

README 只留「这是什么 / 怎么跑起来」。实现细节拆在 docs/ 下,改代码前按需要翻:

文档 讲什么
docs/sync.md 本地优先的数据流、补传秩序、增量水位、纯函数引擎清单
docs/data-model.md 目录结构、五个云集合、必建索引
docs/reminder-engine.md 提醒的四类循环语义、「提前 N 天」、5 分钟窗口、订阅额度
docs/deploy.md 云函数部署、定时触发器的唯一重建方式、推送链路排障
docs/privacy.md 隐私保护指引的声明清单、可粘贴全文、错误码自检

功能

随机推荐(首页)

  • 四个维度可叠加筛选:餐次(早餐 / 午餐 / 晚餐 / 夜宵)、分类(13 类)、用餐方式(自己做 / 外卖 / 堂食)、预算档(经济 / 普通 / 丰盛 / 豪华,对应 10 元以下 / 10–30 / 30–120 / 120 元以上)
  • 点「开始随机选」播放约 1.2 秒滚动动画(12 步 × 100ms)后在候选里落定;候选超过一道时保证不与上一次相同
  • 不是均匀随机,是按权重抽:评分(5 分 ×1.6、1 分 ×0.4)、多久没吃(60 天以上 ×1.5)、吃过次数(20 次 ×0.5)三项相乘。打过 2 分又刚吃过的菜明显少出现,但不是永不出现
  • 自动避开最近 N 天吃过的菜(1 / 3 / 7 / 14 / 30 天,默认 7 天);打开「允许近期重复」后不再硬过滤,改为把最近吃过的菜再压到 1/4 权重——允许重复不等于优先重复
  • 单道菜可以单独标记为不参与随机
  • 结果卡显示「3 天前吃过」与平均分,确认后点「就吃这个」直接进记录页,餐次已预填
  • 「最近吃过」六个入口:按记录时间倒序取最近六道不同的菜(刚好铺两行),点一下直接进记录页(菜品 / 餐次 / 日期都预填),把「记一餐」从五步压到两步
  • 候选为空时提示放宽条件,并给出一键清空

节日(utils/festival.js)

  • 三类共 38 个:中国传统节日 14 个(腊八 / 小年 / 除夕 / 春节 / 元宵 / 龙抬头 / 上巳 / 端午 / 七夕 / 中元 / 中秋 / 重阳 + 清明 / 冬至两个节气节日)、国际节日与国际日 18 个、中国法定纪念日 6 个
  • 农历节日每年现算(走 utils/lunar.js),不写死某一年的公历日期;闰月不重复过节;除夕按「腊月最后一天」算,腊月廿九那年也对
  • 清明与冬至是节气,用寿星通用公式 int(Y×0.2422 + C) - int((Y-1)/4) 算,误差最多一天
  • 母亲节 / 父亲节 / 感恩节是「某月第 N 个星期几」,按当月第几个算
  • weekFestivals() 返回本周剩余节日(最多 3 条),按日期升序

菜品库

  • 搜索(菜名 / 标签)、按分类过滤、四种排序:最近添加、名称、评分、吃过次数
  • 新建、编辑、详情、删除。字段:名称(20 字)、Emoji 图标(内置图标库,默认平铺 24 个,可展开)、分类、标签、适用餐次、用餐方式、预算档、备注(200 字)
  • 评分与吃过次数不由用户填写,统一从饮食记录聚合重算;改一餐会同时回算被换下和被换上的两道菜

记录与吃饭日历

  • 日期倒序的日卡列表,一餐一条记录:餐次、菜品、日期、具体时间(时:分,默认取当前时刻、可改)、1–5 分(0.5 步进)、四档状态(吃得很好 / 还不错 / 一般般 / 不太行)、200 字随手记
  • 搜索:日历只能按时间翻,翻不到「上次那家店叫什么」。输入关键词即切换成结果列表,菜名 / 备注 / 状态 / 餐次都能命中,最多返回最近 50 条;清空回到日历
  • 日卡里的餐按时间先后排;老记录没有 time 时用 createdAt 兜底,列表里不会留空
  • 日历页一天一张卡:心情图标 + 随笔 + 当天各餐。有心情的日期在日历上显示心情图标,只有餐的显示当天第一道菜的图标
  • 点日历格子只看那一天,再点月份标题右边的「月」回到整月;日历以当月为锚点,往前 12 个月、往后 6 个月,左右滑动或点两侧箭头翻页(到头会停住);翻到别的月份时标题右边还会多一个「今」,点一下直接跳回今天所在的月
  • 月份标题右边这两个小胶囊按需出现:今 只在不是本月时出现,月 只在选中了某一天时出现,都不是就都不出现
  • 「+ 一餐」可补记历史日期,不能选未来;点某一餐是编辑(菜品 / 餐次 / 日期 / 评分 / 状态 / 备注都可改),长按是删除
  • 顶部汇总当前范围的「N 餐 · M 天心情」
  • 日历下面挂「本周节日」:本周还剩的节日才出现(周一算一周之始,已经过去的不提醒),一行一个,带倒计时、分类标签和「吃点:XX」;看当月且没挑某一天时才挂
  • 节日卡那行小字二选一:有吃食的写「吃点:XX」,没有的写一句和这日子有关的话(15 字以内,比如教师节「给老师说声谢谢」)。两句不会同时出现
  • 右侧的日期分两种:选中某一天时,节日 / 提醒 / 纪念日都显示农历(中秋 → 八月十五,教师节 → 七月廿九),因为那天在日历上已经写了公历,重复一遍没意义;没选中时(「本周节日」那块)只显示公历,那块要横着扫七个日子,公历更好认
  • 在日历上点某一天,那一块改成显示那天的节日、当天会响的提醒与纪念日(标题「X 月 X 日这天」)。那天要是空白,会给一张「这天还是空白 · 写心情」的卡,点一下直接进那天的签到页(未来日子不给这张卡,只提示「这天还没到」)
  • 整月视图且今天没记录时,同样挂一张「今天还是空白 · 写心情」的卡 —— 两种空态共用一个组件,只是文案和跳转目标跟着选中态走
  • 空态卡左边那个天气图标每次卡片出现都换一个(12 种 emoji 的库里随机)—— 选中某天、取消选中、翻月重绘都会重新挑,不是写死的一朵云
  • 未来的日子也能点开看(比如提前看下个月节日落在星期几),但只读:签到与记餐仍然只到当天,日历里点签到 / 补一餐会被拦下并提示,两个表单本身也会再拦一次
  • 有节日的那天,日历数字是亮蓝色;自己加的纪念日那天是亮紫色。同一天两者都有时紫色赢;今天 / 选中态的颜色压过它们
  • 有提醒的日子,数字右上角挂一个橙红小圆点(停用的提醒不算;纪念日不点,它已经有紫色数字了)。它是绝对定位的角标,不占格子高度;压在「今天」的橙框上时靠白描边分开
  • 当周存在的自定义纪念日也会列进同一块,标题自动变成「本周节日 · 纪念日」

心情与签到

  • 只记心情,不掺饮食(和「记一餐」是两条独立数据线);16 个心情,签到页 4×4 宫格选择(images/moods/*.png:羞愧/厌恶/自豪/嘴馋/嫉妒/愤怒/高兴/孤独/焦虑/满足/迷茫/难受/疲惫/平静/期待/惬意);200 字随笔;一天一条,再保存即覆盖;可补历史,不能选未来
  • 老心情(开心/兴奋/平淡/低落/忙乱/瞌睡)在 utils/checkin.js 的 LEGACY_KEYS 里映射到新的一套(高兴/期待/平静/难受/焦虑/迷茫),历史签到不会变成同一个表情
  • 页面顶部每进一次随机显示一句古诗词(诗经 / 楚辞 / 唐诗 / 宋词共 400 句,单句不超过 20 字),取自 utils/quotes.js,不会连着两次重复
  • 连续签到天数:今天已填则从今天往前数,今天还没填则从昨天往前数,当天补填后自动接上
  • 心情页汇总本月心情分布(按次数倒序)、最近 14 天心情条带(缺失那天显示为灰点)与历史心情列表
  • 首页顶部有一条签到提示,按「今天签没签」分流:没签到点它直接进今天那张写心情的表单(少一步),已经签过就跳到签到记录页(那里点某天还能回头改);右侧的「去签到 / 看记录」跟着状态变

日期提醒

完整语义见下文「日期提醒:能力与含义」,这里只列形态。

  • 入口在「我的 → 日期提醒」,列表按 纪念日 / 今天 / 未来 7 天 / 更远 / 已关闭或已结束 分组
  • 支持公历与农历(含闰月,1900–2100)、不重复 / 每天 / 每月 / 每年四种循环、当天 / 提前 1 天 / 提前 3 天三种提前节点
  • 「每天」可以改成每 N 天一次(表单里循环方式下面有步进器,默认 1 = 每天,范围 1–365);节奏以起始日期为准,10-01 建、每 3 天 → 10-01 / 10-04 / 10-07…,列表标签跟着写成「每 3 天」
  • 同一张表单还能建「纪念日」:纪念日每年都有(公历按同月日、农历按农历同月日)
  • 纪念日默认不推送,只在日历上把那天标成紫色;打开「到期提醒」开关后,可以像普通提醒一样选**当天 / 提前 1 天 / 提前 3 天(可多选同时提醒)**与提醒时间,会照常推送并计入当日角标;停用整条则不推
  • 云函数扫描时按 kind + notify 判断:没开提醒的纪念日一律跳过,开了的跟普通提醒走同一套触发计算
  • 卡片开关可临时停用,轻点编辑、长按删除;「我的」页会显示条数与当日提醒角标
  • 应用内提醒开箱可用;微信服务通知需要自己申请模板并部署云函数

个人资料与统计

  • 收录菜品数、本月记录数、平均评分、「目前最喜欢」(评分最高者,并列时取吃过次数多的)
  • 昵称与头像走 chooseAvatar / 昵称填写能力,头像上传云存储,换设备自动恢复
  • 三个偏好项:默认餐次、最近天数、是否允许重复(默认开着,最近吃过的只是降权到 1/4,不硬性排除)

月度饮食报告(utils/report.js + pages/report)

入口在「我的 → 饮食报告」,可往前翻月份,不给看未来月份。

  • 三个数字:本月餐数、有记录的天数、平均分;下面一行「比上月多 / 少 N 餐」
  • 最常吃 Top5:按菜名合并(菜品被删了也还能统计,菜名是记下那一刻的快照),次数相同按总分排
  • 评分走势:一天一根柱子,按 5 分制换算高度;没记录的天留一条浅色底线
  • 餐次分布:早 / 午 / 晚 / 夜宵四条横条,带百分比
  • 口味偏好:按菜品标签统计,只认「辣 / 清淡 / 减脂 / 高蛋白 / 快手」五类(「快餐」「聚餐」不算口味)

备份与恢复(文件形式)

入口在「我的 → 数据」,导出和导入都走文件,不碰剪贴板。

  • 导出:生成 今天吃什么-备份-20261030.json 写到本地用户目录,再调 wx.shareFileMessage 打开微信转发面板——可以直接发给自己(文件传输助手)或任意联系人。用文件而不是剪贴板,是因为几千条记录约 800KB,剪贴板在部分机型会被截断或静默失败;低版本微信用不了转发时会退回复制文本
  • 导入:wx.chooseMessageFile 从聊天记录里挑 .json(文件得先发到某个聊天里),先预演一遍告诉用户「会导入 N 条、跳过 M 条」,确认后才真正写入
  • 合并规则与云端同步完全一致:同 id 比 updatedAt,新的赢;本地独有的条目不删。同一份文件导两次,第二次全部跳过
  • 文件带 schema 版本号。比当前版本新的备份会被拒绝——更高版本可能带了这个版本不认识的字段,硬导进来会静默丢字段
  • 导入的数据逐条排进待传清单,syncFromCloud 每轮最多补传 40 条(一条要 2 次网络往返),量大时需要多开几次小程序才能全部上云
  • 导出时剥掉 cloudId(换机器没意义的本地指针)、avatarFileId(绑账号的云存储地址)、pushQuota(绑模板的授权次数)
  • 「我的」页的占用数字走 wx.getStorageInfoSync,一次同步调用拿总量,不解析任何数据

清空数据

入口在「我的 → 危险操作」,本机与云端一起删,删完回到新装状态:8 道预置菜、空记录、默认设置、推送额度归 0。

昵称和头像会保留——它们标识「你是谁」,不属于「你吃了什么」。清一次库就要重新设昵称、重新传头像,那是找骂。云端 user_settings 虽然也被删,但下次同步会用本地这份重建,头像文件本来就躺在云存储里。

  • 确认弹窗逐项列要删的数量(多少条记录 / 多少天心情 / 额度还剩几次),不是笼统的「确定吗」——看见具体数字才会真的犹豫
  • 只清本机是不够的:下一次 syncFromCloud 会按「本地没有、云端有」把数据全拉回来,用户会觉得根本没清掉。所以云端五个集合一起清,user_settings 删掉后推送额度也随之消失
  • 清空那一刻如果断网,会落一个 wte_wipe_pending_v1 标记。下次同步开头先补删云端,并且这一轮跳过拉取——否则刚删的东西会被灌回来。补删成功才摘标记
  • 订阅勾选状态不清(wte_sub_state_v1):它只是「总是保持以上选择」在本地的镜像,微信侧本来就不受清空影响。跟着清掉只会让自动补额度整个瘫掉,用户还得再新建一次提醒才能恢复。所以 clearAllData 的删除列表里唯独排除了它
  • 微信侧的订阅授权(模板那个开关)没有接口能撤销。要彻底停掉推送,得用户自己去「设置 → 订阅消息」里关

意见反馈

入口在「我的 → 反馈 → 意见反馈」。

  • 类型三选一(功能建议 / 遇到问题 / 其他),内容上限 500 字,最多 3 张截图,邮箱选填
  • 只收邮箱,不要改成「微信号 / QQ 号 / 手机号」:2026-11 提审按运营规范 5.12「非合理场景下收集微信帐号等敏感信息」被驳回,当时提示语就是「微信号 / 邮箱」。IM 账号一律不能出现在收集字段里
  • 自动带上版本、机型、系统、微信版本与来源页——线上问题最缺的就是这些,指望用户手打不现实
  • 提交走云函数 feedbackSubmit:服务端再校一遍长度和类型,图片只收本环境的 cloud:// fileID(外部链接会把通知变成钓鱼入口),超长内容按 500 字截断
  • 落库后推一条群机器人消息:企业微信和飞书都行(地址放云函数环境变量 FEEDBACK_WEBHOOK,仓库是公开的,key 不进代码)
    • 渠道按 host 自动认:qyapi.weixin.qq.com → 企微 markdown,open.feishu.cn → 飞书 post 富文本;认不出来用 FEEDBACK_WEBHOOK_TYPE=wecom|feishu 强制指定
    • 飞书机器人若开了「签名校验」,密钥放 FEEDBACK_WEBHOOK_SECRET,云函数会按飞书的算法(HMAC-SHA256(key = timestamp\nsecret, data = ""))现算 timestamp + sign
    • 成功与否不只看 HTTP 状态码,还看响应体里的 errcode / code——地址填错时也是 200,不回查就会记成「已推送」
    • 截图发的是云存储临时链接,不是内嵌图片:飞书图片消息要 image_key,得用应用凭证调 im/v1/images 上传,群机器人没这个身份。链接默认 2 小时有效,过期点开 403;取链接失败会降级成「N 张(临时链接没取到)」
    • 没配也能用,只是少一条推送;推送失败也照样算提交成功
  • 同一台设备 60 秒内只放一条,防手滑连点
  • 集合不存在时云函数会自己 createCollection,第一次提交就能通,不必先去控制台建表

跨设备同步

  • 同一微信账号在不同手机上读同一份云端数据;不同账号通过 _openid 与集合权限隔离
  • 写操作本地先落盘,云端失败只提示不阻断;某个集合缺失时只让对应功能退回本地缓存,补齐后下次启动自动补传

技术结构

选型

层 选型 说明
运行形态 原生小程序(JavaScript / WXML / WXSS) 无 npm、无构建;Skyline 渲染 + glass-easel,全局自定义导航栏
本地存储 wx.setStorageSync 菜品 / 设置 / 提醒 / 签到各一个键;记录按年分片(wte_records_2026_v1),首屏只读本地
云端数据 云开发文档数据库 5 个集合,全部「仅创建者可读写」
云端文件 云存储 头像;同样仅创建者可读写
云端计算 云函数 reminderPush / feedbackSubmit / syncBatch Node + wx-server-sdk;定时推送、反馈通知、批量补传
消息 订阅消息 前端 wx.requestSubscribeMessage,云端 openapi.subscribeMessage.send
测试 Node 直跑 12 套回归测试脚本(仅本地 .workbuddy/tests/,不进仓库)

本地运行

  1. 安装并打开微信开发者工具
  2. 导入本项目根目录
  3. 确认登录微信号拥有该小程序的开发权限
  4. 确认云环境、五个必需集合(dishes / meal_records / user_settings / reminders / daily_checkins)与云存储权限已就位;error_logs 可选
  5. 点「编译」,再用「预览」做实机测试

项目不依赖 npm,无需安装依赖或构建。只有部署提醒推送云函数时才需要 wx-server-sdk,由开发者工具的「云端安装依赖」自动完成。

About

A WeChat Mini Program that answers "what should I eat": dish library, filter-based random picker, meal calendar with daily mood notes, synced across devices with WeChat Cloud Development.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages