add
This commit is contained in:
@@ -366,6 +366,7 @@
|
||||
"useThrottleFn": true,
|
||||
"useThrottledRefHistory": true,
|
||||
"useTimeAgo": true,
|
||||
"useTimeAgoIntl": true,
|
||||
"useTimeout": true,
|
||||
"useTimeoutFn": true,
|
||||
"useTimeoutPoll": true,
|
||||
|
||||
1
auto-imports.d.ts
vendored
1
auto-imports.d.ts
vendored
@@ -817,6 +817,7 @@ declare module 'vue' {
|
||||
readonly useThrottleFn: UnwrapRef<typeof import('@vueuse/core')['useThrottleFn']>
|
||||
readonly useThrottledRefHistory: UnwrapRef<typeof import('@vueuse/core')['useThrottledRefHistory']>
|
||||
readonly useTimeAgo: UnwrapRef<typeof import('@vueuse/core')['useTimeAgo']>
|
||||
readonly useTimeAgoIntl: UnwrapRef<typeof import('@vueuse/core')['useTimeAgoIntl']>
|
||||
readonly useTimeout: UnwrapRef<typeof import('@vueuse/core')['useTimeout']>
|
||||
readonly useTimeoutFn: UnwrapRef<typeof import('@vueuse/core')['useTimeoutFn']>
|
||||
readonly useTimeoutPoll: UnwrapRef<typeof import('@vueuse/core')['useTimeoutPoll']>
|
||||
|
||||
@@ -194,8 +194,25 @@ export const certificationApi = {
|
||||
}
|
||||
}),
|
||||
|
||||
// 上传认证图片到七牛云(企业信息中的营业执照、办公场地、场景附件、授权代表身份证等)
|
||||
uploadFile: (file) => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
return request.post('/certifications/upload', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 管理员代用户完成认证(暂不关联合同)
|
||||
adminCompleteWithoutContract: (data) => request.post('/certifications/admin/complete-without-contract', data)
|
||||
adminCompleteWithoutContract: (data) => request.post('/certifications/admin/complete-without-contract', data),
|
||||
|
||||
// 管理端企业审核:列表、详情、通过、拒绝
|
||||
adminListSubmitRecords: (params) => request.get('/certifications/admin/submit-records', { params }),
|
||||
adminGetSubmitRecord: (id) => request.get(`/certifications/admin/submit-records/${id}`),
|
||||
adminApproveSubmitRecord: (id, data) => request.post(`/certifications/admin/submit-records/${id}/approve`, data || {}),
|
||||
adminRejectSubmitRecord: (id, data) => request.post(`/certifications/admin/submit-records/${id}/reject`, data)
|
||||
}
|
||||
|
||||
// API相关接口
|
||||
|
||||
@@ -116,6 +116,7 @@ export const getUserAccessibleMenuItems = (userType = 'user') => {
|
||||
icon: Setting,
|
||||
children: [
|
||||
{ name: '系统统计', path: '/admin/statistics', icon: ChartBar },
|
||||
{ name: '企业审核', path: '/admin/certification-reviews', icon: ShieldCheck },
|
||||
{ name: '产品管理', path: '/admin/products', icon: Cube },
|
||||
{ name: '用户管理', path: '/admin/users', icon: Users },
|
||||
{ name: '分类管理', path: '/admin/categories', icon: Tag },
|
||||
|
||||
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>
|
||||
@@ -22,7 +22,7 @@
|
||||
label-width="10em"
|
||||
class="enterprise-form-content"
|
||||
>
|
||||
<div class="form-section">
|
||||
<div class="form-section">
|
||||
<h3 class="section-title">基本信息</h3>
|
||||
|
||||
<!-- 企业名称和OCR识别区域 -->
|
||||
@@ -42,21 +42,7 @@
|
||||
<div class="ocr-compact">
|
||||
<div class="ocr-header-compact">
|
||||
<el-icon class="text-green-600"><DocumentIcon /></el-icon>
|
||||
<span class="ocr-title-compact">OCR识别</span>
|
||||
<el-upload
|
||||
ref="uploadRef"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
:on-change="handleFileChange"
|
||||
:before-upload="beforeUpload"
|
||||
accept="image/jpeg,image/jpg,image/png,image/webp"
|
||||
class="ocr-uploader-compact"
|
||||
>
|
||||
<el-button type="success" size="small" plain>
|
||||
<el-icon><ArrowUpTrayIcon /></el-icon>
|
||||
上传营业执照
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<span class="ocr-title-compact">可进行OCR识别,请在下方上传营业执照</span>
|
||||
</div>
|
||||
<div v-if="ocrLoading" class="ocr-status-compact">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
@@ -84,6 +70,7 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="16" class="mb-4">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="法人姓名" prop="legalPersonName">
|
||||
@@ -96,21 +83,9 @@
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="法人身份证号" prop="legalPersonID">
|
||||
<el-input
|
||||
v-model="form.legalPersonID"
|
||||
placeholder="请输入法人身份证号"
|
||||
clearable
|
||||
size="default"
|
||||
class="form-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="16" class="mb-4">
|
||||
<el-col :span="24">
|
||||
|
||||
<el-col :span="12">
|
||||
<el-form-item label="企业地址" prop="enterpriseAddress">
|
||||
<el-input
|
||||
v-model="form.enterpriseAddress"
|
||||
@@ -122,8 +97,78 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 营业执照图片上传(对应 BusinessLicenseImageURL) -->
|
||||
<el-row :gutter="16" class="mb-4">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="营业执照图片" prop="businessLicenseImageURL">
|
||||
<el-upload
|
||||
class="upload-area single-upload-area"
|
||||
action="#"
|
||||
list-type="picture-card"
|
||||
:auto-upload="false"
|
||||
:file-list="businessLicenseFileList"
|
||||
:limit="1"
|
||||
:on-change="handleBusinessLicenseChange"
|
||||
:on-remove="handleBusinessLicenseRemove"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<el-icon class="upload-icon"><ArrowUpTrayIcon /></el-icon>
|
||||
<div class="el-upload__text">上传清晰可辨的营业执照图片</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<!-- 办公场地图片上传 -->
|
||||
<el-row :gutter="16" class="mb-4">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="办公场地照片">
|
||||
<div class="text-xs mb-1 text-blue-500">
|
||||
请在非 IE 浏览器下上传大小不超过 3M 的图片,最多 10 张,需体现门楣 LOGO、办公设备与工作人员。
|
||||
</div>
|
||||
<div
|
||||
class="upload-drop-zone"
|
||||
:class="{ 'is-dragover': officePlaceDragover }"
|
||||
@dragover.prevent="officePlaceDragover = true"
|
||||
@dragleave.prevent="officePlaceDragover = false"
|
||||
@drop.prevent="onOfficePlaceDrop"
|
||||
>
|
||||
<el-upload
|
||||
ref="officePlaceUploadRef"
|
||||
class="upload-area"
|
||||
action="#"
|
||||
list-type="picture-card"
|
||||
:auto-upload="false"
|
||||
v-model:file-list="officePlaceFileList"
|
||||
accept="image/jpeg,image/jpg,image/png,image/webp"
|
||||
multiple
|
||||
:limit="10"
|
||||
:on-change="handleOfficePlaceChange"
|
||||
:on-remove="handleOfficePlaceRemove"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<div class="upload-trigger-inner">
|
||||
<el-icon class="upload-icon"><ArrowUpTrayIcon /></el-icon>
|
||||
<div class="el-upload__text">上传办公场地环境照片</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<el-row :gutter="16" class="mb-4">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="法人身份证号" prop="legalPersonID">
|
||||
<el-input
|
||||
v-model="form.legalPersonID"
|
||||
placeholder="请输入法人身份证号"
|
||||
clearable
|
||||
size="default"
|
||||
class="form-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="法人手机号" prop="legalPersonPhone">
|
||||
<el-input
|
||||
@@ -161,6 +206,149 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
<h3 class="section-title">授权代表信息</h3>
|
||||
<p class="section-desc">授权代表信息用于证明该人员已获得企业授权,请确保姓名、身份证号、手机号及身份证正反面照片真实有效。</p>
|
||||
|
||||
<!-- 授权代表信息 -->
|
||||
<el-row :gutter="16" class="mb-4">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="授权代表姓名" prop="authorizedRepName">
|
||||
<el-input
|
||||
v-model="form.authorizedRepName"
|
||||
placeholder="请输入授权代表姓名"
|
||||
clearable
|
||||
size="default"
|
||||
class="form-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="授权代表身份证号" prop="authorizedRepID">
|
||||
<el-input
|
||||
v-model="form.authorizedRepID"
|
||||
placeholder="请输入授权代表身份证号"
|
||||
clearable
|
||||
size="default"
|
||||
class="form-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="授权代表手机号" prop="authorizedRepPhone">
|
||||
<el-input
|
||||
v-model="form.authorizedRepPhone"
|
||||
placeholder="请输入授权代表手机号"
|
||||
clearable
|
||||
size="default"
|
||||
maxlength="11"
|
||||
class="form-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 授权代表身份证图片上传 -->
|
||||
<el-row :gutter="16" class="mb-4">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身份证人像面" prop="authorizedRepIDImageURLs">
|
||||
<el-upload
|
||||
class="upload-area single-upload-area"
|
||||
action="#"
|
||||
list-type="picture-card"
|
||||
:auto-upload="false"
|
||||
:file-list="authorizedRepIDFrontFileList"
|
||||
:limit="1"
|
||||
:on-change="handleAuthorizedRepIDFrontChange"
|
||||
:on-remove="handleAuthorizedRepIDFrontRemove"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<el-icon class="upload-icon"><ArrowUpTrayIcon /></el-icon>
|
||||
<div class="el-upload__text">上传授权代表身份证人像面</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="身份证国徽面" prop="authorizedRepIDImageURLs">
|
||||
<el-upload
|
||||
class="upload-area single-upload-area"
|
||||
action="#"
|
||||
list-type="picture-card"
|
||||
:auto-upload="false"
|
||||
:file-list="authorizedRepIDBackFileList"
|
||||
:limit="1"
|
||||
:on-change="handleAuthorizedRepIDBackChange"
|
||||
:on-remove="handleAuthorizedRepIDBackRemove"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<el-icon class="upload-icon"><ArrowUpTrayIcon /></el-icon>
|
||||
<div class="el-upload__text">上传授权代表身份证国徽面</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<h3 class="section-title">应用场景填写</h3>
|
||||
<p class="section-desc">请描述您调用接口的具体业务场景</p>
|
||||
|
||||
<!-- 接口用途描述(对应 APIUsage) -->
|
||||
<el-row :gutter="16" class="mb-4">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="应用场景" prop="apiUsage">
|
||||
<el-input
|
||||
v-model="form.apiUsage"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请描述您调用接口的具体业务场景和用途"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
class="form-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
|
||||
|
||||
<!-- 应用场景附件图片上传 -->
|
||||
<el-row :gutter="16" class="mb-4">
|
||||
<el-col :span="24">
|
||||
|
||||
<el-form-item label="应用场景附件">
|
||||
<div class="text-xs mb-1 text-blue-500">
|
||||
请在非IE浏览器下上传大小不超过3M的图片,要求:不超过10张后台应用截图
|
||||
</div>
|
||||
<div
|
||||
class="upload-drop-zone"
|
||||
:class="{ 'is-dragover': scenarioDragover }"
|
||||
@dragover.prevent="scenarioDragover = true"
|
||||
@dragleave.prevent="scenarioDragover = false"
|
||||
@drop.prevent="onScenarioDrop"
|
||||
>
|
||||
<el-upload
|
||||
ref="scenarioUploadRef"
|
||||
class="upload-area"
|
||||
action="#"
|
||||
list-type="picture-card"
|
||||
:auto-upload="false"
|
||||
v-model:file-list="scenarioFileList"
|
||||
accept="image/jpeg,image/jpg,image/png,image/webp"
|
||||
multiple
|
||||
:limit="10"
|
||||
:on-change="handleScenarioChange"
|
||||
:on-remove="handleScenarioRemove"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<div class="upload-trigger-inner">
|
||||
<el-icon class="upload-icon"><ArrowUpTrayIcon /></el-icon>
|
||||
<div class="el-upload__text">上传业务场景相关截图或证明材料</div>
|
||||
</div>
|
||||
</el-upload>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
@@ -190,7 +378,7 @@ import {
|
||||
CheckIcon,
|
||||
DocumentIcon
|
||||
} from '@heroicons/vue/24/outline'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox, genFileId } from 'element-plus'
|
||||
import { useAliyunCaptcha } from '@/composables/useAliyunCaptcha'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -203,7 +391,17 @@ const props = defineProps({
|
||||
legalPersonID: '',
|
||||
legalPersonPhone: '',
|
||||
enterpriseAddress: '',
|
||||
legalPersonCode: ''
|
||||
legalPersonCode: '',
|
||||
// 扩展:营业执照 & 办公场地 & 场景
|
||||
businessLicenseImageURL: '',
|
||||
officePlaceImageURLs: [],
|
||||
apiUsage: '',
|
||||
scenarioAttachmentURLs: [],
|
||||
// 授权代表信息
|
||||
authorizedRepName: '',
|
||||
authorizedRepID: '',
|
||||
authorizedRepPhone: '',
|
||||
authorizedRepIDImageURLs: []
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -224,7 +422,17 @@ const form = ref({
|
||||
legalPersonID: '',
|
||||
legalPersonPhone: '',
|
||||
enterpriseAddress: '',
|
||||
legalPersonCode: ''
|
||||
legalPersonCode: '',
|
||||
// 扩展:营业执照 & 办公场地 & 场景
|
||||
businessLicenseImageURL: '',
|
||||
officePlaceImageURLs: [],
|
||||
apiUsage: '',
|
||||
scenarioAttachmentURLs: [],
|
||||
// 授权代表信息
|
||||
authorizedRepName: '',
|
||||
authorizedRepID: '',
|
||||
authorizedRepPhone: '',
|
||||
authorizedRepIDImageURLs: []
|
||||
})
|
||||
|
||||
// 验证码相关状态
|
||||
@@ -239,6 +447,17 @@ const submitting = ref(false)
|
||||
const ocrLoading = ref(false)
|
||||
const ocrResult = ref(false)
|
||||
const uploadRef = ref()
|
||||
const officePlaceUploadRef = ref()
|
||||
const scenarioUploadRef = ref()
|
||||
const officePlaceDragover = ref(false)
|
||||
const scenarioDragover = ref(false)
|
||||
|
||||
// 上传文件列表(前端展示用)
|
||||
const officePlaceFileList = ref([])
|
||||
const scenarioFileList = ref([])
|
||||
const businessLicenseFileList = ref([])
|
||||
const authorizedRepIDFrontFileList = ref([])
|
||||
const authorizedRepIDBackFileList = ref([])
|
||||
|
||||
// 计算属性
|
||||
const canSendCode = computed(() => {
|
||||
@@ -326,6 +545,30 @@ const enterpriseRules = {
|
||||
legalPersonCode: [
|
||||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
{ len: 6, message: '验证码应为6位数字', trigger: 'blur' }
|
||||
],
|
||||
// 扩展字段简单校验(可按需加强)
|
||||
businessLicenseImageURL: [
|
||||
{ required: true, message: '请上传营业执照图片', trigger: 'change' }
|
||||
],
|
||||
apiUsage: [
|
||||
{ required: true, message: '请填写接口用途', trigger: 'blur' },
|
||||
{ min: 5, max: 500, message: '接口用途长度应在5-500个字符之间', trigger: 'blur' }
|
||||
],
|
||||
// 授权代表信息简单校验(可按需加强)
|
||||
authorizedRepName: [
|
||||
{ required: true, message: '请输入授权代表姓名', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '授权代表姓名长度应在2-20个字符之间', trigger: 'blur' }
|
||||
],
|
||||
authorizedRepID: [
|
||||
{ required: true, message: '请输入授权代表身份证号', trigger: 'blur' },
|
||||
{ validator: validateIDCard, trigger: 'blur' }
|
||||
],
|
||||
authorizedRepPhone: [
|
||||
{ required: true, message: '请输入授权代表手机号', trigger: 'blur' },
|
||||
{ validator: validatePhone, trigger: 'blur' }
|
||||
],
|
||||
authorizedRepIDImageURLs: [
|
||||
{ required: true, message: '请上传授权代表身份证正反面图片', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -395,7 +638,7 @@ const beforeUpload = (file) => {
|
||||
return true
|
||||
}
|
||||
|
||||
// 处理文件变化
|
||||
// 处理文件变化:触发 OCR,并保存营业执照原图 URL(若有上传地址)
|
||||
const handleFileChange = async (file) => {
|
||||
if (!beforeUpload(file.raw)) {
|
||||
return
|
||||
@@ -419,6 +662,11 @@ const handleFileChange = async (file) => {
|
||||
form.value.legalPersonID = ocrData.legal_person_id || ''
|
||||
form.value.enterpriseAddress = ocrData.address || ''
|
||||
|
||||
// 如果后端返回了已保存的营业执照图片URL,可以直接写入
|
||||
if (ocrData.license_image_url) {
|
||||
form.value.businessLicenseImageURL = ocrData.license_image_url
|
||||
}
|
||||
|
||||
ocrResult.value = true
|
||||
ElMessage.success('营业执照识别成功,已自动填充表单')
|
||||
} else {
|
||||
@@ -432,8 +680,211 @@ const handleFileChange = async (file) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否为前端 blob 预览地址(需上传到服务器后替换为真实 URL)
|
||||
const isBlobUrl = (url) => typeof url === 'string' && url.startsWith('blob:')
|
||||
|
||||
// 上传单张图片到七牛云,返回可访问 URL
|
||||
const uploadFileToServer = async (file) => {
|
||||
const res = await certificationApi.uploadFile(file)
|
||||
if (!res?.success || !res?.data?.url) {
|
||||
throw new Error(res?.error?.message || '图片上传失败')
|
||||
}
|
||||
return res.data.url
|
||||
}
|
||||
|
||||
// 提交前将 blob 图片全部上传到七牛云,并更新表单中的 URL
|
||||
const uploadAllBlobFilesAndFillForm = async () => {
|
||||
const tasks = []
|
||||
|
||||
// 营业执照:若当前是 blob 则上传
|
||||
if (isBlobUrl(form.value.businessLicenseImageURL) && businessLicenseFileList.value.length > 0 && businessLicenseFileList.value[0].raw) {
|
||||
tasks.push(
|
||||
uploadFileToServer(businessLicenseFileList.value[0].raw).then((url) => {
|
||||
form.value.businessLicenseImageURL = url
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// 办公场地多图:按顺序上传
|
||||
if (officePlaceFileList.value.length > 0) {
|
||||
const files = officePlaceFileList.value.filter((f) => f.raw).map((f) => f.raw)
|
||||
if (files.length > 0) {
|
||||
tasks.push(
|
||||
Promise.all(files.map((f) => uploadFileToServer(f))).then((urls) => {
|
||||
form.value.officePlaceImageURLs = urls
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用场景附件多图:按顺序上传
|
||||
if (scenarioFileList.value.length > 0) {
|
||||
const files = scenarioFileList.value.filter((f) => f.raw).map((f) => f.raw)
|
||||
if (files.length > 0) {
|
||||
tasks.push(
|
||||
Promise.all(files.map((f) => uploadFileToServer(f))).then((urls) => {
|
||||
form.value.scenarioAttachmentURLs = urls
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 授权代表身份证正反面:人像面 + 国徽面
|
||||
const frontRaw = authorizedRepIDFrontFileList.value[0]?.raw
|
||||
const backRaw = authorizedRepIDBackFileList.value[0]?.raw
|
||||
if (frontRaw || backRaw) {
|
||||
tasks.push(
|
||||
(async () => {
|
||||
const urls = []
|
||||
if (frontRaw) urls.push(await uploadFileToServer(frontRaw))
|
||||
if (backRaw) urls.push(await uploadFileToServer(backRaw))
|
||||
form.value.authorizedRepIDImageURLs = urls
|
||||
})()
|
||||
)
|
||||
}
|
||||
|
||||
await Promise.all(tasks)
|
||||
}
|
||||
|
||||
// 从 el-upload 的 fileList 中提取 URL 数组(后端接好上传接口后可用 response.url)
|
||||
const extractUrls = (fileList) => {
|
||||
return fileList
|
||||
.map(f => f.url || f.response?.url || f.name)
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
// 营业执照图片变更(同时触发 OCR 识别)
|
||||
const handleBusinessLicenseChange = async (file, fileList) => {
|
||||
businessLicenseFileList.value = fileList
|
||||
const urls = extractUrls(fileList)
|
||||
form.value.businessLicenseImageURL = urls[0] || ''
|
||||
|
||||
// 使用当前选择的营业执照图片触发 OCR 识别
|
||||
if (file && file.raw) {
|
||||
await handleFileChange(file)
|
||||
}
|
||||
}
|
||||
|
||||
const handleBusinessLicenseRemove = (file, fileList) => {
|
||||
businessLicenseFileList.value = fileList
|
||||
const urls = extractUrls(fileList)
|
||||
form.value.businessLicenseImageURL = urls[0] || ''
|
||||
}
|
||||
|
||||
// 手动清除营业执照图片(预览区域中的“删除”按钮)
|
||||
const clearBusinessLicense = () => {
|
||||
businessLicenseFileList.value = []
|
||||
form.value.businessLicenseImageURL = ''
|
||||
}
|
||||
|
||||
// 授权代表身份证人像面图片变更
|
||||
const handleAuthorizedRepIDFrontChange = (file, fileList) => {
|
||||
authorizedRepIDFrontFileList.value = fileList
|
||||
updateAuthorizedRepIDImageURLs()
|
||||
}
|
||||
|
||||
const handleAuthorizedRepIDFrontRemove = (file, fileList) => {
|
||||
authorizedRepIDFrontFileList.value = fileList
|
||||
updateAuthorizedRepIDImageURLs()
|
||||
}
|
||||
|
||||
// 授权代表身份证国徽面图片变更
|
||||
const handleAuthorizedRepIDBackChange = (file, fileList) => {
|
||||
authorizedRepIDBackFileList.value = fileList
|
||||
updateAuthorizedRepIDImageURLs()
|
||||
}
|
||||
|
||||
const handleAuthorizedRepIDBackRemove = (file, fileList) => {
|
||||
authorizedRepIDBackFileList.value = fileList
|
||||
updateAuthorizedRepIDImageURLs()
|
||||
}
|
||||
|
||||
// 手动清除授权代表身份证人像面
|
||||
const clearAuthorizedRepFront = () => {
|
||||
authorizedRepIDFrontFileList.value = []
|
||||
updateAuthorizedRepIDImageURLs()
|
||||
}
|
||||
|
||||
// 手动清除授权代表身份证国徽面
|
||||
const clearAuthorizedRepBack = () => {
|
||||
authorizedRepIDBackFileList.value = []
|
||||
updateAuthorizedRepIDImageURLs()
|
||||
}
|
||||
|
||||
// 汇总授权代表身份证正反面图片URL到一个数组字段
|
||||
const updateAuthorizedRepIDImageURLs = () => {
|
||||
const frontUrl = extractUrls(authorizedRepIDFrontFileList.value)[0] || ''
|
||||
const backUrl = extractUrls(authorizedRepIDBackFileList.value)[0] || ''
|
||||
const urls = []
|
||||
if (frontUrl) urls.push(frontUrl)
|
||||
if (backUrl) urls.push(backUrl)
|
||||
form.value.authorizedRepIDImageURLs = urls
|
||||
}
|
||||
|
||||
// 办公场地:拖放文件时通过 handleStart 加入列表
|
||||
const onOfficePlaceDrop = (e) => {
|
||||
officePlaceDragover.value = false
|
||||
const upload = officePlaceUploadRef.value
|
||||
if (!upload) return
|
||||
const files = Array.from(e.dataTransfer?.files || []).filter(
|
||||
(f) => f.type && /^image\/(jpeg|jpg|png|webp)$/i.test(f.type)
|
||||
)
|
||||
const limit = 10
|
||||
const remain = Math.max(0, limit - officePlaceFileList.value.length)
|
||||
const toAdd = files.slice(0, remain)
|
||||
for (const file of toAdd) {
|
||||
if (!beforeUpload(file)) continue
|
||||
if (typeof file.uid === 'undefined') file.uid = genFileId()
|
||||
upload.handleStart(file)
|
||||
}
|
||||
if (files.length > remain && remain > 0) ElMessage.warning(`最多上传 ${limit} 张,已忽略多余文件`)
|
||||
}
|
||||
|
||||
// 应用场景:拖放文件时通过 handleStart 加入列表
|
||||
const onScenarioDrop = (e) => {
|
||||
scenarioDragover.value = false
|
||||
const upload = scenarioUploadRef.value
|
||||
if (!upload) return
|
||||
const files = Array.from(e.dataTransfer?.files || []).filter(
|
||||
(f) => f.type && /^image\/(jpeg|jpg|png|webp)$/i.test(f.type)
|
||||
)
|
||||
const limit = 10
|
||||
const remain = Math.max(0, limit - scenarioFileList.value.length)
|
||||
const toAdd = files.slice(0, remain)
|
||||
for (const file of toAdd) {
|
||||
if (!beforeUpload(file)) continue
|
||||
if (typeof file.uid === 'undefined') file.uid = genFileId()
|
||||
upload.handleStart(file)
|
||||
}
|
||||
if (files.length > remain && remain > 0) ElMessage.warning(`最多上传 ${limit} 张,已忽略多余文件`)
|
||||
}
|
||||
|
||||
// 办公场地图片变更
|
||||
const handleOfficePlaceChange = (file, fileList) => {
|
||||
officePlaceFileList.value = fileList
|
||||
form.value.officePlaceImageURLs = extractUrls(fileList)
|
||||
}
|
||||
|
||||
const handleOfficePlaceRemove = (file, fileList) => {
|
||||
officePlaceFileList.value = fileList
|
||||
form.value.officePlaceImageURLs = extractUrls(fileList)
|
||||
}
|
||||
|
||||
// 应用场景附件图片变更
|
||||
const handleScenarioChange = (file, fileList) => {
|
||||
scenarioFileList.value = fileList
|
||||
form.value.scenarioAttachmentURLs = extractUrls(fileList)
|
||||
}
|
||||
|
||||
const handleScenarioRemove = (file, fileList) => {
|
||||
scenarioFileList.value = fileList
|
||||
form.value.scenarioAttachmentURLs = extractUrls(fileList)
|
||||
}
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
if (submitting.value) return
|
||||
submitting.value = true
|
||||
try {
|
||||
await enterpriseFormRef.value.validate()
|
||||
|
||||
@@ -450,12 +901,42 @@ const submitForm = async () => {
|
||||
}
|
||||
)
|
||||
|
||||
submitting.value = true
|
||||
// 先将所有 blob 图片上传到七牛云,再提交企业信息
|
||||
try {
|
||||
await uploadAllBlobFilesAndFillForm()
|
||||
} catch (err) {
|
||||
ElMessage.error(err?.message || '图片上传失败,请重试')
|
||||
submitting.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// Mock API 调用
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
// 调用后端提交接口
|
||||
const payload = {
|
||||
company_name: form.value.companyName,
|
||||
unified_social_code: form.value.unifiedSocialCode,
|
||||
legal_person_name: form.value.legalPersonName,
|
||||
legal_person_id: form.value.legalPersonID,
|
||||
legal_person_phone: form.value.legalPersonPhone,
|
||||
enterprise_address: form.value.enterpriseAddress,
|
||||
verification_code: form.value.legalPersonCode,
|
||||
// 扩展字段
|
||||
business_license_image_url: form.value.businessLicenseImageURL,
|
||||
office_place_image_urls: form.value.officePlaceImageURLs,
|
||||
api_usage: form.value.apiUsage,
|
||||
scenario_attachment_urls: form.value.scenarioAttachmentURLs,
|
||||
// 授权代表信息
|
||||
authorized_rep_name: form.value.authorizedRepName,
|
||||
authorized_rep_id: form.value.authorizedRepID,
|
||||
authorized_rep_phone: form.value.authorizedRepPhone,
|
||||
authorized_rep_id_image_urls: form.value.authorizedRepIDImageURLs
|
||||
}
|
||||
|
||||
emit('submit', form.value)
|
||||
const res = await certificationApi.submitEnterpriseInfo(payload)
|
||||
if (!res.success) {
|
||||
throw new Error(res?.error?.message || '提交企业信息失败')
|
||||
}
|
||||
|
||||
emit('submit', { formData: form.value, response: res })
|
||||
|
||||
} catch (error) {
|
||||
// 用户点击取消或关闭对话框,不处理
|
||||
@@ -609,6 +1090,13 @@ onUnmounted(() => {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.section-desc {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
margin: 0 0 20px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 表单输入框 */
|
||||
.form-input :deep(.el-input__wrapper) {
|
||||
border-radius: 8px;
|
||||
@@ -633,6 +1121,45 @@ onUnmounted(() => {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
/* 拖放区域:包裹 picture-card 上传,支持拖拽图片到整块区域 */
|
||||
.upload-drop-zone {
|
||||
width: 100%;
|
||||
border-radius: 8px;
|
||||
transition: background 0.2s, border-color 0.2s;
|
||||
}
|
||||
.upload-drop-zone.is-dragover {
|
||||
background: var(--el-color-primary-light-9, #ecf5ff);
|
||||
outline: 2px dashed var(--el-color-primary);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
/* 上传区域基础样式 */
|
||||
.upload-area {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 保证 picture-card 触发区域整块可点击、可拖拽 */
|
||||
.upload-trigger-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 148px;
|
||||
cursor: pointer;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.upload-icon {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
/* 当已有一张图片时,隐藏单图上传的“+ 选择文件”入口 */
|
||||
.single-upload-area :deep(.el-upload-list__item + .el-upload--picture-card) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 验证码按钮 */
|
||||
.code-btn {
|
||||
min-width: 100px;
|
||||
|
||||
100
src/pages/certification/components/ManualReviewPending.vue
Normal file
100
src/pages/certification/components/ManualReviewPending.vue
Normal file
@@ -0,0 +1,100 @@
|
||||
<template>
|
||||
<el-card class="step-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<div class="header-icon">
|
||||
<el-icon class="text-amber-600">
|
||||
<ClockIcon />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="header-content">
|
||||
<h2 class="header-title">人工审核</h2>
|
||||
<p class="header-subtitle">您的企业信息已提交,请等待管理员审核</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="manual-review-content">
|
||||
<div class="review-status-box">
|
||||
<el-icon class="status-icon"><ClockIcon /></el-icon>
|
||||
<p class="status-text">我们正在审核您提交的企业信息,请耐心等待。</p>
|
||||
<p v-if="submitTime" class="submit-time">提交时间:{{ submitTime }}</p>
|
||||
<p v-if="companyName" class="company-name">企业名称:{{ companyName }}</p>
|
||||
</div>
|
||||
<el-button type="primary" class="refresh-btn" :loading="refreshing" @click="handleRefresh">
|
||||
刷新状态
|
||||
</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ClockIcon } from '@heroicons/vue/24/outline'
|
||||
|
||||
defineProps({
|
||||
certificationData: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
submitTime: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
companyName: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const refreshing = ref(false)
|
||||
|
||||
const handleRefresh = async () => {
|
||||
refreshing.value = true
|
||||
try {
|
||||
emit('refresh')
|
||||
} finally {
|
||||
refreshing.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.manual-review-content {
|
||||
padding: 24px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.review-status-box {
|
||||
background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%);
|
||||
border: 1px solid rgba(245, 158, 11, 0.3);
|
||||
border-radius: 12px;
|
||||
padding: 32px 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
font-size: 48px;
|
||||
color: #d97706;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 16px;
|
||||
color: #92400e;
|
||||
margin: 0 0 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.submit-time,
|
||||
.company-name {
|
||||
font-size: 14px;
|
||||
color: #b45309;
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.refresh-btn {
|
||||
min-width: 120px;
|
||||
}
|
||||
</style>
|
||||
@@ -68,6 +68,14 @@
|
||||
@submit="handleEnterpriseSubmit"
|
||||
/>
|
||||
|
||||
<ManualReviewPending
|
||||
v-if="currentStep === 'manual_review'"
|
||||
:certification-data="certificationData"
|
||||
:submit-time="manualReviewSubmitTime"
|
||||
:company-name="enterpriseForm.companyName"
|
||||
@refresh="getCertificationDetails"
|
||||
/>
|
||||
|
||||
<EnterpriseVerify
|
||||
v-if="currentStep === 'enterprise_verify'"
|
||||
:enterprise-data="enterpriseForm"
|
||||
@@ -120,6 +128,7 @@ import { useUserStore } from '@/stores/user'
|
||||
import {
|
||||
BuildingOfficeIcon,
|
||||
CheckCircleIcon,
|
||||
ClockIcon,
|
||||
CodeBracketIcon,
|
||||
DocumentTextIcon,
|
||||
UserIcon,
|
||||
@@ -133,6 +142,7 @@ import ContractRejected from './components/ContractRejected.vue'
|
||||
import ContractSign from './components/ContractSign.vue'
|
||||
import EnterpriseInfo from './components/EnterpriseInfo.vue'
|
||||
import EnterpriseVerify from './components/EnterpriseVerify.vue'
|
||||
import ManualReviewPending from './components/ManualReviewPending.vue'
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
// 认证步骤配置
|
||||
@@ -143,6 +153,12 @@ const certificationSteps = [
|
||||
description: '填写企业基本信息和法人信息',
|
||||
icon: BuildingOfficeIcon,
|
||||
},
|
||||
{
|
||||
key: 'manual_review',
|
||||
title: '人工审核',
|
||||
description: '等待管理员审核企业信息',
|
||||
icon: ClockIcon,
|
||||
},
|
||||
{
|
||||
key: 'enterprise_verify',
|
||||
title: '企业认证',
|
||||
@@ -176,6 +192,18 @@ const currentStepIndex = computed(() => {
|
||||
// 步骤特定元数据
|
||||
const stepMeta = ref({}) // 用于存储当前步骤的metadata
|
||||
|
||||
// 人工审核步骤的提交时间展示
|
||||
const manualReviewSubmitTime = computed(() => {
|
||||
const at = certificationData.value?.metadata?.enterprise_info?.submit_at ?? certificationData.value?.info_submitted_at
|
||||
if (!at) return ''
|
||||
try {
|
||||
const d = new Date(at)
|
||||
return Number.isNaN(d.getTime()) ? '' : d.toLocaleString('zh-CN')
|
||||
} catch {
|
||||
return ''
|
||||
}
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const enterpriseForm = ref({
|
||||
companyName: '',
|
||||
@@ -188,35 +216,32 @@ const enterpriseForm = ref({
|
||||
enterpriseEmail: '',
|
||||
})
|
||||
|
||||
// 开发模式控制
|
||||
//
|
||||
const isDevelopment = ref(false)
|
||||
const devCurrentStep = ref('enterprise_info')
|
||||
|
||||
// 合同签署加载状态
|
||||
const contractSignLoading = ref(false)
|
||||
|
||||
// 事件处理
|
||||
const handleEnterpriseSubmit = async (formData) => {
|
||||
// 事件处理:优先用提交接口返回的认证数据更新步骤,确保进入「人工审核」页,避免依赖二次请求
|
||||
const handleEnterpriseSubmit = async (payload) => {
|
||||
try {
|
||||
loading.value = true
|
||||
// 字段映射
|
||||
const payload = {
|
||||
company_name: formData.companyName,
|
||||
unified_social_code: formData.unifiedSocialCode,
|
||||
legal_person_name: formData.legalPersonName,
|
||||
legal_person_id: formData.legalPersonID,
|
||||
legal_person_phone: formData.legalPersonPhone,
|
||||
enterprise_address: formData.enterpriseAddress,
|
||||
enterprise_email: formData.enterpriseEmail,
|
||||
verification_code: formData.legalPersonCode,
|
||||
const nextAction = payload?.response?.data?.metadata?.next_action
|
||||
if (nextAction) {
|
||||
ElMessage.success(nextAction)
|
||||
} else {
|
||||
ElMessage.success('企业信息提交成功,请等待管理员审核')
|
||||
}
|
||||
if (payload?.response?.data?.status) {
|
||||
certificationData.value = payload.response.data
|
||||
stepMeta.value = payload.response.data?.metadata || {}
|
||||
await setCurrentStepByStatus()
|
||||
} else {
|
||||
await getCertificationDetails()
|
||||
}
|
||||
await certificationApi.submitEnterpriseInfo(payload)
|
||||
ElMessage.success('企业信息提交成功')
|
||||
// 提交成功后刷新认证详情
|
||||
await getCertificationDetails()
|
||||
} catch (error) {
|
||||
ElMessage.error(error?.message || '提交失败,请检查表单信息')
|
||||
// 提交失败时不刷新认证详情,保持用户填写的信息
|
||||
ElMessage.error(error?.message || '获取认证状态失败,请刷新页面')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -355,6 +380,9 @@ const setCurrentStepByStatus = async () => {
|
||||
case 'pending':
|
||||
currentStep.value = 'enterprise_info'
|
||||
break
|
||||
case 'info_pending_review':
|
||||
currentStep.value = 'manual_review'
|
||||
break
|
||||
case 'info_submitted':
|
||||
currentStep.value = 'enterprise_verify'
|
||||
break
|
||||
|
||||
@@ -304,6 +304,12 @@ const routes = [
|
||||
name: 'AdminPurchaseRecords',
|
||||
component: () => import('@/pages/admin/purchase-records/index.vue'),
|
||||
meta: { title: '购买记录管理' }
|
||||
},
|
||||
{
|
||||
path: 'certification-reviews',
|
||||
name: 'AdminCertificationReviews',
|
||||
component: () => import('@/pages/admin/certification-reviews/index.vue'),
|
||||
meta: { title: '企业审核' }
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user