feat: 消耗分析+认证修复+套装更新+认证简化
All checks were successful
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 6s
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 16s
Test / e2e-test (push) Successful in 49s
All checks were successful
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Successful in 6s
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Successful in 5s
PR Preview / deploy-preview (pull_request) Successful in 16s
Test / e2e-test (push) Successful in 49s
商业核算: - 芳香调理技术作为体验项目,使用真实配方数据 - 新增消耗分析:每种精油可做次数、哪个最先消耗完、最多可做几次 个人库存: - 芳香调理套装改为正确精油列表 商业认证: - 简化为商户名+证明图片 - 通过后内容保持显示 - 用户管理页面根据用户实际认证状态显示(修复拒绝后又通过仍显示已拒绝) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -118,7 +118,7 @@ const KITS = {
|
||||
family: ['乳香', '茶树', '薰衣草', '柠檬', '椒样薄荷', '保卫', '牛至', '乐活', '顺畅呼吸', '舒缓'],
|
||||
home3988: ['乳香', '野橘', '柠檬', '薰衣草', '椒样薄荷', '冬青', '茶树', '生姜', '柠檬草', '西班牙牛至',
|
||||
'西洋蓍草石榴籽', '乐活', '保卫', '新瑞活力', '舒缓', '安定情绪', '安宁神气', '顺畅呼吸', '柑橘清新', '芳香调理', '元气焕能'],
|
||||
aroma: ['芳香调理'],
|
||||
aroma: ['薰衣草', '舒缓', '安定情绪', '芳香调理', '野橘', '椒样薄荷', '保卫', '茶树'],
|
||||
full: ['侧柏', '乳香', '雪松', '芫荽', '丝柏', '圆柚', '红橘', '冬青', '没药', '扁柏', '檀香', '姜黄', '玫瑰',
|
||||
'绿薄荷', '薰衣草', '永久花', '香蜂草', '迷迭香', '麦卢卡', '天竺葵', '蓝艾菊', '小茴香',
|
||||
'古巴香脂', '依兰依兰', '丁香花蕾', '柠檬尤加利', '藿香', '西班牙牛至尾草',
|
||||
|
||||
@@ -13,14 +13,14 @@
|
||||
</div>
|
||||
|
||||
<div v-if="!selectedProject" class="project-list">
|
||||
<!-- Demo project (always visible) -->
|
||||
<!-- Demo: use real 芳香调理技术 recipe -->
|
||||
<div class="project-card demo-card" @click="openDemo">
|
||||
<div class="proj-header">
|
||||
<span class="proj-name">芳香调理(示意)</span>
|
||||
<span class="proj-name">芳香调理技术</span>
|
||||
<span class="proj-badge">体验</span>
|
||||
</div>
|
||||
<div class="proj-summary">
|
||||
<span>点击查看示意项目</span>
|
||||
<span>点击体验成本利润分析</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Real projects -->
|
||||
@@ -181,6 +181,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Consumption Analysis -->
|
||||
<div v-if="consumptionData.length" class="consumption-section">
|
||||
<h4>🧪 消耗分析</h4>
|
||||
<table class="consumption-table">
|
||||
<thead>
|
||||
<tr><th>精油</th><th>单次用量</th><th>瓶装容量</th><th>可做次数</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="c in consumptionData" :key="c.oil" :class="{ 'limit-oil': c.isLimit }">
|
||||
<td>{{ c.oil }}</td>
|
||||
<td>{{ c.drops }}滴</td>
|
||||
<td>{{ c.bottleDrops }}滴</td>
|
||||
<td>{{ c.sessions }}次</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="consumption-summary">
|
||||
<span>⚠️ <strong>{{ limitingOil }}</strong> 最先消耗完,最多可做 <strong>{{ maxSessions }}</strong> 次</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="notes-section">
|
||||
<h4>📝 备注</h4>
|
||||
@@ -268,24 +289,21 @@ function handleCreateProject() {
|
||||
createProject()
|
||||
}
|
||||
|
||||
// Demo project data (local only, per user)
|
||||
const demoProject = ref(null)
|
||||
function openDemo() {
|
||||
demoProject.value = {
|
||||
// Find the real 芳香调理技术 recipe
|
||||
const recipe = recipeStore.recipes.find(r => r.name.includes('芳香调理技术') || r.name === '芳香调理')
|
||||
const ings = recipe ? recipe.ingredients.map(i => ({ ...i })) : [{ oil: '芳香调理', drops: 12 }, { oil: '椰子油', drops: 186 }]
|
||||
selectedProject.value = {
|
||||
_demo: true,
|
||||
name: '芳香调理(示意)',
|
||||
ingredients: [
|
||||
{ oil: '芳香调理', drops: 12 },
|
||||
{ oil: '椰子油', drops: 186 },
|
||||
],
|
||||
name: recipe ? recipe.name : '芳香调理技术',
|
||||
ingredients: ings,
|
||||
packaging_cost: 5,
|
||||
labor_cost: 30,
|
||||
other_cost: 10,
|
||||
selling_price: 198,
|
||||
quantity: 1,
|
||||
notes: '这是一个示意项目,您可以修改数字体验功能',
|
||||
notes: '体验项目:修改数字不会影响其他用户',
|
||||
}
|
||||
selectedProject.value = demoProject.value
|
||||
}
|
||||
|
||||
async function createProject() {
|
||||
@@ -416,6 +434,31 @@ const batchRevenue = computed(() => {
|
||||
return (selectedProject.value?.selling_price || 0) * (selectedProject.value?.quantity || 1)
|
||||
})
|
||||
|
||||
const consumptionData = computed(() => {
|
||||
if (!selectedProject.value) return []
|
||||
const ings = (selectedProject.value.ingredients || []).filter(i => i.oil && i.oil !== '椰子油' && i.drops > 0)
|
||||
return ings.map(i => {
|
||||
const meta = oils.oilsMeta[i.oil]
|
||||
const bottleDrops = meta ? meta.dropCount : 0
|
||||
const sessions = bottleDrops > 0 && i.drops > 0 ? Math.floor(bottleDrops / i.drops) : 0
|
||||
return { oil: i.oil, drops: i.drops, bottleDrops, sessions, isLimit: false }
|
||||
})
|
||||
})
|
||||
|
||||
const limitingOil = computed(() => {
|
||||
const data = consumptionData.value.filter(c => c.sessions > 0)
|
||||
if (!data.length) return ''
|
||||
const min = data.reduce((a, b) => a.sessions < b.sessions ? a : b)
|
||||
min.isLimit = true
|
||||
return min.oil
|
||||
})
|
||||
|
||||
const maxSessions = computed(() => {
|
||||
const data = consumptionData.value.filter(c => c.sessions > 0)
|
||||
if (!data.length) return 0
|
||||
return Math.min(...data.map(c => c.sessions))
|
||||
})
|
||||
|
||||
function formatDate(d) {
|
||||
if (!d) return ''
|
||||
return new Date(d).toLocaleDateString('zh-CN')
|
||||
@@ -433,6 +476,13 @@ function formatDate(d) {
|
||||
.commercial-icon { font-size: 48px; margin-bottom: 8px; }
|
||||
.commercial-desc { font-size: 14px; color: var(--text-light, #999); }
|
||||
.demo-card { border-style: dashed !important; opacity: 0.85; }
|
||||
.consumption-section { margin-bottom: 20px; padding: 14px; background: #f8f7f5; border-radius: 12px; border: 1.5px solid #e5e4e7; }
|
||||
.consumption-section h4 { margin: 0 0 12px; font-size: 14px; color: #3e3a44; }
|
||||
.consumption-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 10px; }
|
||||
.consumption-table th { text-align: left; padding: 6px 8px; color: #999; font-size: 12px; border-bottom: 1px solid #eee; }
|
||||
.consumption-table td { padding: 6px 8px; border-bottom: 1px solid #f5f5f5; }
|
||||
.consumption-table .limit-oil { background: #fff3e0; font-weight: 600; }
|
||||
.consumption-summary { font-size: 13px; color: #e65100; padding: 8px; background: #fff8e1; border-radius: 8px; }
|
||||
.proj-badge {
|
||||
font-size: 10px; background: #fff3e0; color: #e65100; padding: 2px 8px; border-radius: 8px;
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
<div class="review-info">
|
||||
<span class="review-name">{{ group.latest.display_name || group.latest.username }}</span>
|
||||
<span class="review-reason">商户名:{{ group.latest.business_name }}</span>
|
||||
<span class="biz-status-tag" :class="'biz-' + group.latest.status">{{ { pending: '待审核', approved: '已通过', rejected: '已拒绝' }[group.latest.status] }}</span>
|
||||
<span class="biz-status-tag" :class="'biz-' + group.effectiveStatus">{{ { pending: '待审核', approved: '已通过', rejected: '已拒绝' }[group.effectiveStatus] }}</span>
|
||||
</div>
|
||||
<div class="review-actions">
|
||||
<template v-if="group.latest.status === 'pending'">
|
||||
<template v-if="group.effectiveStatus === 'pending'">
|
||||
<button class="btn-sm btn-approve" @click="approveBusiness(group.latest)">通过</button>
|
||||
<button class="btn-sm btn-reject" @click="rejectBusiness(group.latest)">拒绝</button>
|
||||
</template>
|
||||
@@ -141,6 +141,13 @@ const groupedBizApps = computed(() => {
|
||||
return Object.values(map).map(g => {
|
||||
g.history.sort((a, b) => b.id - a.id)
|
||||
g.latest = g.history[0]
|
||||
// Check if user is already verified (from users list)
|
||||
const user = users.value.find(u => (u._id || u.id) === g.user_id)
|
||||
if (user && user.business_verified) {
|
||||
g.effectiveStatus = 'approved'
|
||||
} else {
|
||||
g.effectiveStatus = g.latest.status
|
||||
}
|
||||
return reactive(g)
|
||||
}).filter(g => g.latest)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user