feat: 配方卡片加入上传个人二维码功能 #5

Merged
hera merged 23 commits from feature/qr-upload-hint into main 2026-04-08 22:09:30 +00:00
2 changed files with 45 additions and 6 deletions
Showing only changes of commit dee4b1649a - Show all commits

View File

@@ -36,6 +36,17 @@
>English</button>
</div>
<!-- Volume selector -->
<div class="volume-controls card-volume-controls">
<button
v-for="(drops, ml) in VOLUME_DROPS"
:key="ml"
class="volume-btn"
:class="{ active: selectedCardVolume === ml }"
@click="onCardVolumeChange(ml)"
>{{ ml === '单次' ? '单次' : ml + 'ml' }}</button>
</div>
<!-- QR / brand upload hint -->
<div v-if="showBrandHint" class="brand-upload-hint">
<span class="hint-icon"></span>
@@ -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);

View File

@@ -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,