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 }} 滴
+
-
+
-
-
-
-
-
-
-
- {{ 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;