1
This commit is contained in:
@@ -194,9 +194,13 @@ describe('CustomersView', () => {
|
||||
expect(customersSource).not.toContain('<AppDrawer v-model="editorOpen"')
|
||||
})
|
||||
|
||||
it('reserves enough space for qualification validity dates without widening the upload column', () => {
|
||||
it('keeps qualification file controls usable when the editor dialog is narrow', () => {
|
||||
expect(customersSource).toContain('class="qualification-validity" role="cell"')
|
||||
expect(customersSource).toContain('grid-template-columns:170px minmax(0,1fr) 220px')
|
||||
expect(customersSource).toContain('.qualification-table { container-type:inline-size;')
|
||||
expect(customersSource).toContain('@container (max-width:660px)')
|
||||
expect(customersSource).toContain('.qualification-row { grid-template-columns:112px minmax(0,1fr); }')
|
||||
expect(customersSource).toContain('.qualification-label { grid-row:span 2; align-items:center; }')
|
||||
expect(customersSource).toContain('.qualification-validity :deep(.el-date-editor) { width:100%; min-width:0; }')
|
||||
expect(customersSource).toContain('.qualification-file-name { min-width:0;')
|
||||
expect(readFileSync(resolve(process.cwd(), 'src/modules/customers/customerDensity.css'), 'utf8')).toContain('max-width: none; overflow-wrap: anywhere; white-space: normal;')
|
||||
@@ -313,8 +317,41 @@ describe('CustomersView', () => {
|
||||
|
||||
expect(updatePlatformTenant).toHaveBeenCalledWith(fullTenant.id, expect.objectContaining({ contactPhone: '0755-12345678' }))
|
||||
expect(wrapper.findComponent(AppDialog).props('modelValue')).toBe(false)
|
||||
expect(document.body.textContent).toContain('客户资料已保存')
|
||||
expect(document.body.textContent).not.toContain('尚未提交审核')
|
||||
expect(document.body.textContent).toContain('不会修改企业管理员登录手机号')
|
||||
expect(customersSource).not.toContain('联系电话(登录用户名)')
|
||||
expect(customersSource.indexOf('if (!failures.length) editorOpen.value = false')).toBeLessThan(customersSource.indexOf('await loadCustomers()'))
|
||||
})
|
||||
|
||||
it('allows an active customer to replace qualifications and select a validity date', async () => {
|
||||
const activeTenant = { ...fullTenant, status: 'ACTIVE' as const }
|
||||
vi.mocked(listPlatformTenants).mockResolvedValue({ records: [activeTenant], page: 1, size: 10, total: 1, pages: 1 })
|
||||
vi.mocked(getPlatformTenant).mockResolvedValue(activeTenant)
|
||||
vi.mocked(listTenantQualifications).mockResolvedValue([
|
||||
{
|
||||
id: 'qualification-license', tenantId: fullTenant.id, qualificationType: 'BUSINESS_LICENSE',
|
||||
certificateNo: fullTenant.unifiedSocialCreditCode, documentUrl: '/files/license.png', status: 'APPROVED', reviewComment: null, validTo: null,
|
||||
},
|
||||
{
|
||||
id: 'qualification-freight', tenantId: fullTenant.id, qualificationType: 'FREIGHT_FORWARDER_CERTIFICATE',
|
||||
certificateNo: null, documentUrl: '/files/freight.png', status: 'APPROVED', reviewComment: null, validTo: null,
|
||||
},
|
||||
])
|
||||
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
await flushPromises()
|
||||
tableButtonByText('编辑')?.click()
|
||||
await flushPromises()
|
||||
|
||||
const fileInputs = [...document.querySelectorAll<HTMLInputElement>('.qualification-file input[type="file"]')]
|
||||
const validityInputs = [...document.querySelectorAll<HTMLInputElement>('.qualification-validity input')]
|
||||
expect(fileInputs).toHaveLength(4)
|
||||
expect(fileInputs.every((input) => !input.disabled)).toBe(true)
|
||||
expect(validityInputs.map((input) => input.disabled)).toEqual([true, false, true, false])
|
||||
|
||||
selectQualificationFile(0, new File(['license-new'], 'license-new.png', { type: 'image/png' }))
|
||||
await flushPromises()
|
||||
expect(validityInputs[0]?.disabled).toBe(false)
|
||||
})
|
||||
|
||||
it('does not count rejected or superseded history as a current qualification when resubmitting', async () => {
|
||||
|
||||
@@ -228,7 +228,8 @@ let reviewLoadSequence = 0
|
||||
let editorLoadSequence = 0
|
||||
const editorTitle = computed(() => editingId.value ? '编辑客户' : '新增客户')
|
||||
const editorDialogWidth = 'min(1120px, calc(100vw - 28px))'
|
||||
const canUploadQualifications = computed(() => !editorStatus.value || ['PROFILE_REQUIRED', 'REJECTED'].includes(editorStatus.value))
|
||||
const canUploadQualifications = computed(() => !editorStatus.value
|
||||
|| ['PROFILE_REQUIRED', 'REJECTED', 'ACTIVE', 'DISABLED'].includes(editorStatus.value))
|
||||
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))
|
||||
@@ -603,19 +604,22 @@ async function saveCustomer() {
|
||||
editorStatus.value = saved.status
|
||||
savedFormSnapshot.value = JSON.stringify(form.value)
|
||||
const failures = hadSelectedQualifications ? await uploadSelectedQualifications(String(saved.id)) : []
|
||||
const canSubmitSavedCustomerForReview = ['PROFILE_REQUIRED', 'REJECTED'].includes(saved.status)
|
||||
if (failures.length) {
|
||||
ElMessage.warning(`客户资料已保存,但${failures.join('、')}上传失败;失败文件已保留,请修正后再次保存`)
|
||||
} else if (hadBusinessLicense && hadLegalPersonId) {
|
||||
} else if (canSubmitSavedCustomerForReview && hadBusinessLicense && hadLegalPersonId) {
|
||||
ElMessage.success('营业执照、统一社会信用代码和选填的法人身份证已保存,尚未提交审核')
|
||||
} else if (hadBusinessLicense) {
|
||||
} else if (canSubmitSavedCustomerForReview && hadBusinessLicense) {
|
||||
ElMessage.success('营业执照和统一社会信用代码已保存,法人身份证可按需补充,尚未提交审核')
|
||||
} else if (hadSelectedQualifications) {
|
||||
} else if (canSubmitSavedCustomerForReview && hadSelectedQualifications) {
|
||||
ElMessage.success('客户资料和已选资质已保存,尚未提交审核')
|
||||
} else if (hadSelectedQualifications) {
|
||||
ElMessage.success('客户资料和已选资质已保存')
|
||||
} else {
|
||||
ElMessage.success('客户资料已保存')
|
||||
}
|
||||
await loadCustomers()
|
||||
if (!failures.length) editorOpen.value = false
|
||||
await loadCustomers()
|
||||
} catch (error) { ElMessage.error(getApiErrorMessage(error, '客户资料保存失败,请稍后重试')) }
|
||||
finally { saving.value = false }
|
||||
}
|
||||
@@ -1112,7 +1116,7 @@ onMounted(() => { void loadCustomers() })
|
||||
</button>
|
||||
<div class="qualification-file-control"><label class="file-picker" :class="{ 'is-disabled': !canUploadQualifications || draft.uploading }"><span>{{ draft.uploading ? '上传中' : (draft.file ? '已选择新文件' : (draft.uploaded ? '更换文件' : '选择文件')) }}</span><input type="file" accept=".pdf,.jpg,.jpeg,.png,application/pdf,image/jpeg,image/png" :disabled="!canUploadQualifications || draft.uploading" @change="selectQualificationFile($event, draft)" /></label><span class="qualification-file-name" :title="draft.file?.name || draft.uploadedName">{{ draft.file?.name || draft.uploadedName || '未选择文件' }}</span><small v-if="draft.error" class="qualification-error" role="alert">{{ draft.error }}</small></div>
|
||||
</div>
|
||||
<div class="qualification-validity" role="cell"><el-date-picker v-model="draft.validTo" type="date" value-format="YYYY-MM-DD" placeholder="请选择有效期" :disabled="!canUploadQualifications || draft.uploaded" /></div>
|
||||
<div class="qualification-validity" role="cell"><el-date-picker v-model="draft.validTo" type="date" value-format="YYYY-MM-DD" placeholder="请选择有效期" :disabled="!canUploadQualifications || draft.uploading || (draft.uploaded && !draft.file)" /></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="qualification-hint">支持 PDF、JPG、PNG,单个文件不超过 5MB;<span>*</span> 为提交审核必填项。</p>
|
||||
@@ -1222,7 +1226,7 @@ onMounted(() => { void loadCustomers() })
|
||||
.capital-input { display:grid; grid-template-columns:minmax(0,1fr) 130px; width:100%; gap:var(--admin-space-3, 8px); } .capital-input :deep(.el-input-number) { width:100%; } .capital-input :deep(.el-select) { width:100%; }
|
||||
.qualification-heading { display:flex; justify-content:flex-end; margin-bottom:var(--admin-space-5, 12px); } .qualification-hint { margin:var(--admin-space-5, 12px) 0 0; color:var(--cc-muted); font-size:var(--admin-font-caption, 12px); line-height:1.6; } .qualification-hint span { color:var(--el-color-danger); } .qualification-label > span { color:var(--el-color-danger); }
|
||||
.qualification-credit-code { max-width:420px; margin-top:var(--admin-space-6, 16px); } .qualification-credit-code small { display:block; margin-top:var(--admin-space-2, 6px); color:var(--cc-muted); font-size:var(--admin-font-caption, 11px); line-height:1.5; } .field-hint { display:block; margin-top:var(--admin-space-2, 6px); color:var(--cc-muted); 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:170px minmax(0,1fr) 220px; min-width:0; border-top:1px solid var(--cc-border); } .qualification-row:first-child { border-top:0; } .qualification-row > * { display:flex; min-width:0; align-items:center; } .qualification-header { min-height:44px; background:#f7f8fc; color:var(--cc-muted); font-size:var(--admin-font-caption, 12px); font-weight:600; }
|
||||
.qualification-table { container-type:inline-size; overflow:hidden; margin-top:14px; border:1px solid var(--cc-border); border-radius:8px; } .qualification-row { display:grid; grid-template-columns:170px minmax(0,1fr) 220px; min-width:0; border-top:1px solid var(--cc-border); } .qualification-row:first-child { border-top:0; } .qualification-row > * { display:flex; min-width:0; align-items:center; } .qualification-header { min-height:44px; background:#f7f8fc; color:var(--cc-muted); font-size:var(--admin-font-caption, 12px); font-weight:600; }
|
||||
.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:var(--admin-radius, 4px); background:var(--el-fill-color-light); color:var(--cc-muted); font-size:var(--admin-font-caption, 10px); font-weight:500; line-height:1.4; }
|
||||
.qualification-file { gap:var(--admin-space-5, 12px); } .qualification-file-control { display:flex; min-width:0; flex:1; flex-wrap:wrap; align-items:center; gap:var(--admin-space-3, 8px); } .file-picker { position:relative; display:inline-flex; flex:0 0 auto; align-items:center; gap:7px; overflow:hidden; padding:0 var(--admin-space-5, 12px); border:1px solid var(--cc-border); border-radius:6px; background:var(--admin-surface, #fff); color:var(--cc-text); cursor:pointer; } .file-picker:focus-within { outline:2px solid color-mix(in srgb,var(--cc-primary) 35%,transparent); outline-offset:2px; } .file-picker.is-disabled { cursor:not-allowed; opacity:.6; } .file-picker input { position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:inherit; } .qualification-file-name { min-width:0; overflow:hidden; color:var(--cc-muted); font-size:var(--admin-font-caption, 12px); text-overflow:ellipsis; } .qualification-validity :deep(.el-date-editor) { width:100%; min-width:0; } .qualification-error { width:100%; color:var(--el-color-danger); line-height:1.4; }
|
||||
@@ -1230,6 +1234,12 @@ onMounted(() => { void loadCustomers() })
|
||||
.qualification-preview-trigger:hover { border-color:var(--cc-primary); color:var(--cc-primary); }
|
||||
.qualification-preview-trigger:focus-visible { outline:2px solid color-mix(in srgb,var(--cc-primary) 42%,transparent); outline-offset:2px; }
|
||||
.qualification-preview-trigger:disabled { cursor:wait; opacity:.72; }
|
||||
@container (max-width:660px) {
|
||||
.qualification-row { grid-template-columns:112px minmax(0,1fr); }
|
||||
.qualification-header { display:none; }
|
||||
.qualification-label { grid-row:span 2; align-items:center; }
|
||||
.qualification-file,.qualification-validity { min-height:44px; }
|
||||
}
|
||||
.qualification-preview-trigger img { width:100%; height:100%; object-fit:contain; background:var(--admin-surface, #fff); }
|
||||
.qualification-preview-placeholder { display:grid; place-items:center; gap:2px; }
|
||||
.qualification-preview-placeholder small { font-size:var(--admin-font-caption, 10px); font-weight:700; }
|
||||
|
||||
Reference in New Issue
Block a user