add
This commit is contained in:
441
src/pages/admin/certification-reviews/index.vue
Normal file
441
src/pages/admin/certification-reviews/index.vue
Normal file
@@ -0,0 +1,441 @@
|
||||
<template>
|
||||
<div class="certification-reviews-page">
|
||||
<div class="page-header">
|
||||
<h1 class="page-title">企业审核</h1>
|
||||
<p class="page-subtitle">审核用户提交的企业信息,通过后可进入企业认证流程</p>
|
||||
</div>
|
||||
|
||||
<div class="toolbar">
|
||||
<el-select
|
||||
v-model="filterStatus"
|
||||
placeholder="审核状态"
|
||||
clearable
|
||||
style="width: 140px"
|
||||
@change="loadList"
|
||||
>
|
||||
<el-option label="全部" value="" />
|
||||
<el-option label="待审核" value="pending" />
|
||||
<el-option label="已通过" value="approved" />
|
||||
<el-option label="已拒绝" value="rejected" />
|
||||
</el-select>
|
||||
<el-button type="primary" @click="loadList">查询</el-button>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="list"
|
||||
stripe
|
||||
style="width: 100%"
|
||||
class="reviews-table"
|
||||
>
|
||||
<el-table-column prop="submit_at" label="提交时间" width="170">
|
||||
<template #default="{ row }">
|
||||
{{ formatDate(row.submit_at) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="user_id" label="用户ID" width="280" 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="legal_person_name" label="法人姓名" width="100" />
|
||||
<el-table-column prop="manual_review_status" label="审核状态" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="statusTagType(row)" size="small">
|
||||
{{ reviewStatusDisplay(row) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="240" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" @click="openDetail(row.id)">查看详情</el-button>
|
||||
<template v-if="canShowApproveReject(row)">
|
||||
<el-button link type="success" @click="handleApprove(row)">通过</el-button>
|
||||
<el-button link type="danger" @click="handleReject(row)">拒绝</el-button>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<div class="pagination-wrap">
|
||||
<el-pagination
|
||||
v-model:current-page="page"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
@size-change="loadList"
|
||||
@current-change="loadList"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 详情抽屉 -->
|
||||
<el-drawer
|
||||
v-model="drawerVisible"
|
||||
title="企业信息详情"
|
||||
size="560"
|
||||
direction="rtl"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<template #header>
|
||||
<span class="drawer-title">企业信息详情</span>
|
||||
<div class="drawer-actions">
|
||||
<template v-if="detail && canShowApproveReject(detail)">
|
||||
<el-button type="success" size="small" @click="approveFromDrawer">通过</el-button>
|
||||
<el-button type="danger" size="small" @click="rejectFromDrawer">拒绝</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="detail" class="detail-content">
|
||||
<el-descriptions :column="1" border size="small">
|
||||
<el-descriptions-item label="企业名称">{{ detail.company_name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="统一社会信用代码">{{ detail.unified_social_code }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法人姓名">{{ detail.legal_person_name }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法人身份证号">{{ detail.legal_person_id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="法人手机号">{{ detail.legal_person_phone }}</el-descriptions-item>
|
||||
<el-descriptions-item label="企业地址">{{ detail.enterprise_address }}</el-descriptions-item>
|
||||
<el-descriptions-item label="授权代表姓名">{{ (detail.authorized_rep_name ?? detail.authorizedRepName) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="授权代表身份证号">{{ (detail.authorized_rep_id ?? detail.authorizedRepId) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="授权代表手机号">{{ (detail.authorized_rep_phone ?? detail.authorizedRepPhone) || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="应用场景说明">{{ detail.api_usage || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="提交时间">{{ formatDate(detail.submit_at) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="审核状态">
|
||||
<el-tag :type="statusTagType(detail)" size="small">
|
||||
{{ reviewStatusDisplay(detail) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detail.manual_review_remark" label="审核备注">
|
||||
{{ detail.manual_review_remark }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="image-section">
|
||||
<h4>营业执照</h4>
|
||||
<div v-if="detail.business_license_image_url" class="image-list">
|
||||
<a :href="detail.business_license_image_url" target="_blank" rel="noopener" class="image-link">
|
||||
<img v-if="isImageUrl(detail.business_license_image_url)" :src="detail.business_license_image_url" alt="营业执照" class="thumb" />
|
||||
<span v-else>查看链接</span>
|
||||
</a>
|
||||
</div>
|
||||
<p v-else class="text-gray-500 text-sm">无</p>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<h4>办公场地照片</h4>
|
||||
<div v-if="officePlaceUrls.length" class="image-list">
|
||||
<a v-for="(url, i) in officePlaceUrls" :key="i" :href="url" target="_blank" rel="noopener" class="image-link">
|
||||
<img v-if="isImageUrl(url)" :src="url" :alt="`场地${i + 1}`" class="thumb" />
|
||||
<span v-else>链接{{ i + 1 }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<p v-else class="text-gray-500 text-sm">无</p>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<h4>应用场景附件</h4>
|
||||
<div v-if="scenarioUrls.length" class="image-list">
|
||||
<a v-for="(url, i) in scenarioUrls" :key="i" :href="url" target="_blank" rel="noopener" class="image-link">
|
||||
<img v-if="isImageUrl(url)" :src="url" :alt="`场景${i + 1}`" class="thumb" />
|
||||
<span v-else>链接{{ i + 1 }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<p v-else class="text-gray-500 text-sm">无</p>
|
||||
</div>
|
||||
|
||||
<div class="image-section">
|
||||
<h4>授权代表身份证</h4>
|
||||
<div v-if="authorizedRepIdUrls.length" class="image-list">
|
||||
<a v-for="(url, i) in authorizedRepIdUrls" :key="i" :href="url" target="_blank" rel="noopener" class="image-link">
|
||||
<img v-if="isImageUrl(url)" :src="url" :alt="i === 0 ? '人像面' : '国徽面'" class="thumb" />
|
||||
<span v-else>{{ i === 0 ? '人像面' : '国徽面' }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<p v-else class="text-gray-500 text-sm">无</p>
|
||||
</div>
|
||||
</div>
|
||||
</el-drawer>
|
||||
|
||||
<!-- 通过弹窗 -->
|
||||
<el-dialog v-model="approveDialogVisible" title="审核通过" width="400px">
|
||||
<el-form label-width="80">
|
||||
<el-form-item label="审核备注">
|
||||
<el-input v-model="approveRemark" type="textarea" :rows="3" placeholder="选填" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="approveDialogVisible = false">取消</el-button>
|
||||
<el-button type="success" :loading="actionLoading" @click="confirmApprove">确认通过</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 拒绝弹窗 -->
|
||||
<el-dialog v-model="rejectDialogVisible" title="审核拒绝" width="400px">
|
||||
<el-form label-width="80">
|
||||
<el-form-item label="拒绝原因" required>
|
||||
<el-input v-model="rejectRemark" type="textarea" :rows="3" placeholder="必填" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="rejectDialogVisible = false">取消</el-button>
|
||||
<el-button type="danger" :loading="actionLoading" :disabled="!rejectRemark.trim()" @click="confirmReject">确认拒绝</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { certificationApi } from '@/api/index.js'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const loading = ref(false)
|
||||
const list = ref([])
|
||||
const total = ref(0)
|
||||
const page = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const filterStatus = ref('')
|
||||
|
||||
const drawerVisible = ref(false)
|
||||
const detail = ref(null)
|
||||
const approveDialogVisible = ref(false)
|
||||
const rejectDialogVisible = ref(false)
|
||||
const approveRemark = ref('')
|
||||
const rejectRemark = ref('')
|
||||
const actionLoading = ref(false)
|
||||
const pendingRecordId = ref('')
|
||||
|
||||
function formatDate(val) {
|
||||
if (!val) return '-'
|
||||
try {
|
||||
const d = new Date(val)
|
||||
return Number.isNaN(d.getTime()) ? val : d.toLocaleString('zh-CN')
|
||||
} catch {
|
||||
return val
|
||||
}
|
||||
}
|
||||
|
||||
// 已完成企业认证的状态:展示「已审核」,不再展示通过/拒绝
|
||||
const ENTERPRISE_VERIFIED_STATUSES = ['enterprise_verified', 'contract_applied', 'contract_signed', 'contract_rejected', 'contract_expired', 'completed']
|
||||
|
||||
function isEnterpriseVerified(certificationStatus) {
|
||||
if (!certificationStatus) return false
|
||||
return ENTERPRISE_VERIFIED_STATUSES.includes(certificationStatus)
|
||||
}
|
||||
|
||||
function reviewStatusDisplay(row) {
|
||||
if (isEnterpriseVerified(row?.certification_status)) return '已审核'
|
||||
const m = { pending: '待审核', approved: '已通过', rejected: '已拒绝' }
|
||||
return m[row?.manual_review_status] || row?.manual_review_status || '-'
|
||||
}
|
||||
|
||||
function statusTagType(row) {
|
||||
if (isEnterpriseVerified(row?.certification_status)) return 'info'
|
||||
const m = { pending: 'warning', approved: 'success', rejected: 'danger' }
|
||||
return m[row?.manual_review_status] || 'info'
|
||||
}
|
||||
|
||||
function canShowApproveReject(row) {
|
||||
if (!row) return false
|
||||
if (row.manual_review_status !== 'pending') return false
|
||||
if (isEnterpriseVerified(row.certification_status)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
// 判断是否可作为图片展示(含七牛云等无扩展名的 CDN URL)
|
||||
function isImageUrl(url) {
|
||||
if (!url || typeof url !== 'string') return false
|
||||
if (url.startsWith('blob:') || url.startsWith('data:')) return true
|
||||
if (url.startsWith('https://file.tianyuanapi.com')) return true
|
||||
return /\.(jpe?g|png|webp|gif)(\?|$)/i.test(url)
|
||||
}
|
||||
|
||||
const officePlaceUrls = computed(() => {
|
||||
if (!detail.value?.office_place_image_urls) return []
|
||||
try {
|
||||
const v = detail.value.office_place_image_urls
|
||||
return typeof v === 'string' ? JSON.parse(v) : v
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
const scenarioUrls = computed(() => {
|
||||
if (!detail.value?.scenario_attachment_urls) return []
|
||||
try {
|
||||
const v = detail.value.scenario_attachment_urls
|
||||
return typeof v === 'string' ? JSON.parse(v) : v
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
const authorizedRepIdUrls = computed(() => {
|
||||
if (!detail.value?.authorized_rep_id_image_urls) return []
|
||||
try {
|
||||
const v = detail.value.authorized_rep_id_image_urls
|
||||
return typeof v === 'string' ? JSON.parse(v) : v
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
async function loadList() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await certificationApi.adminListSubmitRecords({
|
||||
page: page.value,
|
||||
page_size: pageSize.value,
|
||||
manual_review_status: filterStatus.value || undefined
|
||||
})
|
||||
const data = res?.data
|
||||
list.value = data?.items ?? []
|
||||
total.value = data?.total ?? 0
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.message || '加载列表失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openDetail(id) {
|
||||
try {
|
||||
const res = await certificationApi.adminGetSubmitRecord(id)
|
||||
detail.value = res?.data ?? res
|
||||
drawerVisible.value = true
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.message || '加载详情失败')
|
||||
}
|
||||
}
|
||||
|
||||
function handleApprove(row) {
|
||||
pendingRecordId.value = row.id
|
||||
approveRemark.value = ''
|
||||
approveDialogVisible.value = true
|
||||
}
|
||||
|
||||
function approveFromDrawer() {
|
||||
if (!detail.value?.id) return
|
||||
pendingRecordId.value = detail.value.id
|
||||
approveRemark.value = ''
|
||||
approveDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function confirmApprove() {
|
||||
if (!pendingRecordId.value) return
|
||||
actionLoading.value = true
|
||||
try {
|
||||
await certificationApi.adminApproveSubmitRecord(pendingRecordId.value, { remark: approveRemark.value })
|
||||
ElMessage.success('已通过')
|
||||
approveDialogVisible.value = false
|
||||
drawerVisible.value = false
|
||||
detail.value = null
|
||||
loadList()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.message || '操作失败')
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleReject(row) {
|
||||
pendingRecordId.value = row.id
|
||||
rejectRemark.value = ''
|
||||
rejectDialogVisible.value = true
|
||||
}
|
||||
|
||||
function rejectFromDrawer() {
|
||||
if (!detail.value?.id) return
|
||||
pendingRecordId.value = detail.value.id
|
||||
rejectRemark.value = ''
|
||||
rejectDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function confirmReject() {
|
||||
if (!pendingRecordId.value || !rejectRemark.value?.trim()) return
|
||||
actionLoading.value = true
|
||||
try {
|
||||
await certificationApi.adminRejectSubmitRecord(pendingRecordId.value, { remark: rejectRemark.value.trim() })
|
||||
ElMessage.success('已拒绝')
|
||||
rejectDialogVisible.value = false
|
||||
drawerVisible.value = false
|
||||
detail.value = null
|
||||
loadList()
|
||||
} catch (e) {
|
||||
ElMessage.error(e?.message || '操作失败')
|
||||
} finally {
|
||||
actionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.certification-reviews-page {
|
||||
padding: 24px;
|
||||
}
|
||||
.page-header {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
.page-subtitle {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
margin: 0;
|
||||
}
|
||||
.toolbar {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.pagination-wrap {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.drawer-title {
|
||||
margin-right: 12px;
|
||||
}
|
||||
.drawer-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.detail-content {
|
||||
padding-right: 8px;
|
||||
}
|
||||
.image-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
.image-section h4 {
|
||||
font-size: 14px;
|
||||
color: #475569;
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
.image-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
.image-link {
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
line-height: 100px;
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
}
|
||||
.image-link .thumb {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user