import { defineStore } from 'pinia' import { ref, computed } from 'vue' import { api } from '../composables/useApi' export const DROPS_PER_ML = 18.6 export const VOLUME_DROPS = { '单次': null, '2.5': 46, '5': 93, '10': 186, '15': 280, '115': 2146, } export const useOilsStore = defineStore('oils', () => { const oils = ref({}) const oilsMeta = ref({}) const oilCards = ref({}) // Getters const oilNames = computed(() => Object.keys(oils.value).sort((a, b) => a.localeCompare(b, 'zh')) ) function pricePerDrop(name) { return oils.value[name] || 0 } function calcCost(ingredients) { return ingredients.reduce((sum, ing) => { return sum + pricePerDrop(ing.oil) * ing.drops }, 0) } function calcRetailCost(ingredients) { return ingredients.reduce((sum, ing) => { const meta = oilsMeta.value[ing.oil] if (meta && meta.retailPrice && meta.dropCount) { return sum + (meta.retailPrice / meta.dropCount) * ing.drops } return sum + pricePerDrop(ing.oil) * ing.drops }, 0) } function fmtPrice(n) { return '¥ ' + n.toFixed(2) } function fmtCostWithRetail(ingredients) { const cost = calcCost(ingredients) const retail = calcRetailCost(ingredients) const costStr = fmtPrice(cost) const anyRetail = ingredients.some(i => { const m = oilsMeta.value[i.oil] return m && m.retailPrice && m.dropCount }) if (anyRetail && retail > 0) { return { cost: costStr, retail: fmtPrice(retail), hasRetail: true } } return { cost: costStr, retail: null, hasRetail: false } } // Actions async function loadOils() { const data = await api.get('/api/oils') const newOils = {} const newMeta = {} for (const oil of data) { const ppd = oil.drop_count ? oil.bottle_price / oil.drop_count : 0 newOils[oil.name] = ppd newMeta[oil.name] = { bottlePrice: oil.bottle_price, dropCount: oil.drop_count, retailPrice: oil.retail_price ?? null, isActive: oil.is_active !== 0, enName: oil.en_name ?? null, unit: oil.unit || 'drop', } } oils.value = newOils oilsMeta.value = newMeta // Also fetch oil cards from DB try { const cards = await api.get('/api/oil-cards') const newCards = {} for (const card of cards) { newCards[card.name] = { emoji: card.emoji || '', en: card.en || '', effects: card.effects || '', usage: card.usage || '', method: card.method || '', caution: card.caution || '', } } oilCards.value = newCards } catch { // oil_cards table may not exist yet on older backends } } async function saveOil(name, bottlePrice, dropCount, retailPrice, enName = null, unit = null, card = null) { const payload = { name, bottle_price: bottlePrice, drop_count: dropCount, retail_price: retailPrice, en_name: enName, } if (unit) payload.unit = unit if (card) { payload.card_emoji = card.emoji ?? null payload.card_effects = card.effects ?? null payload.card_usage = card.usage ?? null payload.card_method = card.method ?? null payload.card_caution = card.caution ?? null } await api.post('/api/oils', payload) await loadOils() } async function deleteOil(name) { await api.delete(`/api/oils/${encodeURIComponent(name)}`) delete oils.value[name] delete oilsMeta.value[name] } const UNIT_LABELS = { drop: { zh: '滴', en: 'drop', enPlural: 'drops' }, ml: { zh: 'ml', en: 'ml', enPlural: 'ml' }, g: { zh: 'g', en: 'g', enPlural: 'g' }, capsule: { zh: '颗', en: 'capsule', enPlural: 'capsules' }, } function getUnit(name) { const meta = oilsMeta.value[name] return (meta && meta.unit) || 'drop' } function isDropUnit(name) { return getUnit(name) === 'drop' } function isMlUnit(name) { return getUnit(name) === 'ml' } function isPortionUnit(name) { return !isDropUnit(name) } function unitLabel(name, lang = 'zh') { const u = UNIT_LABELS[getUnit(name)] || UNIT_LABELS.drop return lang === 'en' ? u.en : u.zh } function unitLabelPlural(name, count, lang = 'zh') { const u = UNIT_LABELS[getUnit(name)] || UNIT_LABELS.drop if (lang === 'en') return count === 1 ? u.en : u.enPlural return u.zh } return { oils, oilsMeta, oilCards, oilNames, pricePerDrop, calcCost, calcRetailCost, fmtPrice, fmtCostWithRetail, loadOils, saveOil, deleteOil, getUnit, isDropUnit, isMlUnit, isPortionUnit, unitLabel, unitLabelPlural, } })