diff --git a/frontend/src/components/RecipeDetailOverlay.vue b/frontend/src/components/RecipeDetailOverlay.vue index 6d9d651..b9229ef 100644 --- a/frontend/src/components/RecipeDetailOverlay.vue +++ b/frontend/src/components/RecipeDetailOverlay.vue @@ -36,6 +36,17 @@ >English + +
+ +
+
@@ -382,6 +393,7 @@ const viewMode = ref('card') const cardRef = ref(null) const cardImageUrl = ref(null) const cardLang = ref('zh') +const selectedCardVolume = ref('单次') const showTranslationEditor = ref(false) const customRecipeNameEn = ref('') const customOilNameEn = ref({}) @@ -400,14 +412,30 @@ const canEditThisRecipe = computed(() => { const isFav = computed(() => recipesStore.isFavorite(recipe.value)) -// Card ingredients: exclude coconut oil -const cardIngredients = computed(() => - recipe.value.ingredients.filter(ing => ing.oil !== '椰子油') +// Scale ingredients proportionally to target volume; '单次' = no scaling +function scaleIngredients(ingredients, volume) { + const targetDrops = VOLUME_DROPS[volume] + if (!targetDrops) return ingredients // 单次:不缩放 + const totalDrops = ingredients.reduce((sum, ing) => sum + (ing.drops || 0), 0) + if (totalDrops === 0) return ingredients + return ingredients.map(ing => ({ + ...ing, + drops: Math.round(ing.drops * targetDrops / totalDrops), + })) +} + +// Card ingredients: scaled to selected volume, coconut oil excluded from display +const scaledCardIngredients = computed(() => + scaleIngredients(recipe.value.ingredients, selectedCardVolume.value) ) -// Coconut oil drops +const cardIngredients = computed(() => + scaledCardIngredients.value.filter(ing => ing.oil !== '椰子油') +) + +// Coconut oil drops (from scaled set) const coconutDrops = computed(() => { - const coco = recipe.value.ingredients.find(ing => ing.oil === '椰子油') + const coco = scaledCardIngredients.value.find(ing => ing.oil === '椰子油') return coco ? coco.drops : 0 }) @@ -433,7 +461,7 @@ const dilutionDesc = computed(() => { : `该配方适用于单次用量(共${totalDrops}滴),其中纯精油 ${totalEoDrops.value} 滴,椰子油 ${coconutDrops.value} 滴,稀释比例为 1:${ratio}` }) -const priceInfo = computed(() => oilsStore.fmtCostWithRetail(recipe.value.ingredients)) +const priceInfo = computed(() => oilsStore.fmtCostWithRetail(scaledCardIngredients.value)) // Today string const todayStr = computed(() => { @@ -512,6 +540,12 @@ function switchLang(lang) { nextTick(() => generateCardImage()) } +function onCardVolumeChange(ml) { + selectedCardVolume.value = ml + cardImageUrl.value = null + nextTick(() => generateCardImage()) +} + async function saveImage() { if (!cardImageUrl.value) { await generateCardImage() @@ -1469,6 +1503,10 @@ async function saveRecipe() { margin-bottom: 10px; } +.card-volume-controls { + margin: 8px 0 12px; +} + .volume-btn { padding: 7px 16px; border: 1.5px solid var(--border, #e0d4c0); diff --git a/frontend/src/stores/oils.js b/frontend/src/stores/oils.js index 2b2f647..f890b0e 100644 --- a/frontend/src/stores/oils.js +++ b/frontend/src/stores/oils.js @@ -5,6 +5,7 @@ import { api } from '../composables/useApi' export const DROPS_PER_ML = 18.6 export const VOLUME_DROPS = { + '单次': null, '2.5': 46, '5': 93, '10': 186,