Compare commits

...

4 Commits

Author SHA1 Message Date
Mrx
a7cd16848c f 2026-04-08 17:16:44 +08:00
Mrx
33dc672440 f 2026-04-06 17:34:42 +08:00
Mrx
7c2f828222 fadd 2026-04-02 14:01:29 +08:00
Mrx
d3dde79474 fadd 2026-04-02 12:47:02 +08:00
3 changed files with 193 additions and 47 deletions

View File

@@ -43,6 +43,7 @@
</div>
<el-table
v-if="!isMobile"
v-loading="loading"
:data="list"
stripe
@@ -76,6 +77,31 @@
</el-table-column>
</el-table>
<div v-else v-loading="loading" class="mobile-list">
<div v-if="list.length" class="mobile-card-list">
<article v-for="row in list" :key="row.id" class="mobile-card">
<div class="mobile-card-header">
<div class="mobile-company">{{ row.company_name || '-' }}</div>
<el-tag :type="statusTagType(row)" size="small">
{{ certificationStatusDisplay(row?.certification_status) }}
</el-tag>
</div>
<div class="mobile-card-info"><span>提交时间</span>{{ formatDate(row.submit_at) }}</div>
<div class="mobile-card-info"><span>法人</span>{{ row.legal_person_name || '-' }}</div>
<div class="mobile-card-info"><span>手机号</span>{{ row.legal_person_phone || '-' }}</div>
<div class="mobile-card-info"><span>统一代码</span>{{ row.unified_social_code || '-' }}</div>
<div class="mobile-card-actions">
<el-button type="primary" plain size="small" @click="openDetail(row.id)">查看详情</el-button>
<template v-if="canShowApproveReject(row)">
<el-button type="success" plain size="small" @click="handleApprove(row)">通过</el-button>
<el-button type="danger" plain size="small" @click="handleReject(row)">拒绝</el-button>
</template>
</div>
</article>
</div>
<el-empty v-else description="暂无数据" />
</div>
<div class="pagination-wrap">
<el-pagination
v-model:current-page="page"
@@ -92,7 +118,7 @@
<el-drawer
v-model="drawerVisible"
title="企业信息详情"
size="560"
:size="isMobile ? '100%' : '560px'"
direction="rtl"
:close-on-click-modal="false"
>
@@ -200,7 +226,7 @@
</el-drawer>
<!-- 通过弹窗 -->
<el-dialog v-model="approveDialogVisible" title="审核通过" width="400px">
<el-dialog v-model="approveDialogVisible" title="审核通过" :width="isMobile ? '92%' : '400px'">
<el-form label-width="80">
<el-form-item label="审核备注">
<el-input v-model="approveRemark" type="textarea" :rows="3" placeholder="选填" />
@@ -213,7 +239,7 @@
</el-dialog>
<!-- 拒绝弹窗 -->
<el-dialog v-model="rejectDialogVisible" title="审核拒绝" width="400px">
<el-dialog v-model="rejectDialogVisible" title="审核拒绝" :width="isMobile ? '92%' : '400px'">
<el-form label-width="80">
<el-form-item label="拒绝原因" required>
<el-input v-model="rejectRemark" type="textarea" :rows="3" placeholder="必填" />
@@ -250,6 +276,12 @@ const rejectRemark = ref('')
const actionLoading = ref(false)
const pendingRecordId = ref('')
const pendingUserId = ref('')
const isMobile = ref(false)
function updateMobileState() {
if (typeof window === 'undefined') return
isMobile.value = window.innerWidth <= 768
}
function formatDate(val) {
if (!val) return '-'
@@ -297,9 +329,14 @@ function statusTagType(row) {
return m[status] || 'info'
}
/**
* 是否展示通过/拒绝:须当前认证为待审核,且本条提交记录为「校验通过」(verified)。
* 历史失败记录 (failed) 与认证状态无关字段共用同一 certification_status否则会误显按钮。
*/
function canShowApproveReject(row) {
if (!row) return false
return row.certification_status === 'info_pending_review'
if (row.certification_status !== 'info_pending_review') return false
return row.status === 'verified'
}
// 判断是否可作为图片展示(含七牛云等无扩展名的 CDN URL
@@ -448,8 +485,14 @@ async function confirmReject() {
}
onMounted(() => {
updateMobileState()
window.addEventListener('resize', updateMobileState)
loadList()
})
onBeforeUnmount(() => {
window.removeEventListener('resize', updateMobileState)
})
</script>
<style scoped>
@@ -472,6 +515,8 @@ onMounted(() => {
}
.toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
@@ -480,6 +525,48 @@ onMounted(() => {
display: flex;
justify-content: flex-end;
}
.mobile-list {
min-height: 200px;
}
.mobile-card-list {
display: grid;
gap: 12px;
}
.mobile-card {
border: 1px solid #e2e8f0;
border-radius: 10px;
padding: 12px;
background: #fff;
}
.mobile-card-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 8px;
margin-bottom: 10px;
}
.mobile-company {
font-size: 15px;
font-weight: 600;
color: #1e293b;
word-break: break-word;
}
.mobile-card-info {
color: #475569;
font-size: 13px;
line-height: 1.5;
margin-bottom: 4px;
word-break: break-all;
}
.mobile-card-info span {
color: #64748b;
}
.mobile-card-actions {
margin-top: 10px;
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.drawer-title {
margin-right: 12px;
}
@@ -579,4 +666,37 @@ onMounted(() => {
height: 100%;
object-fit: cover;
}
@media (max-width: 768px) {
.certification-reviews-page {
padding: 12px;
}
.page-title {
font-size: 18px;
}
.page-subtitle {
font-size: 13px;
}
.toolbar > * {
width: 100% !important;
}
.pagination-wrap {
justify-content: center;
}
.detail-content {
padding-right: 0;
}
.drawer-actions {
gap: 6px;
}
.detail-dl {
grid-template-columns: 88px 1fr;
gap: 6px 10px;
}
.image-link {
width: 88px;
height: 88px;
line-height: 88px;
}
}
</style>

View File

@@ -119,9 +119,9 @@
</el-col>
</el-row>
<!-- 暂时隐藏办公场地图片上传 -->
<!-- <el-row :gutter="16" class="mb-4">
<el-row :gutter="16" class="mb-4">
<el-col :span="24">
<el-form-item label="办公场地照片">
<el-form-item label="办公场地照片" prop="officePlaceImageURLs">
<div class="text-xs mb-1 text-blue-500">
请在非 IE 浏览器下上传大小不超过 1M 的图片最多 10 需体现门楣 LOGO办公设备与工作人员
</div>
@@ -146,7 +146,7 @@
</el-upload>
</el-form-item>
</el-col>
</el-row> -->
</el-row>
<el-row :gutter="16" class="mb-4">
@@ -201,7 +201,7 @@
<!-- 暂时隐藏授权代表信息姓名身份证号手机号授权代表身份证 -->
<!-- <h3 class="section-title">授权代表信息</h3>
<h3 class="section-title">授权代表信息</h3>
<p class="section-desc">授权代表信息用于证明该人员已获得企业授权请确保姓名身份证号手机号及身份证正反面照片真实有效</p>
<el-row :gutter="16" class="mb-4">
@@ -278,10 +278,10 @@
</el-upload>
</el-form-item>
</el-col>
</el-row> -->
</el-row>
<!-- 暂时隐藏应用场景说明应用场景附件 -->
<!-- <h3 class="section-title">应用场景填写</h3>
<h3 class="section-title">应用场景填写</h3>
<p class="section-desc">请描述您调用接口的具体业务场景</p>
<el-row :gutter="16" class="mb-4">
@@ -303,7 +303,7 @@
<el-row :gutter="16" class="mb-4">
<el-col :span="24">
<el-form-item label="应用场景附件">
<el-form-item label="应用场景附件" prop="scenarioAttachmentURLs">
<div class="text-xs mb-1 text-blue-500">
请在非 IE 浏览器下上传大小不超过 1M 的图片最多 10 张后台应用截图
</div>
@@ -328,7 +328,7 @@
</el-upload>
</el-form-item>
</el-col>
</el-row> -->
</el-row>
</div>
<div class="form-actions">
@@ -493,6 +493,24 @@ const validatePhone = (rule, value, callback) => {
callback()
}
// 数组字段必填验证(至少上传/选择一项)
const validateRequiredArray = (message) => (rule, value, callback) => {
if (!Array.isArray(value) || value.length === 0) {
callback(new Error(message))
return
}
callback()
}
// 授权代表身份证要求:正反面都必须上传
const validateAuthorizedRepIDImages = (rule, value, callback) => {
if (!Array.isArray(value) || value.length < 2) {
callback(new Error('请上传授权代表身份证正反面图片'))
return
}
callback()
}
// 表单验证规则
const enterpriseRules = {
companyName: [
@@ -527,26 +545,32 @@ const enterpriseRules = {
businessLicenseImageURL: [
{ required: true, message: '请上传营业执照图片', trigger: 'change' }
],
officePlaceImageURLs: [
{ validator: validateRequiredArray('请上传办公场地照片'), 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' }
// ]
apiUsage: [
{ required: true, message: '请填写接口用途', trigger: 'blur' },
{ min: 5, max: 500, message: '接口用途长度应在5-500个字符之间', trigger: 'blur' }
],
scenarioAttachmentURLs: [
{ validator: validateRequiredArray('请上传应用场景附件'), trigger: 'change' }
],
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: [
{ validator: validateAuthorizedRepIDImages, trigger: 'change' }
]
}
// 监听props变化
@@ -698,10 +722,10 @@ const syncFormUrlsAndCheckReady = () => {
const hasUnfinished = (list) => list.some((f) => f.raw && !f.response?.url)
if (hasUploading(businessLicenseFileList.value) || hasUnfinished(businessLicenseFileList.value)) return false
// 以下上传项已暂时隐藏,不再参与“未上传完成”的拦截
// if (hasUploading(officePlaceFileList.value) || hasUnfinished(officePlaceFileList.value)) return false
// if (hasUploading(scenarioFileList.value) || hasUnfinished(scenarioFileList.value)) return false
// if (hasUploading(authorizedRepIDFrontFileList.value) || hasUnfinished(authorizedRepIDFrontFileList.value)) return false
// if (hasUploading(authorizedRepIDBackFileList.value) || hasUnfinished(authorizedRepIDBackFileList.value)) return false
if (hasUploading(officePlaceFileList.value) || hasUnfinished(officePlaceFileList.value)) return false
if (hasUploading(scenarioFileList.value) || hasUnfinished(scenarioFileList.value)) return false
if (hasUploading(authorizedRepIDFrontFileList.value) || hasUnfinished(authorizedRepIDFrontFileList.value)) return false
if (hasUploading(authorizedRepIDBackFileList.value) || hasUnfinished(authorizedRepIDBackFileList.value)) return false
return true
}
@@ -798,6 +822,7 @@ const updateAuthorizedRepIDImageURLs = () => {
if (frontUrl) urls.push(frontUrl)
if (backUrl) urls.push(backUrl)
form.value.authorizedRepIDImageURLs = urls
enterpriseFormRef.value?.validateField('authorizedRepIDImageURLs')
}
// 办公场地图片变更:选择即上传
@@ -807,11 +832,13 @@ const handleOfficePlaceChange = async (file, fileList) => {
await uploadFileOnceSelected(file)
}
form.value.officePlaceImageURLs = extractUrls(fileList)
enterpriseFormRef.value?.validateField('officePlaceImageURLs')
}
const handleOfficePlaceRemove = (file, fileList) => {
officePlaceFileList.value = fileList
form.value.officePlaceImageURLs = extractUrls(fileList)
enterpriseFormRef.value?.validateField('officePlaceImageURLs')
}
// 应用场景附件图片变更:选择即上传
@@ -821,11 +848,13 @@ const handleScenarioChange = async (file, fileList) => {
await uploadFileOnceSelected(file)
}
form.value.scenarioAttachmentURLs = extractUrls(fileList)
enterpriseFormRef.value?.validateField('scenarioAttachmentURLs')
}
const handleScenarioRemove = (file, fileList) => {
scenarioFileList.value = fileList
form.value.scenarioAttachmentURLs = extractUrls(fileList)
enterpriseFormRef.value?.validateField('scenarioAttachmentURLs')
}
// 提交表单

View File

@@ -68,14 +68,13 @@
@submit="handleEnterpriseSubmit"
/>
<!-- 暂时隐藏第二步人工审核 -->
<!-- <ManualReviewPending
<ManualReviewPending
v-if="currentStep === 'manual_review'"
:certification-data="certificationData"
:submit-time="manualReviewSubmitTime"
:company-name="enterpriseForm.companyName"
@refresh="getCertificationDetails"
/> -->
/>
<EnterpriseVerify
v-if="currentStep === 'enterprise_verify'"
@@ -146,7 +145,6 @@ import EnterpriseVerify from './components/EnterpriseVerify.vue'
import ManualReviewPending from './components/ManualReviewPending.vue'
const router = useRouter()
const userStore = useUserStore()
// 认证步骤配置(暂时隐藏第二步「人工审核」,恢复时取消注释 manual_review 并还原 setCurrentStepByStatus 中 info_pending_review 分支)
const certificationSteps = [
{
key: 'enterprise_info',
@@ -154,12 +152,12 @@ const certificationSteps = [
description: '填写企业基本信息和法人信息',
icon: BuildingOfficeIcon,
},
// {
// key: 'manual_review',
// title: '人工审核',
// description: '等待管理员审核企业信息',
// icon: ClockIcon,
// },
{
key: 'manual_review',
title: '人工审核',
description: '等待管理员审核企业信息',
icon: ClockIcon,
},
{
key: 'enterprise_verify',
title: '企业认证',
@@ -416,8 +414,7 @@ const setCurrentStepByStatus = async () => {
currentStep.value = 'enterprise_info'
break
case 'info_pending_review':
// 暂时跳过人工审核展示,直接进入企业认证步骤
currentStep.value = 'enterprise_verify'
currentStep.value = 'manual_review'
break
case 'info_submitted':
currentStep.value = 'enterprise_verify'