优化
This commit is contained in:
@@ -80,6 +80,14 @@ const reviewLoadError = ref<ApiErrorDetails | null>(null)
|
||||
const selectedReview = ref<EnterpriseReview | null>(null)
|
||||
const decisionOpen = ref(false)
|
||||
const reviewSubmitting = ref(false)
|
||||
const reviewQualifications = ref<PlatformTenantQualification[]>([])
|
||||
const reviewQualificationsLoading = ref(false)
|
||||
const reviewQualificationsError = ref<ApiErrorDetails | null>(null)
|
||||
const reviewPreviewOpen = ref(false)
|
||||
const reviewPreviewName = ref('')
|
||||
const reviewPreviewUrl = ref('')
|
||||
const reviewPreviewKind = ref<'image' | 'pdf'>('image')
|
||||
const reviewDocumentLoading = ref('')
|
||||
const approvalResultOpen = ref(false)
|
||||
const approvalResult = ref<EnterpriseApprovalResponse | null>(null)
|
||||
const credentialSource = ref<'creation' | 'approval'>('approval')
|
||||
@@ -252,6 +260,12 @@ const impersonationRules: FormRules = {
|
||||
}
|
||||
function displayText(value?: string | null) { return value?.trim() || '—' }
|
||||
function formatDate(value?: string | null) { return value ? value.replace('T', ' ').slice(0, 16) : '—' }
|
||||
function qualificationLabel(type?: string | null) {
|
||||
return ({ BUSINESS_LICENSE: '营业执照', LEGAL_PERSON_ID: '法人身份证', FREIGHT_FORWARDER_CERTIFICATE: '货代资质证书', BANK_ACCOUNT_LICENSE: '银行开户许可证' } as Record<string, string>)[type || ''] || type || '企业资质'
|
||||
}
|
||||
function qualificationName(item: PlatformTenantQualification) {
|
||||
try { return qualificationFileName(item.documentUrl) } catch { return qualificationLabel(item.qualificationType) }
|
||||
}
|
||||
function statusMeta(status: PlatformTenantStatus) {
|
||||
if (status === 'ACTIVE') return { label: '启用', type: 'success' as const }
|
||||
if (status === 'PROFILE_REQUIRED') return { label: '待完善', type: 'info' as const }
|
||||
@@ -819,7 +833,7 @@ async function submitArchive() {
|
||||
}
|
||||
}
|
||||
|
||||
function openDecision(row: PlatformTenantResource, type: 'approve' | 'reject') {
|
||||
async function openDecision(row: PlatformTenantResource, type: 'approve' | 'reject') {
|
||||
if (reviewSubmitting.value) return
|
||||
const review = pendingReviewByTenantId.value.get(String(row.id))
|
||||
if (!review) {
|
||||
@@ -830,9 +844,60 @@ function openDecision(row: PlatformTenantResource, type: 'approve' | 'reject') {
|
||||
decision.type = type
|
||||
decision.comment = type === 'approve' ? '企业资料与必填资质完整、清晰,审核通过' : ''
|
||||
decisionOpen.value = true
|
||||
await loadReviewQualifications()
|
||||
void nextTick(() => decisionFormRef.value?.clearValidate('comment'))
|
||||
}
|
||||
|
||||
async function loadReviewQualifications() {
|
||||
const review = selectedReview.value
|
||||
if (!review) return
|
||||
reviewQualifications.value = []
|
||||
reviewQualificationsError.value = null
|
||||
reviewQualificationsLoading.value = true
|
||||
try {
|
||||
reviewQualifications.value = await listTenantQualifications(review.tenantId)
|
||||
} catch (error) {
|
||||
reviewQualificationsError.value = getApiErrorDetails(error, '客户资质加载失败,请重试后再审核')
|
||||
} finally {
|
||||
reviewQualificationsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function clearReviewPreview() {
|
||||
if (reviewPreviewUrl.value) URL.revokeObjectURL(reviewPreviewUrl.value)
|
||||
reviewPreviewOpen.value = false
|
||||
reviewPreviewUrl.value = ''
|
||||
reviewPreviewName.value = ''
|
||||
}
|
||||
|
||||
async function loadReviewDocument(item: PlatformTenantQualification, download: boolean) {
|
||||
const review = selectedReview.value
|
||||
if (!review || reviewDocumentLoading.value) return
|
||||
const fileName = qualificationName(item)
|
||||
reviewDocumentLoading.value = String(item.id)
|
||||
try {
|
||||
const blob = await downloadTenantQualification(review.tenantId, item.documentUrl)
|
||||
const url = URL.createObjectURL(blob)
|
||||
if (download) {
|
||||
const anchor = document.createElement('a')
|
||||
anchor.href = url
|
||||
anchor.download = fileName
|
||||
anchor.click()
|
||||
URL.revokeObjectURL(url)
|
||||
return
|
||||
}
|
||||
clearReviewPreview()
|
||||
reviewPreviewName.value = fileName
|
||||
reviewPreviewKind.value = blob.type === 'application/pdf' || fileName.toLowerCase().endsWith('.pdf') ? 'pdf' : 'image'
|
||||
reviewPreviewUrl.value = url
|
||||
reviewPreviewOpen.value = true
|
||||
} catch (error) {
|
||||
ElMessage.error(getApiErrorMessage(error, '资质文件读取失败,请稍后重试'))
|
||||
} finally {
|
||||
reviewDocumentLoading.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function submitDecision() {
|
||||
const review = selectedReview.value
|
||||
if (!review || !decisionFormRef.value || reviewSubmitting.value) return
|
||||
@@ -931,6 +996,7 @@ watch(() => decision.type, (type) => {
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
clearQualificationPreviews()
|
||||
clearReviewPreview()
|
||||
impersonationCleanup?.()
|
||||
})
|
||||
onMounted(() => { void loadCustomers() })
|
||||
@@ -951,8 +1017,8 @@ onMounted(() => { void loadCustomers() })
|
||||
<template #toolbar-actions><div class="customers-table-actions"><el-button v-if="access.can('platform:customer:export') && access.can('platform:download:list')" :loading="exportSubmitting" :disabled="loading || exportSubmitting" @click="exportCustomers"><Download :size="16" />导出Excel</el-button><el-button v-if="access.can('platform:customer:create')" type="primary" :disabled="loading" @click="openCreate"><Plus :size="16" />新增客户</el-button></div></template>
|
||||
<template #actions="{ row }">
|
||||
<div class="table-actions">
|
||||
<el-button v-if="access.can('platform:customer:view')" class="customer-action-button customer-action-detail" size="small" @click="showDetail(row as PlatformTenantResource)"><Eye :size="14" />详情</el-button>
|
||||
<el-button v-if="access.can('platform:customer:view') && access.can('platform:customer:edit')" class="customer-action-button customer-action-edit" size="small" @click="openEdit(row as PlatformTenantResource)"><Pencil :size="14" />编辑</el-button>
|
||||
<el-button v-if="access.can('platform:customer:list')" class="customer-action-button customer-action-detail" size="small" @click="showDetail(row as PlatformTenantResource)"><Eye :size="14" />详情</el-button>
|
||||
<el-button v-if="access.can('platform:customer:edit')" class="customer-action-button customer-action-edit" size="small" @click="openEdit(row as PlatformTenantResource)"><Pencil :size="14" />编辑</el-button>
|
||||
<el-button v-if="hasReviewAction(row as PlatformTenantResource)" class="customer-action-button customer-action-review" size="small" :disabled="reviewSubmitting" @click="openDecision(row as PlatformTenantResource, access.can('platform:customer:approve') ? 'approve' : 'reject')"><ShieldCheck :size="14" />审核</el-button>
|
||||
<el-button v-for="action in customerSecondaryActions(row as PlatformTenantResource)" :key="action" :class="[`customer-action-button`, `customer-action-${action}`, { 'is-enable': action === 'status' && (row as PlatformTenantResource).status !== 'ACTIVE', 'is-disable': action === 'status' && (row as PlatformTenantResource).status === 'ACTIVE' }]" size="small" :disabled="customerActionDisabled(action)" @click="handleMoreAction(row as PlatformTenantResource, action)"><component :is="customerRowActionIcons[action]" :size="14" />{{ customerActionLabel(row as PlatformTenantResource, action) }}</el-button>
|
||||
</div>
|
||||
@@ -1043,9 +1109,12 @@ onMounted(() => { void loadCustomers() })
|
||||
</section>
|
||||
<section class="review-dialog-section">
|
||||
<h3>资质文件</h3>
|
||||
<div v-if="reviewQualificationsLoading" class="review-document-state">正在加载资质文件...</div>
|
||||
<div v-else-if="reviewQualificationsError" class="review-document-error" role="alert"><span>{{ reviewQualificationsError.message }}</span><el-button link type="primary" @click="loadReviewQualifications">重试</el-button></div>
|
||||
<div class="review-document-table" role="table" aria-label="待审核客户资质">
|
||||
<div class="review-document-header" role="row"><span role="columnheader">文件类型</span><span role="columnheader">证书编号</span><span role="columnheader">状态</span></div>
|
||||
<div class="review-document-row" role="row"><span role="cell">{{ selectedReview.qualificationType || '企业资质' }}</span><span role="cell">{{ selectedReview.certificateNo || '—' }}</span><span role="cell">待审核</span></div>
|
||||
<div class="review-document-header" role="row"><span role="columnheader">文件类型</span><span role="columnheader">文件名称</span><span role="columnheader">操作</span></div>
|
||||
<div v-for="item in reviewQualifications" :key="String(item.id)" class="review-document-row" role="row"><span role="cell">{{ qualificationLabel(item.qualificationType) }}</span><span role="cell">{{ qualificationName(item) }}</span><span role="cell" class="review-document-actions"><el-button link type="primary" :loading="reviewDocumentLoading === String(item.id)" @click="loadReviewDocument(item, false)"><Eye :size="14" />预览</el-button><el-button link :disabled="Boolean(reviewDocumentLoading)" @click="loadReviewDocument(item, true)"><Download :size="14" />下载</el-button></span></div>
|
||||
<div v-if="!reviewQualificationsLoading && !reviewQualificationsError && reviewQualifications.length === 0" class="review-document-empty">暂无资质文件</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
@@ -1056,6 +1125,10 @@ onMounted(() => { void loadCustomers() })
|
||||
<template #footer><div class="drawer-footer"><el-button :disabled="reviewSubmitting" @click="decisionOpen = false">返回</el-button><el-button v-if="access.can('platform:customer:reject') && decision.type === 'reject'" type="danger" :loading="reviewSubmitting" @click="submitDecision">确认驳回</el-button><el-button v-if="access.can('platform:customer:approve') && decision.type === 'approve'" type="primary" :loading="reviewSubmitting" @click="submitDecision">审核通过</el-button></div></template>
|
||||
</el-dialog>
|
||||
|
||||
<AppDialog v-model="reviewPreviewOpen" :title="reviewPreviewName || '查看资质文件'" width="min(860px, calc(100vw - 28px))" destroy-on-close :show-confirm="false" cancel-text="关闭预览" @closed="clearReviewPreview">
|
||||
<div class="qualification-preview"><img v-if="reviewPreviewUrl && reviewPreviewKind === 'image'" :src="reviewPreviewUrl" :alt="`${reviewPreviewName}预览`" /><iframe v-else-if="reviewPreviewUrl" :src="reviewPreviewUrl" :title="`${reviewPreviewName} PDF 预览`" /></div>
|
||||
</AppDialog>
|
||||
|
||||
<el-dialog v-model="passwordDialogOpen" title="重置客户密码" width="min(520px, calc(100vw - 28px))" class="customer-password-dialog" :close-on-click-modal="false" :close-on-press-escape="!passwordSubmitting" :show-close="!passwordSubmitting">
|
||||
<div v-if="passwordTenant" class="decision-summary"><div><strong>正在重置企业管理员登录密码</strong><span>{{ passwordTenant.tenantName }}</span></div><span>客户编号:{{ passwordTenant.tenantCode }}</span></div>
|
||||
<el-alert v-if="passwordError" type="error" :closable="false" show-icon role="alert"><template #title><div class="api-error-alert-title"><span>{{ passwordError.message }}</span><ApiErrorMeta :details="passwordError" /></div></template></el-alert>
|
||||
@@ -1152,11 +1225,16 @@ onMounted(() => { void loadCustomers() })
|
||||
.review-info-grid dt { color:#64748b; font-size:12px; }
|
||||
.review-info-grid dd { min-width:0; margin:0; overflow-wrap:anywhere; color:#1e293b; font-size:13px; }
|
||||
.review-document-table { overflow:hidden; border:1px solid #e4e4e7; border-radius:6px; }
|
||||
.review-document-header,.review-document-row { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr) 76px; }
|
||||
.review-document-header,.review-document-row { display:grid; grid-template-columns:minmax(120px,.8fr) minmax(140px,1fr) 150px; }
|
||||
.review-document-header { background:rgba(102,126,234,.08); color:#52525b; font-size:12px; font-weight:600; }
|
||||
.review-document-header > span,.review-document-row > span { min-width:0; padding:9px 10px; overflow-wrap:anywhere; border-right:1px solid #e4e4e7; }
|
||||
.review-document-header > span:last-child,.review-document-row > span:last-child { border-right:0; }
|
||||
.review-document-row { border-top:1px solid #e4e4e7; color:#334155; font-size:13px; }
|
||||
.review-document-actions { display:flex; align-items:center; gap:8px; }
|
||||
.review-document-actions :deep(.el-button + .el-button) { margin-left:0; }
|
||||
.review-document-state,.review-document-error,.review-document-empty { padding:14px; color:var(--cc-muted); font-size:12px; }
|
||||
.review-document-error { display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:8px; border:1px solid #fecdca; border-radius:6px; background:#fffafa; color:#b42318; }
|
||||
.review-document-empty { text-align:center; }
|
||||
.decision-summary > div > span { display:block; margin-top:4px; }.decision-type { margin:0 0 16px; }.password-input-row { display:grid; grid-template-columns:minmax(0,1fr) auto; width:100%; gap:8px; }.dialog-hint { margin:12px 0 0; color:var(--cc-muted); font-size:12px; line-height:1.6; }.archive-confirm { display:grid; gap:8px; margin:16px 0; }.archive-confirm strong { color:var(--cc-text); }.archive-confirm span { color:var(--cc-muted); font-size:12px; }
|
||||
.approval-result-warning { display:flex; align-items:flex-start; gap:10px; padding:12px 14px; border:1px solid #fecdca; border-radius:8px; background:#fffafa; color:#b42318; } .approval-result-warning strong,.approval-result-warning span { display:block; } .approval-result-warning strong { font-size:14px; } .approval-result-warning span { margin-top:4px; font-size:12px; line-height:1.5; } .approval-credentials { margin:18px 0 0; border:1px solid var(--cc-border); border-radius:8px; } .approval-credentials > div { display:grid; grid-template-columns:90px minmax(0,1fr); gap:12px; padding:12px 14px; } .approval-credentials > div + div { border-top:1px solid var(--cc-border); } .approval-credentials dt { color:var(--cc-muted); font-size:12px; } .approval-credentials dd { display:flex; min-width:0; align-items:center; gap:10px; margin:0; } .approval-credentials code { min-width:0; flex:1; overflow-wrap:anywhere; color:var(--cc-text); font-size:14px; font-weight:700; }
|
||||
@media (max-width:720px) { .customer-editor-footer,.review-sync-error { align-items:stretch; flex-direction:column; } .customers-table-actions { display:grid; grid-template-columns:1fr 1fr; width:100%; } .customers-table-actions :deep(.el-button) { min-height:40px; margin:0; } .qualification-table { overflow-x:auto; } .qualification-row { min-width:640px; } .customer-editor-footer > div { display:grid; grid-template-columns:repeat(auto-fit,minmax(110px,1fr)); width:100%; } .customer-editor-footer :deep(.el-button) { min-height:40px; margin:0; } .decision-summary { align-items:flex-start; flex-direction:column; }.review-dialog-grid { grid-template-columns:1fr; gap:18px; }.review-info-grid > div { grid-template-columns:92px minmax(0,1fr); }.password-input-row { grid-template-columns:1fr; }.table-actions :deep(.el-button) { min-height:40px; } }
|
||||
|
||||
Reference in New Issue
Block a user