同名食材不能直接相减:一个 JavaScript 用量模型的边界

开发小程序「食愿小灶」时,食材数据里有个问题很容易被一句"统一单位"带过去:菜谱要 400g 番茄,冰箱里记了 3 个番茄,该算还缺多少?

如果只按名称合并,再做减法,得出的 397 没有意义;如果替每个番茄估一个重量,又把未经确认的经验写进了用户的数据。我在仓库的领域层保留了一条限制:名称相同、单位可比较,数量才能参与同一笔计算。这篇讲的是这条用量规则;产品里的采购清单由用户按菜选择需要准备的材料。

第一步是收紧换算范围。源码只明确换算两组:重量统一到 g,体积统一到 ml。kg、g,以及按中国大陆市制处理的斤、两,可以放进同一组;L 和 ml 属于另一组。瓶、把、勺等单位通过输入校验后仍保留自身单位,换算系数为 1。一瓶牛奶多大、一把葱多重,当前记录没有提供答案。

关键落在归一化后的键上。下面是 normalizedIngredient 的数字用量分支,name 已经过名称清洗:

js 复制代码
const [unit, factor] = conversions[ingredient.unit] || [ingredient.unit, 1];
return {
  name, unit, factor,
  quantity: round(Number(ingredient.quantity) * factor),
  key: `${name}|${unit}`
};

例如,西红柿 0.4kg 会得到 番茄|g,数量为 400。番茄 3 个得到 番茄|个。名称别名可以归一,单位的边界仍在。后面的聚合函数用这个键组织 Map,半瓶牛奶和 100ml 牛奶就会留下两行,不会因为名字一样而混成一个总量。重量与体积之间也没有默认换算。

库存比较沿用同一个键。在 getRequirements 中,只有键相同的库存进入可用量;同名却单位不同的库存,会触发 unitMismatch。开头那个例子因此得到 missing: 400 和 unitMismatch: true。

这里还藏着一个容易误读的地方:400 表示已录入的、可按克比较的库存没有抵扣这项需求。它不能单独证明还应再买 400g。那三个番茄仍然存在,只是模型不知道它们多重。上层展示需要解释这个标记,使用者也需要核对实际材料。保留标记只是让问题可见,包装规格和实际重量仍需另外获取。

"适量"和"少许"也受这条规则约束。它们保存为文字用量,quantity 是 null,unit 是空字符串。聚合时保留文字及关联菜品,缺口计算返回待确认状态。两道菜都需要盐适量,不会被乘成"盐两份适量",也不会因没有数字而当成零克。校验还会拒绝文字用量同时携带数字和单位,避免一条记录出现两套解释。

我用现有回归用例把几个边界固定下来:

  • 400g 番茄遇到 3 个番茄库存,克重缺口保留,并标记单位不一致。
  • 半瓶牛奶和 100ml 牛奶汇总后仍是两行。
  • 0.5 斤、2 两和 0.1kg 大米合计 450g,原菜谱中的数量与单位保持原样。

第三项也提醒我把计算结果和用户输入分开:计算可以统一到克,菜谱依然应该保留原来的表达。以后再次编辑时,用户能认出自己填过的半斤米;其他逻辑也不会把已转换的数值重复转换。

这套约束有代价:同名食材可能保留多行,遇到不同单位还需要人判断。若以后要支持"一瓶等于多少毫升",就要把具体包装规格及其适用范围存下来。仅往通用换算表里添一个"瓶"的系数,会让所有瓶装材料共享一个未经确认的容量。

实现可从仓库的 kitchen.js:归一化与校验、需求计算 读起;例子对应 基础计算测试 和 单位测试。链接固定到同一份源码提交,方便对照。

我是小程序「食愿小灶」的开发者,这是我自己的项目,主要做家庭共同选菜、餐食安排和备料清单。

体验入口:在小程序中搜索「食愿小灶」即可找到。

源码在 GitHub:pydaxing/shiyuan。如果你也做过类似的数量模型,欢迎交流那些应该交给程序计算、又需要留给人确认的边界。

创作说明:本文使用 AI 辅助整理文字,技术描述、代码片段和测试用例已对照公开源码核查。

相关推荐
独隅27 分钟前
WebStorm Windows 版 React / Next.js 实战替换指南
javascript·windows·webstorm
apollo_qwe41 分钟前
接口超时了,前端为什么不能直接重试?
javascript·http
toooooop85 小时前
CRMEB 故障排查笔记:安全网关只放行 GET/POST 怎么办
前端·javascript·vue.js·thinkphp6·crmeb
软件技术NINI6 小时前
基于SpringBoot和Vue的项目评审系统
javascript·vue.js·spring boot·后端·spring·毕业设计·毕设
vipxieliang6 小时前
JavaScript 模板字符串与标签模板:不只是拼接
前端·javascript
Dream-Y.ocean16 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Highcharts.js17 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
kyriewen20 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
工控丫丫21 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库