优化
This commit is contained in:
@@ -199,6 +199,10 @@ describe('CustomersView', () => {
|
||||
})
|
||||
|
||||
it('saves customer data and selected qualifications with one action', async () => {
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([{
|
||||
id: 'qualification-legal', tenantId: fullTenant.id, qualificationType: 'LEGAL_PERSON_ID',
|
||||
certificateNo: null, documentUrl: '/files/legal-person.png', status: 'PENDING', reviewComment: null, validTo: null,
|
||||
}])
|
||||
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
tableButtonByText('编辑')?.click()
|
||||
|
||||
@@ -108,10 +108,7 @@ const rules: FormRules = {
|
||||
tenantName: [{ required: true, message: '请输入公司名称', trigger: 'blur' }],
|
||||
legalName: [{ required: true, message: '请输入营业执照上的企业名称', trigger: 'blur' }],
|
||||
legalRepresentative: [{ required: true, message: '请输入法定代表人姓名', trigger: 'blur' }],
|
||||
unifiedSocialCreditCode: [
|
||||
{ required: true, message: '请输入统一社会信用代码', trigger: 'blur' },
|
||||
{ pattern: /^[0-9A-Z]{18}$/, message: '请输入 18 位大写字母或数字', trigger: 'blur' },
|
||||
],
|
||||
unifiedSocialCreditCode: [{ pattern: /^[0-9A-Z]{18}$/, message: '请输入 18 位大写字母或数字', trigger: 'blur' }],
|
||||
contactName: [{ required: true, message: '请输入联系人姓名', trigger: 'blur' }],
|
||||
contactPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
|
||||
contactEmail: [{ type: 'email', message: '请输入有效邮箱地址', trigger: 'blur' }],
|
||||
@@ -151,6 +148,8 @@ function newQualificationDrafts(): QualificationDraft[] {
|
||||
}))
|
||||
}
|
||||
const qualifications = ref<QualificationDraft[]>(newQualificationDrafts())
|
||||
const creditCodeEstablished = ref(false)
|
||||
const unifiedSocialCreditCodePattern = /^[0-9A-Z]{18}$/
|
||||
|
||||
const customerColumns: DataTableColumn[] = [
|
||||
{ key: 'actions', label: '操作', minWidth: 190, slotName: 'actions' },
|
||||
@@ -174,6 +173,20 @@ const canUploadQualifications = computed(() => !editorStatus.value || ['PROFILE_
|
||||
const canSubmitReview = computed(() => Boolean(editingId.value) && ['PROFILE_REQUIRED', 'REJECTED'].includes(editorStatus.value || ''))
|
||||
const uploadingQualifications = computed(() => qualifications.value.some((item) => item.uploading))
|
||||
const hasSelectedQualifications = computed(() => qualifications.value.some((item) => item.file))
|
||||
const selectedBusinessLicense = computed(() => qualifications.value.find((item) => item.type === 'BUSINESS_LICENSE')?.file)
|
||||
const creditCodeReadyForSave = computed(() => creditCodeEstablished.value
|
||||
|| (Boolean(selectedBusinessLicense.value) && unifiedSocialCreditCodePattern.test(form.value.unifiedSocialCreditCode || '')))
|
||||
const requiredQualificationPackageComplete = computed(() => creditCodeReadyForSave.value
|
||||
&& qualifications.value.filter((item) => item.required).every((item) => item.uploaded || Boolean(item.file)))
|
||||
const missingQualificationPackageItems = computed(() => {
|
||||
const businessLicense = qualifications.value.find((item) => item.type === 'BUSINESS_LICENSE')
|
||||
const legalPersonId = qualifications.value.find((item) => item.type === 'LEGAL_PERSON_ID')
|
||||
const missing: string[] = []
|
||||
if (!businessLicense?.uploaded && !businessLicense?.file) missing.push('营业执照')
|
||||
if (!creditCodeReadyForSave.value) missing.push('18位统一社会信用代码(需随营业执照保存)')
|
||||
if (!legalPersonId?.uploaded && !legalPersonId?.file) missing.push('法人身份证')
|
||||
return missing
|
||||
})
|
||||
const hasUnsavedChanges = computed(() => Boolean(editingId.value) && JSON.stringify(form.value) !== savedFormSnapshot.value)
|
||||
const pendingReviewByTenantId = computed(() => new Map(pendingReviews.value.map((review) => [String(review.tenantId), review])))
|
||||
function displayText(value?: string | null) { return value?.trim() || '—' }
|
||||
@@ -302,7 +315,7 @@ function clearQualificationPreviews() {
|
||||
}
|
||||
function resetEditor() {
|
||||
clearQualificationPreviews()
|
||||
form.value = emptyForm(); qualifications.value = newQualificationDrafts(); qualificationLoadError.value = ''; savedFormSnapshot.value = ''
|
||||
form.value = emptyForm(); qualifications.value = newQualificationDrafts(); creditCodeEstablished.value = false; qualificationLoadError.value = ''; savedFormSnapshot.value = ''
|
||||
}
|
||||
function openCreate() {
|
||||
editorLoadSequence += 1
|
||||
@@ -352,6 +365,7 @@ async function openEdit(row: PlatformTenantResource) {
|
||||
if (sequence !== editorLoadSequence || editingId.value !== id || !editorOpen.value) return
|
||||
editorStatus.value = tenant.status
|
||||
form.value = mapTenantToForm(tenant)
|
||||
creditCodeEstablished.value = unifiedSocialCreditCodePattern.test(tenant.unifiedSocialCreditCode || '')
|
||||
savedFormSnapshot.value = JSON.stringify(form.value)
|
||||
await loadEditorQualifications(row.id, sequence)
|
||||
await nextTick(() => formRef.value?.clearValidate())
|
||||
@@ -430,6 +444,7 @@ async function uploadSelectedQualifications(tenantId: string) {
|
||||
draft.uploaded,
|
||||
)
|
||||
draft.uploaded = true
|
||||
if (draft.type === 'BUSINESS_LICENSE') creditCodeEstablished.value = true
|
||||
draft.uploadedName = draft.file?.name || '已登记文件'
|
||||
draft.documentUrl = uploaded.documentUrl
|
||||
draft.file = null
|
||||
@@ -442,6 +457,11 @@ async function uploadSelectedQualifications(tenantId: string) {
|
||||
|
||||
async function saveCustomer() {
|
||||
if (!formRef.value || !(await formRef.value.validate().catch(() => false))) return
|
||||
const qualificationPackageTouched = hasSelectedQualifications.value || Boolean(form.value.unifiedSocialCreditCode)
|
||||
if (qualificationPackageTouched && !requiredQualificationPackageComplete.value) {
|
||||
ElMessage.warning(`请完整保存必填业务项:${missingQualificationPackageItems.value.join('、')}`)
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const hadSelectedQualifications = hasSelectedQualifications.value
|
||||
@@ -450,7 +470,7 @@ async function saveCustomer() {
|
||||
const { unifiedSocialCreditCode: _immutable, ...payload } = form.value
|
||||
saved = await updatePlatformTenant(editingId.value, payload)
|
||||
} else {
|
||||
saved = await createPlatformTenant(form.value)
|
||||
saved = await createPlatformTenant({ ...form.value, unifiedSocialCreditCode: undefined })
|
||||
editingId.value = String(saved.id)
|
||||
}
|
||||
editorStatus.value = saved.status
|
||||
@@ -459,7 +479,7 @@ async function saveCustomer() {
|
||||
if (failures.length) {
|
||||
ElMessage.warning(`客户资料已保存,但${failures.join('、')}上传失败;失败文件已保留,请修正后再次保存`)
|
||||
} else if (hadSelectedQualifications) {
|
||||
ElMessage.success('客户资料和已选资质已保存,尚未提交审核')
|
||||
ElMessage.success('营业执照、统一社会信用代码和法人身份证已完整保存,尚未提交审核')
|
||||
} else {
|
||||
ElMessage.success('客户资料已保存,尚未提交审核')
|
||||
}
|
||||
@@ -477,6 +497,10 @@ async function submitReview() {
|
||||
if (hasUnsavedChanges.value) { ElMessage.warning('客户资料有未保存的修改,请先保存资料'); return }
|
||||
if (uploadingQualifications.value) { ElMessage.warning('资质文件正在上传,请稍候再提交审核'); return }
|
||||
if (hasSelectedQualifications.value) { ElMessage.warning('存在尚未保存的已选文件,请先点击“保存资料和资质”'); return }
|
||||
if (!creditCodeEstablished.value || !unifiedSocialCreditCodePattern.test(form.value.unifiedSocialCreditCode || '')) {
|
||||
ElMessage.warning('请先完整保存营业执照、18位统一社会信用代码和法人身份证')
|
||||
return
|
||||
}
|
||||
const missing = qualifications.value.filter((item) => item.required && !item.uploaded)
|
||||
for (const item of missing) item.error = `${item.label}为提交审核必填文件`
|
||||
if (missing.length) { ElMessage.warning(`请先上传${missing.map((item) => item.label).join('、')}`); return }
|
||||
@@ -627,7 +651,6 @@ onMounted(async () => {
|
||||
<el-form-item label="公司名称" prop="tenantName" required><el-input v-model="form.tenantName" placeholder="请输入工商登记公司名称" maxlength="128" /></el-form-item>
|
||||
<el-form-item label="营业执照企业名称" prop="legalName" required><el-input v-model="form.legalName" placeholder="请输入营业执照上的企业名称" maxlength="128" /></el-form-item>
|
||||
<el-form-item label="法定代表人" prop="legalRepresentative" required><el-input v-model="form.legalRepresentative" placeholder="请输入法定代表人姓名" maxlength="64" /></el-form-item>
|
||||
<el-form-item label="统一社会信用代码" prop="unifiedSocialCreditCode" required><el-input v-model="form.unifiedSocialCreditCode" :disabled="Boolean(editingId)" placeholder="请输入18位大写字母或数字" maxlength="18" @input="form.unifiedSocialCreditCode = String($event).toUpperCase()" /></el-form-item>
|
||||
<el-form-item label="企业类型" prop="enterpriseType"><el-input v-model="form.enterpriseType" placeholder="如:有限责任公司" maxlength="64" /></el-form-item>
|
||||
<el-form-item label="成立日期" prop="establishedDate"><el-date-picker style="width: 100%;" v-model="form.establishedDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择成立日期" :disabled-date="(date: Date) => date.getTime() > Date.now()" /></el-form-item>
|
||||
<el-form-item label="注册资本" prop="registeredCapital"><div class="capital-input"><el-input-number v-model="form.registeredCapital" :min="0" :precision="2" controls-position="right" /><el-select v-model="form.registeredCapitalCurrency" aria-label="注册资本币种"><el-option label="人民币 CNY" value="CNY" /><el-option label="美元 USD" value="USD" /><el-option label="港币 HKD" value="HKD" /></el-select></div></el-form-item>
|
||||
@@ -643,7 +666,8 @@ onMounted(async () => {
|
||||
<el-form-item label="账期(天)" prop="paymentTermDays" required><el-input-number v-model="form.paymentTermDays" :min="0" :max="365" controls-position="right" /></el-form-item>
|
||||
</div><el-form-item label="内部备注" prop="internalRemark"><el-input v-model="form.internalRemark" type="textarea" :rows="3" placeholder="请输入仅平台人员可见的客户备注" maxlength="1000" show-word-limit /></el-form-item></div>
|
||||
|
||||
<div class="form-section qualification-section"><div class="qualification-heading"><div><h3>资质文件</h3><p>先选择并上传文件;上传成功不会创建审核单,点击“提交审核”并确认后才会进入审核。</p></div><el-tag v-if="editorStatus" :type="statusMeta(editorStatus).type" effect="light">{{ statusMeta(editorStatus).label }}</el-tag></div>
|
||||
<div class="form-section qualification-section"><div class="qualification-heading"><div><h3>企业认证必填业务项</h3><p>营业执照、18位统一社会信用代码、法人身份证需共同完整保存;货代资质证书和银行开户许可证选填。</p></div><el-tag v-if="editorStatus" :type="statusMeta(editorStatus).type" effect="light">{{ statusMeta(editorStatus).label }}</el-tag></div>
|
||||
<el-form-item label="统一社会信用代码" prop="unifiedSocialCreditCode" required class="qualification-credit-code"><el-input v-model="form.unifiedSocialCreditCode" :disabled="creditCodeEstablished" placeholder="请输入18位大写字母或数字" maxlength="18" @input="form.unifiedSocialCreditCode = String($event).toUpperCase()" /><small>{{ creditCodeEstablished ? '已登记的统一社会信用代码不可直接修改。' : '必须与营业执照一致,并随营业执照文件共同保存。' }}</small></el-form-item>
|
||||
<div v-if="qualificationLoadError" class="qualification-load-error" role="alert">{{ qualificationLoadError }}<el-button v-if="editingId" link type="primary" @click="loadEditorQualifications(editingId)">重试</el-button></div>
|
||||
<div class="qualification-table" role="table" aria-label="客户资质文件">
|
||||
<div class="qualification-row qualification-header" role="row"><span role="columnheader">文件类型</span><span role="columnheader">上传文件</span><span role="columnheader">有效期</span></div>
|
||||
@@ -706,6 +730,7 @@ onMounted(async () => {
|
||||
.customer-company-cell { display:flex; min-width:0; align-items:center; gap:10px; } .customer-company-cell > span { display:grid; width:34px; height:34px; flex:0 0 auto; place-items:center; border-radius:7px; background:#eef4ff; color:var(--cc-primary); } .customer-company-cell strong { overflow:hidden; text-overflow:ellipsis; }
|
||||
.capital-input { display:grid; grid-template-columns:minmax(0,1fr) 130px; width:100%; gap:8px; } .capital-input :deep(.el-input-number),.capital-input :deep(.el-select) { width:100%; }
|
||||
.qualification-heading { display:flex; align-items:flex-start; justify-content:space-between; gap:16px; } .qualification-heading h3 { margin-bottom:4px; } .qualification-heading p,.qualification-hint { margin:0; color:var(--cc-muted); font-size:12px; line-height:1.6; } .qualification-hint { margin-top:12px; } .qualification-hint span,.qualification-label > span { color:var(--el-color-danger); }
|
||||
.qualification-credit-code { max-width:420px; margin-top:16px; } .qualification-credit-code small { display:block; margin-top:6px; color:var(--cc-muted); font-size:11px; line-height:1.5; }
|
||||
.qualification-table { overflow:hidden; margin-top:14px; border:1px solid var(--cc-border); border-radius:8px; } .qualification-row { display:grid; grid-template-columns:180px minmax(300px,1fr) 180px; min-width:0; border-top:1px solid var(--cc-border); } .qualification-row:first-child { border-top:0; } .qualification-row > * { display:flex; min-width:0; min-height:68px; align-items:center; padding:12px 14px; } .qualification-header { min-height:44px; background:#f7f8fc; color:var(--cc-muted); font-size:12px; font-weight:600; } .qualification-header > * { min-height:44px; }
|
||||
.qualification-label { gap:5px; font-weight:600; } .qualification-label svg { margin-left:auto; color:var(--el-color-success); }
|
||||
.qualification-optional { padding:2px 5px; border-radius:4px; background:var(--el-fill-color-light); color:var(--cc-muted); font-size:10px; font-weight:500; line-height:1.4; }
|
||||
|
||||
Reference in New Issue
Block a user