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,