fix: 精油编辑统一保存到 DB + 导出 Excel 增加功效列
Some checks failed
Test / unit-test (push) Successful in 7s
Test / build-check (push) Successful in 4s
PR Preview / teardown-preview (pull_request) Has been skipped
Test / e2e-test (push) Failing after 1m12s
PR Preview / test (pull_request) Successful in 7s
PR Preview / deploy-preview (pull_request) Successful in 19s
Some checks failed
Test / unit-test (push) Successful in 7s
Test / build-check (push) Successful in 4s
PR Preview / teardown-preview (pull_request) Has been skipped
Test / e2e-test (push) Failing after 1m12s
PR Preview / test (pull_request) Successful in 7s
PR Preview / deploy-preview (pull_request) Successful in 19s
- 新增 oil_cards 表,持久化知识卡片(功效/用法/方法/注意/emoji) - POST /api/oils 扩展接受 card_* 字段,在同一事务里 upsert oil_cards - GET /api/oil-cards 返回全部卡片 - 前端 getOilCard 优先查 DB,再 fallback 静态表 - saveEditOil 统一走 saveOil,不再分两套保存 - 精油价目 Excel 导出增加「功效」列 - 首次部署自动从静态 OIL_CARDS 播种到 oil_cards 表 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
// Oil knowledge cards - usage guides for common essential oils
|
||||
// Ported from original vanilla JS implementation
|
||||
import { useOilsStore } from '../stores/oils'
|
||||
|
||||
export const OIL_CARDS = {
|
||||
'野橘': { emoji: '🍊', en: 'Wild Orange', effects: '安抚镇静、驱散负能量、紧张情绪和压力\n抗氧化、消炎、抗病毒、增强免疫\n提振食欲,刺激胆汁分泌,促进消化\n促进循环', usage: '日常香薰,提振情绪、舒压,营造愉悦氛围\n饮水加入 1至2 滴,护肝抗病毒\n泡澡时滴 3至4 滴,消除疲劳,放松身心\n扫地、拖地时加入 3至5 滴,清新空气\n涂抹腹部促进消化\n涂抹肝区帮助肝脏排毒\n口腔溃疡时加入水中漱口', method: '🔹香薰 | 🔸内用 | 🔺涂抹', caution: '轻微光敏,白天涂抹注意防晒' },
|
||||
@@ -33,6 +34,17 @@ export const OIL_CARD_ALIAS = {
|
||||
}
|
||||
|
||||
export function getOilCard(name) {
|
||||
// Check DB-persisted cards first (via store)
|
||||
try {
|
||||
const store = useOilsStore()
|
||||
if (store.oilCards[name]) return store.oilCards[name]
|
||||
// Check alias in store too
|
||||
const aliased = OIL_CARD_ALIAS[name]
|
||||
if (aliased && store.oilCards[aliased]) return store.oilCards[aliased]
|
||||
} catch {
|
||||
// Store may not be initialized yet, fall through to static data
|
||||
}
|
||||
// Fall back to static OIL_CARDS
|
||||
if (OIL_CARDS[name]) return OIL_CARDS[name]
|
||||
if (OIL_CARD_ALIAS[name] && OIL_CARDS[OIL_CARD_ALIAS[name]]) return OIL_CARDS[OIL_CARD_ALIAS[name]]
|
||||
const base = name.replace(/呵护$/, '')
|
||||
|
||||
@@ -16,6 +16,7 @@ export const VOLUME_DROPS = {
|
||||
export const useOilsStore = defineStore('oils', () => {
|
||||
const oils = ref({})
|
||||
const oilsMeta = ref({})
|
||||
const oilCards = ref({})
|
||||
|
||||
// Getters
|
||||
const oilNames = computed(() =>
|
||||
@@ -79,9 +80,28 @@ export const useOilsStore = defineStore('oils', () => {
|
||||
}
|
||||
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) {
|
||||
async function saveOil(name, bottlePrice, dropCount, retailPrice, enName = null, unit = null, card = null) {
|
||||
const payload = {
|
||||
name,
|
||||
bottle_price: bottlePrice,
|
||||
@@ -90,6 +110,13 @@ export const useOilsStore = defineStore('oils', () => {
|
||||
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()
|
||||
}
|
||||
@@ -138,6 +165,7 @@ export const useOilsStore = defineStore('oils', () => {
|
||||
return {
|
||||
oils,
|
||||
oilsMeta,
|
||||
oilCards,
|
||||
oilNames,
|
||||
pricePerDrop,
|
||||
calcCost,
|
||||
|
||||
@@ -442,7 +442,7 @@ import { useAuthStore } from '../stores/auth'
|
||||
import { useUiStore } from '../stores/ui'
|
||||
import { useRecipesStore } from '../stores/recipes'
|
||||
import { oilEn } from '../composables/useOilTranslation'
|
||||
import { getOilCard, setOilCard } from '../composables/useOilCards'
|
||||
import { getOilCard } from '../composables/useOilCards'
|
||||
import { showConfirm } from '../composables/useDialog'
|
||||
import { api } from '../composables/useApi'
|
||||
import { parseOilProductPaste } from '../composables/useOilProductPaste'
|
||||
@@ -816,26 +816,24 @@ async function saveEditOil() {
|
||||
}
|
||||
const finalDropCount = editUnit.value !== 'drop' ? editProductAmount.value : dropCount
|
||||
const finalUnit = editUnit.value !== 'drop' ? editProductUnit.value : null
|
||||
// Build card payload if any card content provided
|
||||
const hasCard = editCardEffects.value.trim() || editCardUsage.value.trim()
|
||||
const cardPayload = hasCard ? {
|
||||
emoji: editCardEmoji.value || '🌿',
|
||||
effects: editCardEffects.value.trim(),
|
||||
usage: editCardUsage.value.trim(),
|
||||
method: editCardMethod.value.trim(),
|
||||
caution: editCardCaution.value.trim(),
|
||||
} : null
|
||||
await oils.saveOil(
|
||||
newName || oldName,
|
||||
editBottlePrice.value,
|
||||
finalDropCount,
|
||||
editRetailPrice.value,
|
||||
editOilEnName.value.trim() || null,
|
||||
finalUnit
|
||||
finalUnit,
|
||||
cardPayload
|
||||
)
|
||||
// Save knowledge card if any content provided
|
||||
const finalName = newName || oldName
|
||||
if (editCardEffects.value.trim() || editCardUsage.value.trim()) {
|
||||
setOilCard(finalName, {
|
||||
emoji: editCardEmoji.value || '🌿',
|
||||
en: editOilEnName.value.trim() || '',
|
||||
effects: editCardEffects.value.trim(),
|
||||
usage: editCardUsage.value.trim(),
|
||||
method: editCardMethod.value.trim(),
|
||||
caution: editCardCaution.value.trim(),
|
||||
})
|
||||
}
|
||||
cardVersion.value++ // trigger re-render for card badges
|
||||
ui.showToast('已更新')
|
||||
editingOilName.value = null
|
||||
@@ -906,6 +904,7 @@ async function exportExcel() {
|
||||
const vol = volumeLabel(meta.dropCount, name)
|
||||
const unit = oilPriceUnit(name)
|
||||
const ppdNum = oils.pricePerDrop(name)
|
||||
const card = getOilCard(name)
|
||||
rows.push({
|
||||
'精油': name,
|
||||
'英文名': en,
|
||||
@@ -913,12 +912,13 @@ async function exportExcel() {
|
||||
'零售价': meta.retailPrice != null ? Number(meta.retailPrice.toFixed(2)) : '',
|
||||
'容量': vol,
|
||||
'单价': ppdNum ? `¥${ppdNum.toFixed(2)}/${unit}` : '',
|
||||
'功效': card?.effects || '',
|
||||
'状态': meta.isActive === false ? '下架' : '在售',
|
||||
})
|
||||
}
|
||||
|
||||
const ws = XLSX.utils.json_to_sheet(rows)
|
||||
ws['!cols'] = [{ wch: 16 }, { wch: 28 }, { wch: 10 }, { wch: 10 }, { wch: 12 }, { wch: 16 }, { wch: 8 }]
|
||||
ws['!cols'] = [{ wch: 16 }, { wch: 28 }, { wch: 10 }, { wch: 10 }, { wch: 12 }, { wch: 16 }, { wch: 40 }, { wch: 8 }]
|
||||
const wb = XLSX.utils.book_new()
|
||||
XLSX.utils.book_append_sheet(wb, ws, '精油价目表')
|
||||
XLSX.writeFile(wb, `精油价目表${dateStr}.xlsx`)
|
||||
|
||||
Reference in New Issue
Block a user