feat: 配方volume字段存储编辑器选择的容量
All checks were successful
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 12s
Test / e2e-test (push) Successful in 52s

- 后端: recipes表新增volume列,API返回/保存volume
- 前端: 保存时发送formVolume,编辑时优先用stored volume
- 容量显示优先级: stored volume > 椰子油计算 > 产品ml求和
- 修复编辑器容量选择保存后不生效的bug

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 17:31:04 +00:00
parent 13b675e63d
commit 74a8d9aeb6
4 changed files with 54 additions and 22 deletions

View File

@@ -48,6 +48,15 @@ const priceInfo = computed(() => oilsStore.fmtCostWithRetail(props.recipe.ingred
const isFav = computed(() => recipesStore.isFavorite(props.recipe))
const volumeLabel = computed(() => {
// Priority 1: stored volume from editor selection
const vol = props.recipe.volume
if (vol) {
if (vol === 'single') return '单次'
if (vol === 'custom') return ''
if (/^\d+$/.test(vol)) return `${vol}ml`
return vol
}
// Priority 2: calculate from ingredients
const ings = props.recipe.ingredients || []
const coco = ings.find(i => i.oil === '椰子油')
if (coco && coco.drops) {
@@ -56,7 +65,7 @@ const volumeLabel = computed(() => {
if (ml <= 2) return '单次'
return `${Math.round(ml)}ml`
}
// Non-coconut: sum all portion products as ml
// Priority 3: sum portion products as ml
let totalMl = 0
let hasProduct = false
for (const ing of ings) {

View File

@@ -171,7 +171,7 @@
/>
<div class="row-info" @click="editRecipe(r)">
<span class="row-name">{{ r.name }}</span>
<span v-if="getVolumeLabel(r.ingredients)" class="row-volume">{{ getVolumeLabel(r.ingredients) }}</span>
<span v-if="getVolumeLabel(r.ingredients, r.volume)" class="row-volume">{{ getVolumeLabel(r.ingredients, r.volume) }}</span>
<span class="row-tags">
<span v-for="t in [...(r.tags || [])].sort((a,b)=>a.localeCompare(b,'zh'))" :key="t" class="mini-tag">{{ t }}</span>
</span>
@@ -777,17 +777,25 @@ function editRecipe(recipe) {
const coco = ings.find(i => i.oil === '椰子油')
if (coco) {
formCocoRow.value = { ...coco, _search: '椰子油', _open: false }
// Guess volume from total drops
const eoDrops = ings.filter(i => i.oil && i.oil !== '椰子油').reduce((s, i) => s + (i.drops || 0), 0)
const totalDrops = eoDrops + coco.drops
const ml = totalDrops / DROPS_PER_ML
if (ml <= 2) formVolume.value = 'single'
else if (Math.abs(ml - 5) < 1.5) formVolume.value = '5'
else if (Math.abs(ml - 10) < 2.5) formVolume.value = '10'
else if (Math.abs(ml - 15) < 2.5) formVolume.value = '15'
else if (Math.abs(ml - 20) < 3) formVolume.value = '20'
else if (Math.abs(ml - 30) < 6) formVolume.value = '30'
else { formVolume.value = 'custom'; formCustomVolume.value = Math.round(ml) }
// Use stored volume if available, otherwise guess from drops
if (recipe.volume) {
formVolume.value = recipe.volume
if (recipe.volume === 'custom') {
const totalDrops = ings.reduce((s, i) => s + (i.drops || 0), 0)
formCustomVolume.value = Math.round(totalDrops / DROPS_PER_ML)
}
} else {
const eoDrops = ings.filter(i => i.oil && i.oil !== '椰子油').reduce((s, i) => s + (i.drops || 0), 0)
const totalDrops = eoDrops + coco.drops
const ml = totalDrops / DROPS_PER_ML
if (ml <= 2) formVolume.value = 'single'
else if (Math.abs(ml - 5) < 1.5) formVolume.value = '5'
else if (Math.abs(ml - 10) < 2.5) formVolume.value = '10'
else if (Math.abs(ml - 15) < 2.5) formVolume.value = '15'
else if (Math.abs(ml - 20) < 3) formVolume.value = '20'
else if (Math.abs(ml - 30) < 6) formVolume.value = '30'
else { formVolume.value = 'custom'; formCustomVolume.value = Math.round(ml) }
}
// Guess dilution
if (eoDrops > 0 && coco.drops > 0) {
const ratio = Math.round(coco.drops / eoDrops)
@@ -1091,6 +1099,7 @@ async function saveCurrentRecipe() {
ingredients: mappedIngs,
note: formNote.value,
tags: formTags.value,
volume: formVolume.value || '',
}
try {
await recipeStore.saveRecipe(payload)
@@ -1455,7 +1464,15 @@ function openRecipeDetail(recipe) {
if (idx >= 0) previewRecipeIndex.value = idx
}
function getVolumeLabel(ingredients) {
function getVolumeLabel(ingredients, volume) {
// Priority 1: stored volume
if (volume) {
if (volume === 'single') return '单次'
if (volume === 'custom') return ''
if (/^\d+$/.test(volume)) return `${volume}ml`
return volume
}
// Priority 2: calculate from ingredients
const ings = ingredients || []
const coco = ings.find(i => i.oil === '椰子油')
if (coco && coco.drops) {
@@ -1464,7 +1481,7 @@ function getVolumeLabel(ingredients) {
if (ml <= 2) return '单次'
return `${Math.round(ml)}ml`
}
// Non-coconut: sum portion products, mixed units all convert to ml
// Priority 3: sum portion products as ml
let totalMl = 0
let hasProduct = false
for (const ing of ings) {