优化
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import type { FormInstance, FormRules, UploadFile } from 'element-plus'
|
||||
import { Building2, CheckCircle2, Clock3, FileCheck2, FileText, Pencil, Trash2, Upload, UserRound } from '@lucide/vue'
|
||||
import DataTable, { type DataTableColumn } from '@/components/common/DataTable.vue'
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
|
||||
const store = useTenantAdministrationStore()
|
||||
const auth = useAuthStore()
|
||||
const route = useRoute()
|
||||
const companyApproved = computed(() => store.company.auditStatus === 'ACTIVE')
|
||||
const auditStatusCopy = computed(() => ({
|
||||
PROFILE_REQUIRED: { title: '请完善企业资质', detail: '上传营业执照并提交审核后,才能提交头程订单;法人身份证可按需补充。' },
|
||||
@@ -41,6 +43,7 @@ const qualificationPage = ref(1)
|
||||
const qualificationPageSize = ref(10)
|
||||
const qualificationSubmitting = ref(false)
|
||||
const reviewSubmitting = ref(false)
|
||||
const deletingQualificationId = ref('')
|
||||
const profileSubmitting = ref(false)
|
||||
const loading = ref(false)
|
||||
const loadError = ref('')
|
||||
@@ -60,7 +63,7 @@ type QualificationDraftItem = QualificationTypeDefinition & {
|
||||
|
||||
const qualificationTypes: QualificationTypeDefinition[] = [
|
||||
{ value: 'BUSINESS_LICENSE', label: '营业执照', description: '上传清晰完整的营业执照文件。', required: true },
|
||||
{ value: 'LEGAL_PERSON_ID', label: '法人身份证', description: '选填;如上传,正反面请合并为一个 PDF、JPG 或 PNG 文件。', required: false },
|
||||
{ value: 'LEGAL_PERSON_ID', label: '法人身份证', description: '选填;如上传,正反面请合并为一个 PDF、JPG、PNG 或 BMP 文件。', required: false },
|
||||
{ value: 'FREIGHT_FORWARDER_CERTIFICATE', label: '货代资质证书', description: '如有,可上传当前有效的货代资质证明。', required: false },
|
||||
{ value: 'BANK_ACCOUNT_LICENSE', label: '银行开户许可证', description: '如有,可上传企业对公账户开户证明。', required: false },
|
||||
]
|
||||
@@ -154,7 +157,7 @@ const profileFormRules: FormRules<TenantCompanyProfile> = {
|
||||
contactEmail: [{ type: 'email', message: '请输入正确的联系邮箱', trigger: 'blur' }],
|
||||
}
|
||||
const qualificationColumns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', width: 90, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'actions', label: '操作', width: 132, fixed: 'left', slotName: 'actions' },
|
||||
{ key: 'type', prop: 'type', label: '文件类型', minWidth: 150 },
|
||||
{ key: 'fileName', prop: 'fileName', label: '文件名称', minWidth: 220, showOverflowTooltip: true },
|
||||
{ key: 'status', label: '状态', width: 110, slotName: 'status' },
|
||||
@@ -184,7 +187,13 @@ async function loadCompanyContext() {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
onMounted(() => void loadCompanyContext())
|
||||
onMounted(async () => {
|
||||
await loadCompanyContext()
|
||||
if (route.query.qualification === 'upload' && canUploadQualification.value) {
|
||||
activeTab.value = 'qualification'
|
||||
openQualificationDialog()
|
||||
}
|
||||
})
|
||||
onBeforeUnmount(() => qualificationDrafts.forEach(releaseQualificationPreview))
|
||||
|
||||
function startEdit() {
|
||||
@@ -299,7 +308,7 @@ function handleQualificationFileChange(type: MerchantQualificationType, file: Up
|
||||
item.fileName = raw.name
|
||||
item.fileSize = raw.size
|
||||
item.fileType = raw.type || raw.name.split('.').pop()?.toUpperCase() || '文件'
|
||||
if (raw.type.startsWith('image/') || /\.(jpe?g|png)$/i.test(raw.name)) item.previewUrl = URL.createObjectURL(raw)
|
||||
if (raw.type.startsWith('image/') || /\.(jpe?g|png|bmp)$/i.test(raw.name)) item.previewUrl = URL.createObjectURL(raw)
|
||||
}
|
||||
|
||||
function removeSelectedFile(type: MerchantQualificationType) {
|
||||
@@ -329,19 +338,19 @@ function resetQualificationDraft() {
|
||||
qualificationCertificateNo.value = store.company.creditCode
|
||||
}
|
||||
|
||||
async function submitQualification() {
|
||||
if (qualificationSubmitting.value) return
|
||||
async function submitQualification(closeDialog = true) {
|
||||
if (qualificationSubmitting.value) return false
|
||||
if (!canUploadQualification.value) {
|
||||
qualificationOpen.value = false
|
||||
await loadCompanyContext()
|
||||
ElMessage.warning(qualificationUploadDisabledReason.value)
|
||||
return
|
||||
return false
|
||||
}
|
||||
if (!requiredPackageComplete.value) {
|
||||
const businessLicense = qualificationDraftItem('BUSINESS_LICENSE')
|
||||
if (businessLicense && !qualificationCreditCodeReady.value) businessLicense.error = qualificationCreditCodeError.value
|
||||
ElMessage.warning(`请完整保存必填业务项:${missingRequiredPackageItems.value.join('、')}`)
|
||||
return
|
||||
return false
|
||||
}
|
||||
const optionalItems = qualificationDrafts.filter((item) => !item.required
|
||||
&& item.file
|
||||
@@ -418,20 +427,32 @@ async function submitQualification() {
|
||||
if (!canUploadQualification.value) qualificationOpen.value = false
|
||||
const uploadedCopy = uploadedCount ? `已保存 ${uploadedCount} 项;` : ''
|
||||
ElMessage.error(`${uploadedCopy}${failures[0]}`)
|
||||
return
|
||||
return false
|
||||
}
|
||||
qualificationOpen.value = false
|
||||
if (closeDialog) qualificationOpen.value = false
|
||||
if (refreshed) ElMessage.success(includedLegalPersonId
|
||||
? (requiredPackageChangeSelected.value
|
||||
? '营业执照、统一社会信用代码和法人身份证已保存,尚未提交审核'
|
||||
: '法人身份证已保存,尚未提交审核')
|
||||
: '资质文件已保存,尚未提交审核')
|
||||
else ElMessage.warning('资质文件已保存,但最新状态刷新失败,请点击“重新加载”确认状态')
|
||||
return refreshed
|
||||
} finally {
|
||||
qualificationSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submitQualificationAndReview() {
|
||||
if (qualificationSubmitting.value || reviewSubmitting.value) return
|
||||
if (selectedQualificationCount.value > 0 && !await submitQualification(false)) return
|
||||
if (!canSubmitQualificationReview.value) {
|
||||
ElMessage.warning(qualificationReviewDisabledReason.value)
|
||||
return
|
||||
}
|
||||
await submitQualificationReview()
|
||||
if (store.company.auditStatus === 'PENDING') qualificationOpen.value = false
|
||||
}
|
||||
|
||||
async function submitQualificationReview() {
|
||||
if (!canSubmitQualificationReview.value || reviewSubmitting.value) return
|
||||
reviewSubmitting.value = true
|
||||
@@ -450,6 +471,34 @@ async function submitQualificationReview() {
|
||||
}
|
||||
}
|
||||
|
||||
function qualificationDeleteDisabled(record: TenantQualification) {
|
||||
return deletingQualificationId.value === record.id
|
||||
|| !auth.hasPermission('company:qualification:upload')
|
||||
|| !canUploadQualification.value
|
||||
}
|
||||
|
||||
async function deleteQualification(record: TenantQualification) {
|
||||
if (qualificationDeleteDisabled(record)) return
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认删除资质文件“${record.fileName}”?删除后如需送审必须重新上传。`,
|
||||
'删除资质文件',
|
||||
{ type: 'warning', confirmButtonText: '确认删除', cancelButtonText: '取消' },
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
deletingQualificationId.value = record.id
|
||||
try {
|
||||
await store.deleteQualification(record.id)
|
||||
ElMessage.success('资质文件已删除')
|
||||
} catch (error) {
|
||||
ElMessage.error(error instanceof Error ? error.message : '资质文件删除失败')
|
||||
} finally {
|
||||
deletingQualificationId.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
async function previewQualification(record: TenantQualification) {
|
||||
if (previewUrl.value) URL.revokeObjectURL(previewUrl.value)
|
||||
previewRecord.value = record
|
||||
@@ -548,7 +597,10 @@ function closeQualificationPreview() {
|
||||
:total="store.qualifications.length"
|
||||
row-key="id"
|
||||
>
|
||||
<template #actions="{ row }"><el-button link type="primary" @click="previewQualification(row as TenantQualification)">查看</el-button></template>
|
||||
<template #actions="{ row }">
|
||||
<el-button link type="primary" @click="previewQualification(row as TenantQualification)">查看</el-button>
|
||||
<el-button link type="danger" :loading="deletingQualificationId === (row as TenantQualification).id" :disabled="qualificationDeleteDisabled(row as TenantQualification)" @click="deleteQualification(row as TenantQualification)">删除</el-button>
|
||||
</template>
|
||||
<template #status="{ row }"><el-tag :type="qualificationStatusMeta(row.status).type" effect="light">{{ qualificationStatusMeta(row.status).label }}</el-tag></template>
|
||||
</DataTable>
|
||||
<div class="mobile-qualification-list">
|
||||
@@ -556,7 +608,10 @@ function closeQualificationPreview() {
|
||||
<header><strong>{{ record.type }}</strong><el-tag :type="qualificationStatusMeta(record.status).type" effect="light">{{ qualificationStatusMeta(record.status).label }}</el-tag></header>
|
||||
<div class="qualification-file-name"><FileText :size="16" /><span>{{ record.fileName }}</span></div>
|
||||
<dl><div><dt>有效期</dt><dd>{{ record.expiresAt }}</dd></div><div><dt>更新时间</dt><dd>{{ record.updatedAt }}</dd></div></dl>
|
||||
<el-button link type="primary" @click="previewQualification(record)">查看文件信息</el-button>
|
||||
<div class="mobile-qualification-actions">
|
||||
<el-button link type="primary" @click="previewQualification(record)">查看文件信息</el-button>
|
||||
<el-button link type="danger" :loading="deletingQualificationId === record.id" :disabled="qualificationDeleteDisabled(record)" @click="deleteQualification(record)">删除</el-button>
|
||||
</div>
|
||||
</article>
|
||||
<div v-if="store.qualifications.length === 0" class="client-empty">暂无企业资质记录。</div>
|
||||
</div>
|
||||
@@ -612,11 +667,11 @@ function closeQualificationPreview() {
|
||||
:key="`${item.value}-${item.inputVersion}`"
|
||||
:auto-upload="false"
|
||||
:show-file-list="false"
|
||||
accept=".pdf,.jpg,.jpeg,.png,application/pdf,image/jpeg,image/png"
|
||||
accept=".pdf,.jpg,.jpeg,.png,.bmp,application/pdf,image/jpeg,image/png,image/bmp,image/x-ms-bmp"
|
||||
@change="handleQualificationFileChange(item.value, $event)"
|
||||
>
|
||||
<el-button :disabled="qualificationSubmitting"><Upload :size="16" />{{ qualificationRecord(item.value) ? '替换文件' : '选择文件' }}</el-button>
|
||||
<template #tip><div class="upload-tip">支持 PDF、JPG、PNG,单个文件不超过 5MB。</div></template>
|
||||
<template #tip><div class="upload-tip">支持 PDF、JPG、PNG、BMP,单个文件不超过 5MB。</div></template>
|
||||
</el-upload>
|
||||
<div v-if="item.fileName" class="selected-qualification-file" aria-live="polite">
|
||||
<a v-if="item.previewUrl" class="selected-file-preview" :href="item.previewUrl" target="_blank" rel="noopener" :aria-label="`查看${item.label}图片预览`">
|
||||
@@ -634,7 +689,7 @@ function closeQualificationPreview() {
|
||||
<small class="qualification-credit-code-hint">{{ companyCreditCodeValid ? '已登记的统一社会信用代码不可直接修改。' : '与营业执照一致,保存后不可直接修改。' }}</small>
|
||||
</el-form-item>
|
||||
<p v-if="qualificationDialogHint" class="qualification-dialog-hint">{{ qualificationDialogHint }}</p>
|
||||
<template #footer><el-button :disabled="qualificationSubmitting" @click="qualificationOpen = false">取消</el-button><el-button type="primary" :loading="qualificationSubmitting" :disabled="!canSaveQualificationBatch" @click="submitQualification">保存全部资质</el-button></template>
|
||||
<template #footer><el-button :disabled="qualificationSubmitting || reviewSubmitting" @click="qualificationOpen = false">取消</el-button><el-button :loading="qualificationSubmitting" :disabled="!canSaveQualificationBatch || reviewSubmitting" @click="submitQualification()">保存草稿</el-button><el-button type="primary" :loading="qualificationSubmitting || reviewSubmitting" :disabled="selectedQualificationCount > 0 ? !canSaveQualificationBatch : !canSubmitQualificationReview" @click="submitQualificationAndReview">提交审核</el-button></template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="previewOpen" :title="`${previewRecord?.type || '资质文件'}详情`" width="min(560px, 92vw)" @closed="closeQualificationPreview">
|
||||
@@ -692,6 +747,7 @@ function closeQualificationPreview() {
|
||||
.form-grid-two { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 16px; }
|
||||
.form-grid-two .full-row { grid-column: 1 / -1; }
|
||||
.drawer-actions { display: flex; justify-content: flex-end; gap: 8px; }
|
||||
.mobile-qualification-actions { display: flex; align-items: center; gap: 8px; }
|
||||
.qualification-dialog-intro { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; margin-bottom: 16px; padding: 13px 15px; background: #f4f7f7; }
|
||||
.qualification-dialog-intro strong { color: var(--navy); font-size: 13px; }
|
||||
.qualification-dialog-intro span { max-width: 420px; color: var(--muted); font-size: 11px; line-height: 1.6; text-align: right; }
|
||||
|
||||
Reference in New Issue
Block a user