import { computed } from 'vue' import { useOilsStore } from '../stores/oils' import { useRecipesStore } from '../stores/recipes' import { KITS } from '../config/kits' /** * 套装成本分摊与配方匹配 * * 分摊逻辑:按各精油原瓶价占比分摊套装总价 * 某精油套装内成本 = (该油原瓶价 / 套装内所有油原瓶价之和) × 套装总价 * 套装内每滴成本 = 套装内成本 / 该油滴数 */ export function useKitCost() { const oils = useOilsStore() const recipeStore = useRecipesStore() // Resolve kit oil name to system oil name (handles 牛至→西班牙牛至 etc.) function resolveOilName(kitOilName) { if (oils.oilsMeta[kitOilName]) return kitOilName // Try finding system oil that ends with kit name return oils.oilNames.find(n => n.endsWith(kitOilName) && n !== kitOilName) || kitOilName } // Calculate per-drop costs for a kit function calcKitPerDrop(kit) { const resolved = kit.oils.map(name => resolveOilName(name)) const bc = kit.bottleCount || {} // e.g. { '椰子油': 2.57 } // Sum of bottle prices for all oils in kit (accounting for multiple bottles) let totalBottlePrice = 0 const oilBottlePrices = {} for (const name of resolved) { const meta = oils.oilsMeta[name] const count = bc[name] || 1 const bp = meta ? meta.bottlePrice * count : 0 oilBottlePrices[name] = bp totalBottlePrice += bp } if (totalBottlePrice === 0) return {} // Proportional allocation — kit accessories treated as freebies, // so oil cost = min(kit price, sum of bottle prices) const effectivePrice = Math.min(kit.price, totalBottlePrice) const perDrop = {} for (const name of resolved) { const meta = oils.oilsMeta[name] const count = bc[name] || 1 const bp = oilBottlePrices[name] const kitCostForOil = (bp / totalBottlePrice) * effectivePrice const totalDrops = meta ? meta.dropCount * count : 1 perDrop[name] = totalDrops > 0 ? kitCostForOil / totalDrops : 0 } return perDrop } // Check if a recipe can be made with a kit function canMakeRecipe(kit, recipe) { const resolvedSet = new Set(kit.oils.map(name => resolveOilName(name))) return recipe.ingredients.every(ing => resolvedSet.has(ing.oil)) } // Calculate recipe cost using kit pricing function calcRecipeCostWithKit(kitPerDrop, recipe) { return recipe.ingredients.reduce((sum, ing) => { const ppd = kitPerDrop[ing.oil] || 0 return sum + ppd * ing.drops }, 0) } // Get all matching recipes for a kit function getKitRecipes(kit) { const perDrop = calcKitPerDrop(kit) return recipeStore.recipes .filter(r => canMakeRecipe(kit, r)) .map(r => ({ ...r, kitCost: calcRecipeCostWithKit(perDrop, r), originalCost: oils.calcCost(r.ingredients), })) .sort((a, b) => a.name.localeCompare(b.name, 'zh')) } // Build full analysis for all kits, sorted by recipe count ascending (fewest recipes first) const kitAnalysis = computed(() => { return KITS.map(kit => { const perDrop = calcKitPerDrop(kit) const recipes = getKitRecipes(kit) return { ...kit, perDrop, recipes, recipeCount: recipes.length, } }).sort((a, b) => a.recipeCount - b.recipeCount) }) // Cross-kit comparison: membership-tier style // Columns: kits ordered by recipe count (fewest→most, from kitAnalysis) // Rows: recipes available to most kits at top, exclusive recipes at bottom (staircase pattern) const crossComparison = computed(() => { const analysis = kitAnalysis.value // Kit order for staircase: index in sorted analysis (0 = smallest kit) const kitOrder = analysis.map(ka => ka.id) const allRecipeIds = new Set() for (const ka of analysis) { for (const r of ka.recipes) allRecipeIds.add(r._id) } const rows = [] for (const id of allRecipeIds) { let recipe = null const costs = {} let availCount = 0 // Track which kit columns have this recipe (by index in sorted order) let smallestKitIdx = kitOrder.length for (let i = 0; i < analysis.length; i++) { const ka = analysis[i] const found = ka.recipes.find(r => r._id === id) if (found) { if (!recipe) recipe = found costs[ka.id] = found.kitCost availCount++ if (i < smallestKitIdx) smallestKitIdx = i } else { costs[ka.id] = null } } rows.push({ id, name: recipe.name, tags: recipe.tags, volume: recipe.volume, ingredients: recipe.ingredients, originalCost: recipe.originalCost, costs, availCount, smallestKitIdx, }) } // Staircase sort: most available first, then by smallest kit that has it, then by name rows.sort((a, b) => { if (a.availCount !== b.availCount) return b.availCount - a.availCount if (a.smallestKitIdx !== b.smallestKitIdx) return a.smallestKitIdx - b.smallestKitIdx return a.name.localeCompare(b.name, 'zh') }) return rows }) return { KITS, resolveOilName, calcKitPerDrop, canMakeRecipe, calcRecipeCostWithKit, getKitRecipes, kitAnalysis, crossComparison, } }