feat: 商业认证+核算页面重写,管理入口移到用户菜单
All checks were successful
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 4s
PR Preview / deploy-preview (pull_request) Successful in 15s
Test / e2e-test (push) Successful in 55s
All checks were successful
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 4s
PR Preview / deploy-preview (pull_request) Successful in 15s
Test / e2e-test (push) Successful in 55s
商业认证: - 重写申请表单:认证类型、企业名称、联系电话、业务描述 - 状态栏样式:左侧彩色条(绿/橙/红) - 用户管理页:同一用户只显示一条,可展开历史查看拒绝原因 - 后端 API 补充 reject_reason 字段 商业核算: - 成分表改为标准表格(精油/用量/单价/小计) - 总成本显示栏(绿色背景大字) - 定价字段放在成本下方 管理入口: - 操作日志/Bug/用户管理从主 tab 栏移到管理员用户菜单 - 添加配方按钮对所有用户可见 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,17 +22,33 @@
|
||||
</div>
|
||||
|
||||
<!-- Business Application Approval -->
|
||||
<div v-if="businessApps.length > 0" class="review-section">
|
||||
<div v-if="groupedBizApps.length > 0" class="review-section">
|
||||
<h4 class="section-title">💼 商业认证申请</h4>
|
||||
<div class="review-list">
|
||||
<div v-for="app in businessApps" :key="app._id || app.id" class="review-item">
|
||||
<div class="review-info">
|
||||
<span class="review-name">{{ app.display_name || app.username }}</span>
|
||||
<span class="review-reason">商户名:{{ app.business_name }}</span>
|
||||
<div v-for="group in groupedBizApps" :key="group.user_id" class="biz-app-group">
|
||||
<div class="review-item">
|
||||
<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>
|
||||
</div>
|
||||
<div class="review-actions">
|
||||
<template v-if="group.latest.status === 'pending'">
|
||||
<button class="btn-sm btn-approve" @click="approveBusiness(group.latest)">通过</button>
|
||||
<button class="btn-sm btn-reject" @click="rejectBusiness(group.latest)">拒绝</button>
|
||||
</template>
|
||||
<button v-if="group.history.length > 1" class="btn-sm btn-outline" @click="group.expanded = !group.expanded">
|
||||
{{ group.expanded ? '收起' : `历史 (${group.history.length})` }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="review-actions">
|
||||
<button class="btn-sm btn-approve" @click="approveBusiness(app)">通过</button>
|
||||
<button class="btn-sm btn-reject" @click="rejectBusiness(app)">拒绝</button>
|
||||
<div v-if="group.expanded" class="biz-history">
|
||||
<div v-for="app in group.history" :key="app.id" class="biz-history-item">
|
||||
<span class="biz-status-tag small" :class="'biz-' + app.status">{{ { pending: '待审核', approved: '已通过', rejected: '已拒绝' }[app.status] }}</span>
|
||||
<span>{{ app.business_name }}</span>
|
||||
<span v-if="app.reject_reason" class="biz-reject-reason">拒绝原因:{{ app.reject_reason }}</span>
|
||||
<span class="biz-time">{{ formatDate(app.created_at) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -111,6 +127,27 @@ const searchQuery = ref('')
|
||||
const filterRole = ref('')
|
||||
const translations = ref([])
|
||||
const businessApps = ref([])
|
||||
import { reactive } from 'vue'
|
||||
|
||||
const groupedBizApps = computed(() => {
|
||||
const map = {}
|
||||
for (const app of businessApps.value) {
|
||||
const uid = app.user_id
|
||||
if (!map[uid]) map[uid] = { user_id: uid, history: [], latest: null, expanded: false }
|
||||
map[uid].history.push(app)
|
||||
}
|
||||
return Object.values(map).map(g => {
|
||||
g.history.sort((a, b) => b.id - a.id)
|
||||
g.latest = g.history[0]
|
||||
return reactive(g)
|
||||
}).filter(g => g.latest)
|
||||
})
|
||||
|
||||
function formatDate(d) {
|
||||
if (!d) return ''
|
||||
return new Date(d + 'Z').toLocaleString('zh-CN', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })
|
||||
}
|
||||
|
||||
const roles = [
|
||||
{ value: 'admin', label: '管理员' },
|
||||
{ value: 'senior_editor', label: '高级编辑' },
|
||||
@@ -138,10 +175,6 @@ function roleLabel(role) {
|
||||
return map[role] || role
|
||||
}
|
||||
|
||||
function formatDate(d) {
|
||||
if (!d) return '--'
|
||||
return new Date(d).toLocaleDateString('zh-CN')
|
||||
}
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
@@ -352,8 +385,26 @@ onMounted(() => {
|
||||
.review-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.biz-app-group { margin-bottom: 6px; }
|
||||
.biz-status-tag {
|
||||
font-size: 11px; padding: 2px 8px; border-radius: 8px; font-weight: 500; white-space: nowrap;
|
||||
}
|
||||
.biz-status-tag.small { font-size: 10px; padding: 1px 6px; }
|
||||
.biz-pending { background: #fff3e0; color: #e65100; }
|
||||
.biz-approved { background: #e8f5e9; color: #2e7d32; }
|
||||
.biz-rejected { background: #fce4ec; color: #c62828; }
|
||||
.biz-history {
|
||||
margin: 4px 0 8px 16px; padding: 8px 12px; background: #fafaf8; border-radius: 8px; border-left: 3px solid #e5e4e7;
|
||||
}
|
||||
.biz-history-item {
|
||||
display: flex; align-items: center; gap: 8px; font-size: 12px; padding: 4px 0; flex-wrap: wrap;
|
||||
}
|
||||
.biz-reject-reason { color: #c62828; font-size: 11px; }
|
||||
.biz-time { color: #bbb; font-size: 11px; margin-left: auto; }
|
||||
|
||||
.btn-approve {
|
||||
background: #4a9d7e;
|
||||
color: #fff;
|
||||
|
||||
Reference in New Issue
Block a user