feat: 商业认证完整流程 + 通知时间过滤
商业认证: - 申请页重写:商户名+说明字段,显示审核中/被拒/已通过状态 - 被拒绝显示原因,可重新申请 - 管理员拒绝时输入原因 - 商业核算页未认证用户点详情弹认证提示,跳转认证页面 - 删除项目仅管理员可见 通知: - 只显示用户注册后的通知,避免角色变更后看到旧通知 - 搜索未收录通知只发管理员和高级编辑者 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -241,18 +241,57 @@
|
||||
</div>
|
||||
|
||||
<!-- Business Verification -->
|
||||
<div v-if="!auth.isBusiness" ref="bizCertRef" class="section-card">
|
||||
<div ref="bizCertRef" class="section-card">
|
||||
<h4>💼 商业认证</h4>
|
||||
<p class="hint-text">申请商业认证后可使用商业核算功能。</p>
|
||||
<div class="form-group">
|
||||
<label>申请说明</label>
|
||||
<textarea v-model="businessReason" class="form-textarea" rows="3" placeholder="请说明您的申请理由..."></textarea>
|
||||
|
||||
<!-- 已认证 -->
|
||||
<div v-if="auth.isBusiness" class="biz-status biz-approved">
|
||||
<div class="biz-status-icon">✅</div>
|
||||
<div class="biz-status-text">已认证商业用户</div>
|
||||
</div>
|
||||
<button class="btn-primary" @click="applyBusiness" :disabled="!businessReason.trim()">提交申请</button>
|
||||
</div>
|
||||
<div v-else class="section-card">
|
||||
<h4>💼 商业认证</h4>
|
||||
<div class="verified-badge">✅ 已认证商业用户</div>
|
||||
|
||||
<!-- 审核中 -->
|
||||
<template v-else-if="bizApp.status === 'pending'">
|
||||
<div class="biz-status biz-pending">
|
||||
<div class="biz-status-icon">⏳</div>
|
||||
<div class="biz-status-text">认证申请审核中</div>
|
||||
<div class="biz-status-detail">商户名:{{ bizApp.business_name }}</div>
|
||||
<div class="biz-status-detail">提交时间:{{ formatDate(bizApp.created_at) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 被拒绝,可重新申请 -->
|
||||
<template v-else-if="bizApp.status === 'rejected'">
|
||||
<div class="biz-status biz-rejected">
|
||||
<div class="biz-status-icon">❌</div>
|
||||
<div class="biz-status-text">认证申请未通过</div>
|
||||
<div v-if="bizApp.reject_reason" class="biz-reject-reason">原因:{{ bizApp.reject_reason }}</div>
|
||||
</div>
|
||||
<p class="hint-text">你可以修改信息后重新申请。</p>
|
||||
<div class="form-group">
|
||||
<label>商户名称</label>
|
||||
<input v-model="businessName" class="form-input" placeholder="你的商户/品牌名称" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>申请说明</label>
|
||||
<textarea v-model="businessReason" class="form-textarea" rows="3" placeholder="请说明您的业务情况和申请理由..."></textarea>
|
||||
</div>
|
||||
<button class="btn-primary" @click="applyBusiness" :disabled="!businessName.trim()">重新申请</button>
|
||||
</template>
|
||||
|
||||
<!-- 首次申请 -->
|
||||
<template v-else>
|
||||
<p class="hint-text">申请商业认证后可使用商业核算功能,请填写以下信息。</p>
|
||||
<div class="form-group">
|
||||
<label>商户名称</label>
|
||||
<input v-model="businessName" class="form-input" placeholder="你的商户/品牌名称" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>申请说明</label>
|
||||
<textarea v-model="businessReason" class="form-textarea" rows="3" placeholder="请说明您的业务情况和申请理由..."></textarea>
|
||||
</div>
|
||||
<button class="btn-primary" @click="applyBusiness" :disabled="!businessName.trim()">提交申请</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -300,13 +339,20 @@ const displayName = ref('')
|
||||
const oldPassword = ref('')
|
||||
const newPassword = ref('')
|
||||
const confirmPassword = ref('')
|
||||
const businessName = ref('')
|
||||
const businessReason = ref('')
|
||||
const bizApp = ref({ status: null })
|
||||
|
||||
onMounted(async () => {
|
||||
await diaryStore.loadDiary()
|
||||
displayName.value = auth.user.display_name || ''
|
||||
await loadBrandSettings()
|
||||
returnRecipeId.value = localStorage.getItem('oil_return_recipe_id') || null
|
||||
// Load business application status
|
||||
try {
|
||||
const bizRes = await api('/api/my-business-application')
|
||||
if (bizRes.ok) bizApp.value = await bizRes.json()
|
||||
} catch {}
|
||||
// 从商业核算跳转过来,滚到商业认证区域
|
||||
if (route.query.section === 'biz-cert') {
|
||||
await nextTick()
|
||||
@@ -623,13 +669,30 @@ async function changePassword() {
|
||||
}
|
||||
|
||||
async function applyBusiness() {
|
||||
if (!businessName.value.trim()) {
|
||||
ui.showToast('请填写商户名称')
|
||||
return
|
||||
}
|
||||
try {
|
||||
await api('/api/business-apply', {
|
||||
const res = await api('/api/business-apply', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ reason: businessReason.value }),
|
||||
body: JSON.stringify({
|
||||
business_name: businessName.value.trim(),
|
||||
document: businessReason.value.trim(),
|
||||
}),
|
||||
})
|
||||
businessReason.value = ''
|
||||
ui.showToast('申请已提交,请等待审核')
|
||||
if (res.ok) {
|
||||
businessName.value = ''
|
||||
businessReason.value = ''
|
||||
bizApp.value = { status: 'pending', business_name: businessName.value }
|
||||
ui.showToast('申请已提交,请等待管理员审核')
|
||||
// Reload status
|
||||
const bizRes = await api('/api/my-business-application')
|
||||
if (bizRes.ok) bizApp.value = await bizRes.json()
|
||||
} else {
|
||||
const err = await res.json().catch(() => ({}))
|
||||
ui.showToast(err.detail || '提交失败')
|
||||
}
|
||||
} catch {
|
||||
ui.showToast('提交失败')
|
||||
}
|
||||
@@ -1084,6 +1147,20 @@ async function applyBusiness() {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.biz-status {
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.biz-status-icon { font-size: 32px; margin-bottom: 8px; }
|
||||
.biz-status-text { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
|
||||
.biz-status-detail { font-size: 12px; color: #999; }
|
||||
.biz-approved { background: #e8f5e9; color: #2e7d5a; }
|
||||
.biz-pending { background: #fff8e1; color: #e65100; }
|
||||
.biz-rejected { background: #fce4ec; color: #c62828; }
|
||||
.biz-reject-reason { font-size: 13px; margin-top: 8px; padding: 8px 12px; background: rgba(0,0,0,0.05); border-radius: 8px; }
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, #7ec6a4 0%, #4a9d7e 100%);
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
<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.user_name || app.display_name }}</span>
|
||||
<span class="review-reason">{{ app.reason }}</span>
|
||||
<span class="review-name">{{ app.display_name || app.username }}</span>
|
||||
<span class="review-reason">商户名:{{ app.business_name }}</span>
|
||||
</div>
|
||||
<div class="review-actions">
|
||||
<button class="btn-sm btn-approve" @click="approveBusiness(app)">通过</button>
|
||||
@@ -101,7 +101,7 @@ import { ref, computed, onMounted } from 'vue'
|
||||
import { useAuthStore } from '../stores/auth'
|
||||
import { useUiStore } from '../stores/ui'
|
||||
import { api } from '../composables/useApi'
|
||||
import { showConfirm } from '../composables/useDialog'
|
||||
import { showConfirm, showPrompt } from '../composables/useDialog'
|
||||
|
||||
const auth = useAuthStore()
|
||||
const ui = useUiStore()
|
||||
@@ -248,8 +248,13 @@ async function approveBusiness(app) {
|
||||
|
||||
async function rejectBusiness(app) {
|
||||
const id = app._id || app.id
|
||||
const reason = await showPrompt('请输入拒绝原因(选填):')
|
||||
if (reason === null) return
|
||||
try {
|
||||
const res = await api(`/api/business-applications/${id}/reject`, { method: 'POST' })
|
||||
const res = await api(`/api/business-applications/${id}/reject`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ reason: reason || '' }),
|
||||
})
|
||||
if (res.ok) {
|
||||
businessApps.value = businessApps.value.filter(item => (item._id || item.id) !== id)
|
||||
ui.showToast('已拒绝')
|
||||
|
||||
Reference in New Issue
Block a user