This commit is contained in:
Mrx
2026-03-19 13:23:51 +08:00
parent d05ded72c7
commit a12c7caa3a
2 changed files with 168 additions and 55 deletions

View File

@@ -208,11 +208,13 @@ export const certificationApi = {
// 管理员代用户完成认证(暂不关联合同) // 管理员代用户完成认证(暂不关联合同)
adminCompleteWithoutContract: (data) => request.post('/certifications/admin/complete-without-contract', data), adminCompleteWithoutContract: (data) => request.post('/certifications/admin/complete-without-contract', data),
// 管理端企业审核:列表、详情、通过、拒绝 // 管理端企业审核:列表(按状态机 certification_status 筛选)、详情、通过、拒绝、按用户变更状态
adminListSubmitRecords: (params) => request.get('/certifications/admin/submit-records', { params }), adminListSubmitRecords: (params) => request.get('/certifications/admin/submit-records', { params }),
adminGetSubmitRecord: (id) => request.get(`/certifications/admin/submit-records/${id}`), adminGetSubmitRecord: (id) => request.get(`/certifications/admin/submit-records/${id}`),
adminApproveSubmitRecord: (id, data) => request.post(`/certifications/admin/submit-records/${id}/approve`, data || {}), adminApproveSubmitRecord: (id, data) => request.post(`/certifications/admin/submit-records/${id}/approve`, data || {}),
adminRejectSubmitRecord: (id, data) => request.post(`/certifications/admin/submit-records/${id}/reject`, data) adminRejectSubmitRecord: (id, data) => request.post(`/certifications/admin/submit-records/${id}/reject`, data),
// 管理端按用户变更认证状态以状态机为准info_submitted=通过 / info_rejected=拒绝)
adminTransitionCertificationStatus: (data) => request.post('/certifications/admin/transition-status', data)
} }
// API相关接口 // API相关接口

View File

@@ -8,15 +8,15 @@
<div class="toolbar"> <div class="toolbar">
<el-select <el-select
v-model="filterStatus" v-model="filterStatus"
placeholder="审核状态" placeholder="认证状态"
clearable clearable
style="width: 140px" style="width: 140px"
@change="loadList" @change="loadList"
> >
<el-option label="全部" value="" /> <el-option label="全部" value="" />
<el-option label="待审核" value="pending" /> <el-option label="待审核" value="info_pending_review" />
<el-option label="已通过" value="approved" /> <el-option label="已通过" value="info_submitted" />
<el-option label="已拒绝" value="rejected" /> <el-option label="已拒绝" value="info_rejected" />
</el-select> </el-select>
<el-input <el-input
v-model="filterCompanyName" v-model="filterCompanyName"
@@ -58,10 +58,10 @@
<el-table-column prop="company_name" label="企业名称" min-width="180" show-overflow-tooltip /> <el-table-column prop="company_name" label="企业名称" min-width="180" show-overflow-tooltip />
<el-table-column prop="unified_social_code" label="统一社会信用代码" width="200" /> <el-table-column prop="unified_social_code" label="统一社会信用代码" width="200" />
<el-table-column prop="legal_person_name" label="法人姓名" width="100" /> <el-table-column prop="legal_person_name" label="法人姓名" width="100" />
<el-table-column prop="manual_review_status" label="审核状态" width="100"> <el-table-column prop="certification_status" label="认证状态" width="100">
<template #default="{ row }"> <template #default="{ row }">
<el-tag :type="statusTagType(row)" size="small"> <el-tag :type="statusTagType(row)" size="small">
{{ reviewStatusDisplay(row) }} {{ certificationStatusDisplay(row?.certification_status) }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
@@ -106,30 +106,52 @@
</div> </div>
</template> </template>
<div v-if="detail" class="detail-content"> <div v-if="detail" class="detail-content">
<el-descriptions :column="1" border size="small"> <section class="detail-section">
<el-descriptions-item label="企业名称">{{ detail.company_name }}</el-descriptions-item> <h4 class="detail-section-title">基本信息</h4>
<el-descriptions-item label="统一社会信用代码">{{ detail.unified_social_code }}</el-descriptions-item> <dl class="detail-dl">
<el-descriptions-item label="法人姓名">{{ detail.legal_person_name }}</el-descriptions-item> <dt>企业名称</dt>
<el-descriptions-item label="法人身份证号">{{ detail.legal_person_id }}</el-descriptions-item> <dd>{{ detail.company_name }}</dd>
<el-descriptions-item label="法人手机号">{{ detail.legal_person_phone }}</el-descriptions-item> <dt>统一社会信用代码</dt>
<el-descriptions-item label="企业地址">{{ detail.enterprise_address }}</el-descriptions-item> <dd class="detail-mono">{{ detail.unified_social_code }}</dd>
<el-descriptions-item label="授权代表姓名">{{ (detail.authorized_rep_name ?? detail.authorizedRepName) || '-' }}</el-descriptions-item> <dt>法人姓名</dt>
<el-descriptions-item label="授权代表身份证号">{{ (detail.authorized_rep_id ?? detail.authorizedRepId) || '-' }}</el-descriptions-item> <dd>{{ detail.legal_person_name }}</dd>
<el-descriptions-item label="授权代表手机号">{{ (detail.authorized_rep_phone ?? detail.authorizedRepPhone) || '-' }}</el-descriptions-item> <dt>法人身份证号</dt>
<el-descriptions-item label="应用场景说明">{{ detail.api_usage || '-' }}</el-descriptions-item> <dd class="detail-mono">{{ detail.legal_person_id }}</dd>
<el-descriptions-item label="提交时间">{{ formatDate(detail.submit_at) }}</el-descriptions-item> <dt>法人手机号</dt>
<el-descriptions-item label="审核状态"> <dd>{{ detail.legal_person_phone }}</dd>
<el-tag :type="statusTagType(detail)" size="small"> <dt>企业地址</dt>
{{ reviewStatusDisplay(detail) }} <dd class="detail-long">{{ detail.enterprise_address || '-' }}</dd>
</el-tag> <dt>提交时间</dt>
</el-descriptions-item> <dd>{{ formatDate(detail.submit_at) }}</dd>
<el-descriptions-item v-if="detail.manual_review_remark" label="审核备注"> <dt>认证状态</dt>
{{ detail.manual_review_remark }} <dd>
</el-descriptions-item> <el-tag :type="statusTagType(detail)" size="small">
<el-descriptions-item v-if="detail.failure_reason" label="失败原因"> {{ certificationStatusDisplay(detail?.certification_status) }}
<span class="text-red-600">{{ detail.failure_reason }}</span> </el-tag>
</el-descriptions-item> </dd>
</el-descriptions> <template v-if="detail.failure_reason">
<dt>失败原因</dt>
<dd class="detail-long detail-error">{{ detail.failure_reason }}</dd>
</template>
</dl>
</section>
<section class="detail-section">
<h4 class="detail-section-title">授权代表</h4>
<dl class="detail-dl">
<dt>姓名</dt>
<dd>{{ (detail.authorized_rep_name ?? detail.authorizedRepName) || '-' }}</dd>
<dt>身份证号</dt>
<dd class="detail-mono">{{ (detail.authorized_rep_id ?? detail.authorizedRepId) || '-' }}</dd>
<dt>手机号</dt>
<dd>{{ (detail.authorized_rep_phone ?? detail.authorizedRepPhone) || '-' }}</dd>
</dl>
</section>
<section class="detail-section">
<h4 class="detail-section-title">应用场景说明</h4>
<div class="detail-long-block">{{ detail.api_usage || '无' }}</div>
</section>
<div class="image-section"> <div class="image-section">
<h4>营业执照</h4> <h4>营业执照</h4>
@@ -227,6 +249,7 @@ const approveRemark = ref('')
const rejectRemark = ref('') const rejectRemark = ref('')
const actionLoading = ref(false) const actionLoading = ref(false)
const pendingRecordId = ref('') const pendingRecordId = ref('')
const pendingUserId = ref('')
function formatDate(val) { function formatDate(val) {
if (!val) return '-' if (!val) return '-'
@@ -238,31 +261,45 @@ function formatDate(val) {
} }
} }
// 已完成企业认证状态展示「已审核」,不再展示通过/拒绝 // 以状态机为准:认证状态展示与是否可操作(全流程口径)
const ENTERPRISE_VERIFIED_STATUSES = ['enterprise_verified', 'contract_applied', 'contract_signed', 'contract_rejected', 'contract_expired', 'completed'] const CERTIFICATION_STATUS_LABELS = {
pending: '待认证',
function isEnterpriseVerified(certificationStatus) { info_pending_review: '待审核',
if (!certificationStatus) return false info_submitted: '已通过',
return ENTERPRISE_VERIFIED_STATUSES.includes(certificationStatus) info_rejected: '已拒绝',
enterprise_verified: '已企业认证',
contract_applied: '已申请合同',
contract_signed: '已签署合同',
contract_rejected: '合同拒签',
contract_expired: '合同超时',
completed: '已完成'
} }
function reviewStatusDisplay(row) { function certificationStatusDisplay(certificationStatus) {
if (isEnterpriseVerified(row?.certification_status)) return '已审核' if (!certificationStatus) return '-'
const m = { pending: '待审核', approved: '已通过', rejected: '已拒绝' } return CERTIFICATION_STATUS_LABELS[certificationStatus] || certificationStatus
return m[row?.manual_review_status] || row?.manual_review_status || '-'
} }
function statusTagType(row) { function statusTagType(row) {
if (isEnterpriseVerified(row?.certification_status)) return 'info' const status = row?.certification_status
const m = { pending: 'warning', approved: 'success', rejected: 'danger' } const m = {
return m[row?.manual_review_status] || 'info' pending: 'info',
info_pending_review: 'warning',
info_submitted: 'success',
info_rejected: 'danger',
enterprise_verified: 'success',
contract_applied: 'info',
contract_signed: 'success',
contract_rejected: 'danger',
contract_expired: 'warning',
completed: 'success'
}
return m[status] || 'info'
} }
function canShowApproveReject(row) { function canShowApproveReject(row) {
if (!row) return false if (!row) return false
if (row.manual_review_status !== 'pending') return false return row.certification_status === 'info_pending_review'
if (isEnterpriseVerified(row.certification_status)) return false
return true
} }
// 判断是否可作为图片展示(含七牛云等无扩展名的 CDN URL // 判断是否可作为图片展示(含七牛云等无扩展名的 CDN URL
@@ -309,7 +346,7 @@ async function loadList() {
const res = await certificationApi.adminListSubmitRecords({ const res = await certificationApi.adminListSubmitRecords({
page: page.value, page: page.value,
page_size: pageSize.value, page_size: pageSize.value,
manual_review_status: filterStatus.value || undefined, certification_status: filterStatus.value || undefined,
company_name: filterCompanyName.value || undefined, company_name: filterCompanyName.value || undefined,
legal_person_phone: filterLegalPersonPhone.value || undefined, legal_person_phone: filterLegalPersonPhone.value || undefined,
legal_person_name: filterLegalPersonName.value || undefined legal_person_name: filterLegalPersonName.value || undefined
@@ -336,6 +373,7 @@ async function openDetail(id) {
function handleApprove(row) { function handleApprove(row) {
pendingRecordId.value = row.id pendingRecordId.value = row.id
pendingUserId.value = row.user_id
approveRemark.value = '' approveRemark.value = ''
approveDialogVisible.value = true approveDialogVisible.value = true
} }
@@ -343,19 +381,26 @@ function handleApprove(row) {
function approveFromDrawer() { function approveFromDrawer() {
if (!detail.value?.id) return if (!detail.value?.id) return
pendingRecordId.value = detail.value.id pendingRecordId.value = detail.value.id
pendingUserId.value = detail.value.user_id
approveRemark.value = '' approveRemark.value = ''
approveDialogVisible.value = true approveDialogVisible.value = true
} }
async function confirmApprove() { async function confirmApprove() {
if (!pendingRecordId.value) return if (!pendingUserId.value) return
actionLoading.value = true actionLoading.value = true
try { try {
await certificationApi.adminApproveSubmitRecord(pendingRecordId.value, { remark: approveRemark.value }) await certificationApi.adminTransitionCertificationStatus({
user_id: pendingUserId.value,
target_status: 'info_submitted',
remark: approveRemark.value || ''
})
ElMessage.success('已通过') ElMessage.success('已通过')
approveDialogVisible.value = false approveDialogVisible.value = false
drawerVisible.value = false drawerVisible.value = false
detail.value = null detail.value = null
pendingRecordId.value = ''
pendingUserId.value = ''
loadList() loadList()
} catch (e) { } catch (e) {
ElMessage.error(e?.message || '操作失败') ElMessage.error(e?.message || '操作失败')
@@ -366,6 +411,7 @@ async function confirmApprove() {
function handleReject(row) { function handleReject(row) {
pendingRecordId.value = row.id pendingRecordId.value = row.id
pendingUserId.value = row.user_id
rejectRemark.value = '' rejectRemark.value = ''
rejectDialogVisible.value = true rejectDialogVisible.value = true
} }
@@ -373,19 +419,26 @@ function handleReject(row) {
function rejectFromDrawer() { function rejectFromDrawer() {
if (!detail.value?.id) return if (!detail.value?.id) return
pendingRecordId.value = detail.value.id pendingRecordId.value = detail.value.id
pendingUserId.value = detail.value.user_id
rejectRemark.value = '' rejectRemark.value = ''
rejectDialogVisible.value = true rejectDialogVisible.value = true
} }
async function confirmReject() { async function confirmReject() {
if (!pendingRecordId.value || !rejectRemark.value?.trim()) return if (!pendingUserId.value || !rejectRemark.value?.trim()) return
actionLoading.value = true actionLoading.value = true
try { try {
await certificationApi.adminRejectSubmitRecord(pendingRecordId.value, { remark: rejectRemark.value.trim() }) await certificationApi.adminTransitionCertificationStatus({
user_id: pendingUserId.value,
target_status: 'info_rejected',
remark: rejectRemark.value.trim()
})
ElMessage.success('已拒绝') ElMessage.success('已拒绝')
rejectDialogVisible.value = false rejectDialogVisible.value = false
drawerVisible.value = false drawerVisible.value = false
detail.value = null detail.value = null
pendingRecordId.value = ''
pendingUserId.value = ''
loadList() loadList()
} catch (e) { } catch (e) {
ElMessage.error(e?.message || '操作失败') ElMessage.error(e?.message || '操作失败')
@@ -436,15 +489,73 @@ onMounted(() => {
align-items: center; align-items: center;
} }
.detail-content { .detail-content {
padding-right: 8px; padding-right: 12px;
max-height: calc(100vh - 60px);
overflow-y: auto;
}
.detail-section {
margin-bottom: 20px;
}
.detail-section-title {
font-size: 13px;
font-weight: 600;
color: #475569;
margin: 0 0 10px;
padding-bottom: 6px;
border-bottom: 1px solid #e2e8f0;
}
.detail-dl {
display: grid;
grid-template-columns: 110px 1fr;
gap: 8px 16px;
margin: 0;
font-size: 13px;
}
.detail-dl dt {
margin: 0;
color: #64748b;
font-weight: 500;
line-height: 1.5;
}
.detail-dl dd {
margin: 0;
color: #1e293b;
line-height: 1.5;
word-break: break-word;
}
.detail-mono {
font-family: ui-monospace, monospace;
font-size: 12px;
}
.detail-long {
white-space: pre-wrap;
word-break: break-word;
}
.detail-long-block {
font-size: 13px;
line-height: 1.6;
color: #334155;
white-space: pre-wrap;
word-break: break-word;
padding: 12px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
max-height: 160px;
overflow-y: auto;
}
.detail-error {
color: #dc2626;
} }
.image-section { .image-section {
margin-top: 20px; margin-top: 20px;
} }
.image-section h4 { .image-section h4 {
font-size: 14px; font-size: 13px;
font-weight: 600;
color: #475569; color: #475569;
margin: 0 0 8px; margin: 0 0 8px;
padding-bottom: 4px;
} }
.image-list { .image-list {
display: flex; display: flex;