feat: 通用单位系统 — drop/ml/g/capsule,去掉硬编码
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 5s
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 13s
Test / e2e-test (push) Failing after 3m5s

unit字段支持4种值,所有显示自动适配:
- drop: 精油(滴/drop)
- ml: 液体产品(ml)
- g: 膏霜产品(g)
- capsule: 胶囊(颗/capsule)
新增产品选单位即可,无需改代码。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-13 16:10:36 +00:00
parent 7bcb1d1a5b
commit eeb9b0aa88
2 changed files with 48 additions and 27 deletions

View File

@@ -94,25 +94,39 @@ export const useOilsStore = defineStore('oils', () => {
delete oilsMeta.value[name]
}
function isMlUnit(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 === 'ml'
return (meta && meta.unit) || 'drop'
}
function isDropUnit(name) {
return getUnit(name) === 'drop'
}
function isMlUnit(name) {
return getUnit(name) === 'ml'
}
function isPortionUnit(name) {
return isMlUnit(name)
return !isDropUnit(name)
}
function unitLabel(name, lang = 'zh') {
if (isMlUnit(name)) return 'ml'
if (name === '植物空胶囊') return lang === 'en' ? 'capsule' : '颗'
return lang === 'en' ? 'drop' : '滴'
const u = UNIT_LABELS[getUnit(name)] || UNIT_LABELS.drop
return lang === 'en' ? u.en : u.zh
}
function unitLabelPlural(name, count, lang = 'zh') {
if (isMlUnit(name)) return 'ml'
if (name === '植物空胶囊') return lang === 'en' ? (count === 1 ? 'capsule' : 'capsules') : '颗'
return lang === 'en' ? (count === 1 ? 'drop' : 'drops') : '滴'
const u = UNIT_LABELS[getUnit(name)] || UNIT_LABELS.drop
if (lang === 'en') return count === 1 ? u.en : u.enPlural
return u.zh
}
return {
@@ -127,6 +141,8 @@ export const useOilsStore = defineStore('oils', () => {
loadOils,
saveOil,
deleteOil,
getUnit,
isDropUnit,
isMlUnit,
isPortionUnit,
unitLabel,

View File

@@ -227,7 +227,7 @@
</div>
<div class="detail-body">
<!-- 精油非ml产品 -->
<template v-if="!oils.isMlUnit(selectedOilName)">
<template v-if="oils.isDropUnit(selectedOilName)">
<div class="detail-row">
<span class="detail-label">总容量</span>
<span class="detail-value">{{ volumeWithDrops(selectedOilName) }}</span>
@@ -253,19 +253,27 @@
<template v-else>
<div class="detail-row">
<span class="detail-label">总容量</span>
<span class="detail-value">{{ getMeta(selectedOilName)?.dropCount || '--' }}ml</span>
<span class="detail-value">{{ getMeta(selectedOilName)?.dropCount || '--' }}{{ oils.unitLabel(selectedOilName) }}</span>
</div>
<div class="detail-row">
<span class="detail-label">会员价</span>
<span class="detail-value">{{ getMeta(selectedOilName)?.bottlePrice != null ? ('¥ ' + getMeta(selectedOilName).bottlePrice.toFixed(2)) : '--' }}</span>
</div>
<div class="detail-row">
<span class="detail-label">ml价格</span>
<span class="detail-label">{{ oils.unitLabel(selectedOilName) }}价格</span>
<span class="detail-value">{{ oils.pricePerDrop(selectedOilName) ? ('¥ ' + oils.pricePerDrop(selectedOilName).toFixed(2)) : '--' }}</span>
</div>
<div class="detail-row" v-if="getMeta(selectedOilName)?.retailPrice">
<span class="detail-label">零售价</span>
<span class="detail-value">¥ {{ getMeta(selectedOilName).retailPrice.toFixed(2) }}</span>
</div>
<div class="detail-row" v-if="getMeta(selectedOilName)?.retailPrice && getMeta(selectedOilName)?.dropCount">
<span class="detail-label">{{ oils.unitLabel(selectedOilName) }}价格</span>
<span class="detail-value">¥ {{ (getMeta(selectedOilName).retailPrice / getMeta(selectedOilName).dropCount).toFixed(2) }}</span>
</div>
</template>
<h4 style="margin:16px 0 8px">含此{{ oils.isMlUnit(selectedOilName) ? '产品' : '精油' }}的配方</h4>
<h4 style="margin:16px 0 8px">含此{{ oils.isDropUnit(selectedOilName) ? '精油' : '产品' }}的配方</h4>
<div v-if="recipesWithOil.length" class="detail-recipes">
<div v-for="r in recipesWithOil" :key="r._id" class="detail-recipe-item">
<span class="dr-name">{{ r.name }}</span>
@@ -490,27 +498,24 @@ for (const [ml, drops] of Object.entries(VOLUME_OPTIONS)) {
DROPS_TO_VOLUME[drops] = ml + 'ml'
}
function volumeWithDrops(name) {
const meta = getMeta(name)
if (!meta || !meta.dropCount) return '--'
if (name === '植物空胶囊') return meta.dropCount + '颗'
const ml = DROPS_TO_VOLUME[meta.dropCount]
if (ml) return `${ml}/${meta.dropCount}`
return meta.dropCount + '滴'
}
function oilPriceUnit(name) {
if (oils.isMlUnit(name)) return 'ml'
if (name === '植物空胶囊') return '颗'
return '滴'
return oils.unitLabel(name)
}
function volumeLabel(dropCount, name) {
if (dropCount === 160) return '160颗'
if (oils.isMlUnit(name)) return dropCount + 'ml'
if (!oils.isDropUnit(name)) return dropCount + oils.unitLabel(name)
return DROPS_TO_VOLUME[dropCount] || (dropCount + '滴')
}
function volumeWithDrops(name) {
const meta = getMeta(name)
if (!meta || !meta.dropCount) return '--'
if (!oils.isDropUnit(name)) return meta.dropCount + oils.unitLabel(name)
const ml = DROPS_TO_VOLUME[meta.dropCount]
if (ml) return `${ml}/${meta.dropCount}`
return meta.dropCount + '滴'
}
function chipStyle(name) {
const hasCard = !!getOilCard(name)
if (hasCard) return 'cursor:pointer;border-left:3px solid var(--sage);background:linear-gradient(90deg,var(--sage-mist),white)'