From d5edc57b989734271f9688dda2d7567a981c8279 Mon Sep 17 00:00:00 2001 From: Hera Zhao Date: Fri, 10 Apr 2026 13:57:47 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=85=8D=E6=96=B9=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E4=B8=8E=E7=AE=A1=E7=90=86=E9=85=8D?= =?UTF-8?q?=E6=96=B9=E7=BC=96=E8=BE=91=E5=99=A8=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RecipeDetailOverlay 编辑器改为新版:容量选择+参考比例+椰子油自动填满 - 和 RecipeManager 新增/编辑完全一致的界面和逻辑 - 实时显示配方摘要(用量/容量/稀释比例) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/RecipeDetailOverlay.vue | 288 ++++++++---------- 1 file changed, 127 insertions(+), 161 deletions(-) diff --git a/frontend/src/components/RecipeDetailOverlay.vue b/frontend/src/components/RecipeDetailOverlay.vue index 56e5599..3c325c4 100644 --- a/frontend/src/components/RecipeDetailOverlay.vue +++ b/frontend/src/components/RecipeDetailOverlay.vue @@ -144,154 +144,72 @@ - -
- 💡 推荐按照单次用量(椰子油10~20滴)添加纯精油,系统会根据容量和稀释比例自动计算。 + +
+ +
+ + + + + + + +
+
+ + ml +
+
+ 参考比例 1: + + 纯精油约 {{ editorSuggestedEo }} 滴 +
- +
- - - - - - - + - + + + + + + + + + - - - + + +
精油滴数单价/滴小计
精油滴数单价/滴小计
{{ ing.oil ? oilsStore.fmtPrice(oilsStore.pricePerDrop(ing.oil)) : '-' }}{{ ing.oil ? oilsStore.fmtPrice(oilsStore.pricePerDrop(ing.oil) * (ing.drops || 0)) : '-' }}
椰子油 - - - {{ ing.oil ? oilsStore.fmtPrice(oilsStore.pricePerDrop(ing.oil)) : '-' }} - - {{ ing.oil ? oilsStore.fmtPrice(oilsStore.pricePerDrop(ing.oil) * (ing.drops || 0)) : '-' }} - - + + {{ oilsStore.fmtPrice(oilsStore.pricePerDrop('椰子油')) }}{{ oilsStore.fmtPrice(oilsStore.pricePerDrop('椰子油') * editorCocoActualDrops) }}
- - -
-
- -
-
- {{ name }} - {{ oilEn(name) }} -
-
-
- - - -
- +
- -
- -
- - - - - -
- - -
- - -
- - -
- 稀释比例 1: - -
- - - -
- {{ dilutionHint }} -
-
+ +
{{ editorSummaryText }}
@@ -308,35 +226,18 @@ ×
-
- + {{ tag }} + + {{ tag }}
-
- +
- 总计: {{ editPriceInfo.cost }} - - 零售 {{ editPriceInfo.retail }} - + 总计: {{ editorTotalCost }}
@@ -789,7 +690,54 @@ function cancelAddRow() { const selectedVolume = ref('single') const customVolumeValue = ref(100) const customVolumeUnit = ref('drops') -const dilutionRatio = ref(3) +const dilutionRatio = ref(6) +const editCocoRow = ref({ oil: '椰子油', drops: 10 }) + +const editEoIngredients = computed(() => + editIngredients.value.filter(i => i.oil !== '椰子油') +) +const editorEoDrops = computed(() => + editEoIngredients.value.filter(i => i.oil && i.drops > 0).reduce((s, i) => s + i.drops, 0) +) +const editorTargetDrops = computed(() => { + if (selectedVolume.value === 'single') return null + if (selectedVolume.value === 'custom') return Math.round((customVolumeValue.value || 0) * DROPS_PER_ML) + return Math.round(Number(selectedVolume.value) * DROPS_PER_ML) +}) +const editorCocoActualDrops = computed(() => { + if (!editCocoRow.value) return 0 + if (selectedVolume.value === 'single') return editCocoRow.value.drops || 0 + if (!editorTargetDrops.value) return 0 + return Math.max(0, editorTargetDrops.value - editorEoDrops.value) +}) +const editorCocoFillMl = computed(() => Math.round(editorCocoActualDrops.value / DROPS_PER_ML)) +const editorSuggestedEo = computed(() => { + if (selectedVolume.value === 'single') { + const coco = editCocoRow.value ? (editCocoRow.value.drops || 10) : 10 + return Math.round(coco / dilutionRatio.value) + } + return Math.round((editorTargetDrops.value || 0) / (1 + dilutionRatio.value)) +}) +const editorSummaryText = computed(() => { + const eo = editorEoDrops.value + const coco = editorCocoActualDrops.value + const ratio = eo > 0 ? Math.round(coco / eo) : 0 + if (selectedVolume.value === 'single') { + return `该配方为单次用量,纯精油 ${eo} 滴,椰子油 ${coco} 滴,稀释比例 1:${ratio}` + } + const vol = selectedVolume.value === 'custom' ? (customVolumeValue.value || 0) : Number(selectedVolume.value) + return `该配方总容量 ${vol}ml,纯精油 ${eo} 滴,剩余用椰子油填满,稀释比例 1:${ratio}` +}) +const editorTotalCost = computed(() => { + let cost = editEoIngredients.value.filter(i => i.oil && i.drops > 0) + .reduce((s, i) => s + oilsStore.pricePerDrop(i.oil) * i.drops, 0) + cost += oilsStore.pricePerDrop('椰子油') * editorCocoActualDrops.value + return oilsStore.fmtPrice(cost) +}) + +function addEoRow() { + editIngredients.value.push({ oil: '', drops: 1 }) +} const editPriceInfo = computed(() => oilsStore.fmtCostWithRetail(editIngredients.value.filter(i => i.oil)) @@ -833,7 +781,10 @@ onMounted(() => { editName.value = r.name editNote.value = r.note || '' editTags.value = [...(r.tags || [])] - editIngredients.value = (r.ingredients || []).map(i => ({ oil: i.oil, drops: i.drops })) + const allIngs = (r.ingredients || []) + editIngredients.value = allIngs.filter(i => i.oil !== '椰子油').map(i => ({ oil: i.oil, drops: i.drops })) + const coco = allIngs.find(i => i.oil === '椰子油') + editCocoRow.value = coco ? { oil: '椰子油', drops: coco.drops } : { oil: '椰子油', drops: 10 } // Init translation defaults customRecipeNameEn.value = r.en_name || recipeNameEn(r.name) const enMap = {} @@ -843,20 +794,21 @@ onMounted(() => { customOilNameEn.value = enMap // Calculate current dilution ratio and volume from ingredients - const ings = r.ingredients || [] - const coco = ings.find(i => i.oil === '椰子油') - const eoDrops = ings.filter(i => i.oil && i.oil !== '椰子油').reduce((s, i) => s + (i.drops || 0), 0) - const cocoDrops = coco ? (coco.drops || 0) : 0 - const totalDrops = eoDrops + cocoDrops - if (eoDrops > 0 && cocoDrops > 0) { - dilutionRatio.value = Math.round(cocoDrops / eoDrops) + const cocoIng = allIngs.find(i => i.oil === '椰子油') + const eoTotal = allIngs.filter(i => i.oil && i.oil !== '椰子油').reduce((s, i) => s + (i.drops || 0), 0) + const cocoTotal = cocoIng ? (cocoIng.drops || 0) : 0 + const totalDrops = eoTotal + cocoTotal + if (eoTotal > 0 && cocoTotal > 0) { + dilutionRatio.value = Math.round(cocoTotal / eoTotal) } const ml = totalDrops / DROPS_PER_ML if (ml <= 1.5) selectedVolume.value = 'single' else if (Math.abs(ml - 5) < 1.5) selectedVolume.value = '5' else if (Math.abs(ml - 10) < 3) selectedVolume.value = '10' + else if (Math.abs(ml - 15) < 3) selectedVolume.value = '15' + else if (Math.abs(ml - 20) < 4) selectedVolume.value = '20' else if (Math.abs(ml - 30) < 8) selectedVolume.value = '30' - else { selectedVolume.value = 'custom'; customVolumeValue.value = Math.round(totalDrops); customVolumeUnit.value = 'drops' } + else { selectedVolume.value = 'custom'; customVolumeValue.value = Math.round(ml) } loadBrand() nextTick(() => generateCardImage()) @@ -1018,23 +970,28 @@ function previewFromEditor() { } async function saveRecipe() { - const ingredients = editIngredients.value.filter(i => i.oil && i.drops > 0) + const eoIngs = editIngredients.value.filter(i => i.oil && i.oil !== '椰子油' && i.drops > 0) if (!editName.value.trim()) { ui.showToast('请输入配方名称') return } - if (ingredients.length === 0) { + if (eoIngs.length === 0) { ui.showToast('请至少添加一种精油') return } + const allIngs = eoIngs.map(i => ({ oil_name: i.oil, drops: i.drops })) + if (editCocoRow.value && editorCocoActualDrops.value > 0) { + allIngs.push({ oil_name: '椰子油', drops: editorCocoActualDrops.value }) + } + try { const payload = { ...recipe.value, name: editName.value.trim(), note: editNote.value.trim(), tags: editTags.value, - ingredients: ingredients.map(i => ({ oil_name: i.oil, drops: i.drops })), + ingredients: allIngs, } await recipesStore.saveRecipe(payload) // Reload recipes so the data is fresh when re-opened @@ -1794,6 +1751,15 @@ async function saveRecipe() { color: var(--sage-dark, #5a7d5e); } +.coco-row { background: #f8faf8; } +.coco-label { font-weight: 600; color: #4a9d7e; font-size: 13px; } +.coco-fill { font-size: 12px; color: #4a9d7e; font-weight: 500; } +.recipe-summary { + padding: 10px 14px; background: #f0faf5; border-radius: 10px; border-left: 3px solid #7ec6a4; + font-size: 13px; color: #2e7d5a; margin-bottom: 12px; line-height: 1.6; +} +.ratio-hint { font-size: 12px; color: #4a9d7e; font-weight: 500; white-space: nowrap; } + /* Volume controls */ .volume-controls { display: flex;