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

商业核算:
- 芳香调理技术作为体验项目,使用真实配方数据
- 新增消耗分析:每种精油可做次数、哪个最先消耗完、最多可做几次

个人库存:
- 芳香调理套装改为正确精油列表

商业认证:
- 简化为商户名+证明图片
- 通过后内容保持显示
- 用户管理页面根据用户实际认证状态显示(修复拒绝后又通过仍显示已拒绝)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-10 23:06:40 +00:00
parent 3dd75f34c0
commit c4005f229e
3 changed files with 73 additions and 16 deletions

View File

@@ -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;
}