|
|
|
|
@@ -65,7 +65,7 @@
|
|
|
|
|
<span v-if="!auth.isAdmin" class="contrib-tag">已贡献 {{ sharedCount.adopted }}/{{ sharedCount.total }} 条</span>
|
|
|
|
|
<span class="toggle-icon">{{ showMyRecipes ? '▾' : '▸' }}</span>
|
|
|
|
|
</h3>
|
|
|
|
|
<template v-if="showMyRecipes">
|
|
|
|
|
<template v-if="showMyRecipes || manageSearch">
|
|
|
|
|
<div class="recipe-list">
|
|
|
|
|
<div
|
|
|
|
|
v-for="d in myFilteredRecipes"
|
|
|
|
|
@@ -106,7 +106,7 @@
|
|
|
|
|
<span>🌿 公共配方库 ({{ publicRecipes.length }})</span>
|
|
|
|
|
<span class="toggle-icon">{{ showPublicRecipes ? '▾' : '▸' }}</span>
|
|
|
|
|
</h3>
|
|
|
|
|
<div v-if="showPublicRecipes" class="recipe-list">
|
|
|
|
|
<div v-if="showPublicRecipes || manageSearch" class="recipe-list">
|
|
|
|
|
<div
|
|
|
|
|
v-for="r in publicFilteredRecipes"
|
|
|
|
|
:key="r._id"
|
|
|
|
|
@@ -382,10 +382,10 @@ const newIngOil = ref('')
|
|
|
|
|
const newIngSearch = ref('')
|
|
|
|
|
const newIngDrops = ref(1)
|
|
|
|
|
const newIngDropdownOpen = ref(false)
|
|
|
|
|
const formVolume = ref('single')
|
|
|
|
|
const formVolume = ref('30')
|
|
|
|
|
const formCustomVolume = ref(100)
|
|
|
|
|
const formCustomUnit = ref('drops')
|
|
|
|
|
const formDilution = ref(3)
|
|
|
|
|
const formDilution = ref(6)
|
|
|
|
|
|
|
|
|
|
const formTotalCost = computed(() => {
|
|
|
|
|
const cost = formIngredients.value
|
|
|
|
|
@@ -529,12 +529,32 @@ async function executeBatchAction(action) {
|
|
|
|
|
clearSelection()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function calcDilutionFromIngs(ingredients) {
|
|
|
|
|
const ings = 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) {
|
|
|
|
|
formDilution.value = Math.round(cocoDrops / eoDrops)
|
|
|
|
|
}
|
|
|
|
|
// Guess volume
|
|
|
|
|
const DROPS_PER_ML = 18.6
|
|
|
|
|
const ml = totalDrops / DROPS_PER_ML
|
|
|
|
|
if (ml <= 1.5) formVolume.value = 'single'
|
|
|
|
|
else if (Math.abs(ml - 5) < 1.5) formVolume.value = '5'
|
|
|
|
|
else if (Math.abs(ml - 10) < 3) formVolume.value = '10'
|
|
|
|
|
else if (Math.abs(ml - 30) < 8) formVolume.value = '30'
|
|
|
|
|
else { formVolume.value = 'custom'; formCustomVolume.value = Math.round(totalDrops); formCustomUnit.value = 'drops' }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function editRecipe(recipe) {
|
|
|
|
|
editingRecipe.value = recipe
|
|
|
|
|
formName.value = recipe.name
|
|
|
|
|
formIngredients.value = recipe.ingredients.map(i => ({ ...i, _search: i.oil, _open: false }))
|
|
|
|
|
formNote.value = recipe.note || ''
|
|
|
|
|
formTags.value = [...(recipe.tags || [])]
|
|
|
|
|
calcDilutionFromIngs(recipe.ingredients)
|
|
|
|
|
showAddOverlay.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -555,6 +575,8 @@ function resetForm() {
|
|
|
|
|
newIngOil.value = ''
|
|
|
|
|
newIngSearch.value = ''
|
|
|
|
|
newIngDrops.value = 1
|
|
|
|
|
formVolume.value = '30'
|
|
|
|
|
formDilution.value = 6
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSmartPaste() {
|
|
|
|
|
@@ -665,7 +687,7 @@ function applyVolumeDilution() {
|
|
|
|
|
const currentEoTotal = eoIngs.reduce((s, i) => s + (i.drops || 0), 0)
|
|
|
|
|
if (currentEoTotal <= 0) return
|
|
|
|
|
const scale = targetEoDrops / currentEoTotal
|
|
|
|
|
eoIngs.forEach(i => { i.drops = Math.max(0.5, Math.round(i.drops * scale * 2) / 2) })
|
|
|
|
|
eoIngs.forEach(i => { i.drops = Math.max(1, Math.round(i.drops * scale)) })
|
|
|
|
|
const actualEo = eoIngs.reduce((s, i) => s + i.drops, 0)
|
|
|
|
|
setFormCoconut(targetTotalDrops - actualEo)
|
|
|
|
|
ui.showToast('已应用容量设置')
|
|
|
|
|
@@ -845,6 +867,7 @@ function editDiaryRecipe(diary) {
|
|
|
|
|
formIngredients.value = (diary.ingredients || []).map(i => ({ ...i, _search: i.oil, _open: false }))
|
|
|
|
|
formNote.value = diary.note || ''
|
|
|
|
|
formTags.value = [...(diary.tags || [])]
|
|
|
|
|
calcDilutionFromIngs(diary.ingredients)
|
|
|
|
|
showAddOverlay.value = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1391,8 +1414,8 @@ watch(() => recipeStore.recipes, () => {
|
|
|
|
|
.action-btn-primary { background: linear-gradient(135deg, #7ec6a4, #4a9d7e); color: #fff; border-color: transparent; }
|
|
|
|
|
.action-btn-primary:hover { opacity: 0.9; }
|
|
|
|
|
.action-btn-sm { padding: 5px 12px; font-size: 12px; }
|
|
|
|
|
.volume-controls { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; }
|
|
|
|
|
.volume-btn { padding: 6px 14px; border: 1.5px solid #d4cfc7; border-radius: 8px; background: #fff; font-size: 13px; cursor: pointer; font-family: inherit; color: #6b6375; }
|
|
|
|
|
.volume-controls { display: flex; gap: 6px; margin-bottom: 8px; }
|
|
|
|
|
.volume-btn { flex: 1; padding: 6px 0; border: 1.5px solid #d4cfc7; border-radius: 8px; background: #fff; font-size: 13px; cursor: pointer; font-family: inherit; color: #6b6375; text-align: center; }
|
|
|
|
|
.volume-btn.active { background: #e8f5e9; border-color: #7ec6a4; color: #2e7d5a; font-weight: 600; }
|
|
|
|
|
.volume-btn:hover { border-color: #7ec6a4; }
|
|
|
|
|
.custom-volume-row { display: flex; gap: 6px; align-items: center; margin-bottom: 6px; }
|
|
|
|
|
|