优化
This commit is contained in:
@@ -13,7 +13,7 @@ server {
|
|||||||
|
|
||||||
# 前端统一请求 /api,保持原始路径转发给 Gateway。
|
# 前端统一请求 /api,保持原始路径转发给 Gateway。
|
||||||
location /api/ {
|
location /api/ {
|
||||||
client_max_body_size 6m;
|
client_max_body_size 12m;
|
||||||
proxy_pass ${API_UPSTREAM};
|
proxy_pass ${API_UPSTREAM};
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|||||||
@@ -5,5 +5,5 @@ import test from 'node:test'
|
|||||||
const config = readFileSync(new URL('../../nginx/default.conf.template', import.meta.url), 'utf8')
|
const config = readFileSync(new URL('../../nginx/default.conf.template', import.meta.url), 'utf8')
|
||||||
|
|
||||||
test('allows qualification uploads with multipart overhead through the API proxy', () => {
|
test('allows qualification uploads with multipart overhead through the API proxy', () => {
|
||||||
assert.match(config, /location \/api\/ \{[\s\S]*?client_max_body_size 6m;/)
|
assert.match(config, /location \/api\/ \{[\s\S]*?client_max_body_size 12m;/)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ test('supports tenant profile updates and authenticated qualification downloads'
|
|||||||
assert.match(storeSource, /normalizeQualificationStatus\(item\.status\)/)
|
assert.match(storeSource, /normalizeQualificationStatus\(item\.status\)/)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('uploads the three required qualification fields through one multipart package request', () => {
|
||||||
|
assert.match(source, /url: '\/merchant\/v1\/files\/qualifications\/required-package'/)
|
||||||
|
assert.match(source, /data\.append\('businessLicense', businessLicense\)/)
|
||||||
|
assert.match(source, /data\.append\('legalPersonId', legalPersonId\)/)
|
||||||
|
assert.match(source, /params: \{ unifiedSocialCreditCode, replaceBusinessLicense, replaceLegalPersonId \}/)
|
||||||
|
})
|
||||||
|
|
||||||
test('uses membershipId and full replacement arrays in authorization writes', () => {
|
test('uses membershipId and full replacement arrays in authorization writes', () => {
|
||||||
assert.match(source, /data: \{ roleId, permissionIds \}/)
|
assert.match(source, /data: \{ roleId, permissionIds \}/)
|
||||||
assert.match(source, /data: \{ membershipId, roleIds \}/)
|
assert.match(source, /data: \{ membershipId, roleIds \}/)
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ export interface QualificationUploadResult {
|
|||||||
documentUrl: string
|
documentUrl: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RequiredQualificationPackageUploadResult {
|
||||||
|
businessLicenseQualificationId: number | string
|
||||||
|
legalPersonIdQualificationId: number | string
|
||||||
|
tenantStatus: MerchantTenantStatus
|
||||||
|
businessLicenseDocumentUrl: string
|
||||||
|
legalPersonIdDocumentUrl: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface MerchantRole {
|
export interface MerchantRole {
|
||||||
id: number | string
|
id: number | string
|
||||||
tenantId: number | string
|
tenantId: number | string
|
||||||
@@ -161,6 +169,24 @@ export function uploadMerchantQualification(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uploadMerchantRequiredQualificationPackage(
|
||||||
|
businessLicense: File,
|
||||||
|
legalPersonId: File,
|
||||||
|
unifiedSocialCreditCode: string,
|
||||||
|
replaceBusinessLicense = false,
|
||||||
|
replaceLegalPersonId = false,
|
||||||
|
) {
|
||||||
|
const data = new FormData()
|
||||||
|
data.append('businessLicense', businessLicense)
|
||||||
|
data.append('legalPersonId', legalPersonId)
|
||||||
|
return apiRequest<RequiredQualificationPackageUploadResult>({
|
||||||
|
method: 'POST',
|
||||||
|
url: '/merchant/v1/files/qualifications/required-package',
|
||||||
|
params: { unifiedSocialCreditCode, replaceBusinessLicense, replaceLegalPersonId },
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export function submitTenantQualificationReview() {
|
export function submitTenantQualificationReview() {
|
||||||
return apiRequest<MerchantTenantProfile>({ method: 'POST', url: '/merchant/v1/iam/tenant/qualifications/submit-review' })
|
return apiRequest<MerchantTenantProfile>({ method: 'POST', url: '/merchant/v1/iam/tenant/qualifications/submit-review' })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
downloadQualificationImage,
|
downloadQualificationImage,
|
||||||
submitTenantQualificationReview,
|
submitTenantQualificationReview,
|
||||||
uploadMerchantQualification,
|
uploadMerchantQualification,
|
||||||
|
uploadMerchantRequiredQualificationPackage,
|
||||||
type MerchantQualificationType,
|
type MerchantQualificationType,
|
||||||
} from '@/modules/tenant-management/api/merchantIam'
|
} from '@/modules/tenant-management/api/merchantIam'
|
||||||
|
|
||||||
@@ -80,11 +81,15 @@ const companyCreditCodeValid = computed(() => unifiedSocialCreditCodePattern.tes
|
|||||||
const normalizedQualificationCertificateNo = computed(() => qualificationCertificateNo.value.trim().toUpperCase())
|
const normalizedQualificationCertificateNo = computed(() => qualificationCertificateNo.value.trim().toUpperCase())
|
||||||
const draftCreditCodeValid = computed(() => unifiedSocialCreditCodePattern.test(normalizedQualificationCertificateNo.value))
|
const draftCreditCodeValid = computed(() => unifiedSocialCreditCodePattern.test(normalizedQualificationCertificateNo.value))
|
||||||
const businessLicenseFileSelected = computed(() => Boolean(qualificationDraftItem('BUSINESS_LICENSE')?.file))
|
const businessLicenseFileSelected = computed(() => Boolean(qualificationDraftItem('BUSINESS_LICENSE')?.file))
|
||||||
const qualificationCreditCodeReady = computed(() => companyCreditCodeValid.value
|
const legalPersonIdFileSelected = computed(() => Boolean(qualificationDraftItem('LEGAL_PERSON_ID')?.file))
|
||||||
|| (businessLicenseFileSelected.value && draftCreditCodeValid.value))
|
const requiredPackageChangeSelected = computed(() => businessLicenseFileSelected.value || legalPersonIdFileSelected.value)
|
||||||
|
const requiredPackageFilesSelected = computed(() => businessLicenseFileSelected.value && legalPersonIdFileSelected.value)
|
||||||
|
const qualificationCreditCodeReady = computed(() => requiredPackageChangeSelected.value
|
||||||
|
? requiredPackageFilesSelected.value && (companyCreditCodeValid.value || draftCreditCodeValid.value)
|
||||||
|
: companyCreditCodeValid.value)
|
||||||
const qualificationCreditCodeError = computed(() => {
|
const qualificationCreditCodeError = computed(() => {
|
||||||
if (qualificationCreditCodeReady.value) return ''
|
if (qualificationCreditCodeReady.value) return ''
|
||||||
if (draftCreditCodeValid.value && !businessLicenseFileSelected.value) return '统一社会信用代码必须随营业执照文件共同保存'
|
if (draftCreditCodeValid.value && !requiredPackageFilesSelected.value) return '统一社会信用代码必须随营业执照和法人身份证共同保存'
|
||||||
return '请输入18位字母或数字组成的统一社会信用代码'
|
return '请输入18位字母或数字组成的统一社会信用代码'
|
||||||
})
|
})
|
||||||
const canUploadQualification = computed(() => ['PROFILE_REQUIRED', 'REJECTED'].includes(store.company.auditStatus))
|
const canUploadQualification = computed(() => ['PROFILE_REQUIRED', 'REJECTED'].includes(store.company.auditStatus))
|
||||||
@@ -92,13 +97,15 @@ const uploadedQualificationTypes = computed(() => new Set(store.qualifications
|
|||||||
.filter((item) => item.status === 'PENDING')
|
.filter((item) => item.status === 'PENDING')
|
||||||
.map((item) => qualificationTypes.find((type) => type.label === item.type)?.value)
|
.map((item) => qualificationTypes.find((type) => type.label === item.type)?.value)
|
||||||
.filter((type): type is MerchantQualificationType => Boolean(type))))
|
.filter((type): type is MerchantQualificationType => Boolean(type))))
|
||||||
const requiredPackageFilesReady = computed(() => requiredQualificationTypes.every((type) => (
|
const requiredPackageFilesReady = computed(() => requiredPackageChangeSelected.value
|
||||||
uploadedQualificationTypes.value.has(type.value) || Boolean(qualificationDraftItem(type.value)?.file)
|
? requiredPackageFilesSelected.value
|
||||||
)))
|
: requiredQualificationTypes.every((type) => uploadedQualificationTypes.value.has(type.value)))
|
||||||
const requiredPackageComplete = computed(() => qualificationCreditCodeReady.value && requiredPackageFilesReady.value)
|
const requiredPackageComplete = computed(() => qualificationCreditCodeReady.value && requiredPackageFilesReady.value)
|
||||||
const missingRequiredPackageItems = computed(() => {
|
const missingRequiredPackageItems = computed(() => {
|
||||||
const missing = requiredQualificationTypes
|
const missing = requiredQualificationTypes
|
||||||
.filter((type) => !uploadedQualificationTypes.value.has(type.value) && !qualificationDraftItem(type.value)?.file)
|
.filter((type) => requiredPackageChangeSelected.value
|
||||||
|
? !qualificationDraftItem(type.value)?.file
|
||||||
|
: !uploadedQualificationTypes.value.has(type.value))
|
||||||
.map((type) => type.label)
|
.map((type) => type.label)
|
||||||
if (!qualificationCreditCodeReady.value) missing.splice(1, 0, '18位统一社会信用代码')
|
if (!qualificationCreditCodeReady.value) missing.splice(1, 0, '18位统一社会信用代码')
|
||||||
return missing
|
return missing
|
||||||
@@ -330,12 +337,46 @@ async function submitQualification() {
|
|||||||
ElMessage.warning(`请完整保存必填业务项:${missingRequiredPackageItems.value.join('、')}`)
|
ElMessage.warning(`请完整保存必填业务项:${missingRequiredPackageItems.value.join('、')}`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const pendingItems = qualificationDrafts.filter((item) => item.file)
|
const optionalItems = qualificationDrafts.filter((item) => !item.required && item.file)
|
||||||
qualificationSubmitting.value = true
|
qualificationSubmitting.value = true
|
||||||
let uploadedCount = 0
|
let uploadedCount = 0
|
||||||
const failures: string[] = []
|
const failures: string[] = []
|
||||||
try {
|
try {
|
||||||
for (const item of pendingItems) {
|
if (requiredPackageChangeSelected.value) {
|
||||||
|
const businessLicense = qualificationDraftItem('BUSINESS_LICENSE')
|
||||||
|
const legalPersonId = qualificationDraftItem('LEGAL_PERSON_ID')
|
||||||
|
for (const item of [businessLicense, legalPersonId]) {
|
||||||
|
if (!item) continue
|
||||||
|
item.error = ''
|
||||||
|
item.uploadState = 'uploading'
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await uploadMerchantRequiredQualificationPackage(
|
||||||
|
businessLicense?.file as File,
|
||||||
|
legalPersonId?.file as File,
|
||||||
|
normalizedQualificationCertificateNo.value,
|
||||||
|
Boolean(replaceableQualification('BUSINESS_LICENSE')),
|
||||||
|
Boolean(replaceableQualification('LEGAL_PERSON_ID')),
|
||||||
|
)
|
||||||
|
for (const item of [businessLicense, legalPersonId]) {
|
||||||
|
if (!item) continue
|
||||||
|
releaseQualificationPreview(item)
|
||||||
|
item.file = null
|
||||||
|
item.fileName = ''
|
||||||
|
item.fileSize = 0
|
||||||
|
item.fileType = ''
|
||||||
|
item.inputVersion += 1
|
||||||
|
item.uploadState = 'uploaded'
|
||||||
|
}
|
||||||
|
uploadedCount += 2
|
||||||
|
} catch (error) {
|
||||||
|
const message = qualificationUploadErrorMessage(error)
|
||||||
|
if (businessLicense) { businessLicense.error = message; businessLicense.uploadState = 'error' }
|
||||||
|
if (legalPersonId) { legalPersonId.error = message; legalPersonId.uploadState = 'error' }
|
||||||
|
failures.push(`必填资质包:${message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const item of failures.length ? [] : optionalItems) {
|
||||||
const file = item.file
|
const file = item.file
|
||||||
if (!file) continue
|
if (!file) continue
|
||||||
item.error = ''
|
item.error = ''
|
||||||
@@ -344,7 +385,7 @@ async function submitQualification() {
|
|||||||
await uploadMerchantQualification(
|
await uploadMerchantQualification(
|
||||||
file,
|
file,
|
||||||
item.value,
|
item.value,
|
||||||
item.value === 'BUSINESS_LICENSE' ? normalizedQualificationCertificateNo.value : undefined,
|
undefined,
|
||||||
Boolean(replaceableQualification(item.value)),
|
Boolean(replaceableQualification(item.value)),
|
||||||
)
|
)
|
||||||
releaseQualificationPreview(item)
|
releaseQualificationPreview(item)
|
||||||
|
|||||||
@@ -33,7 +33,10 @@ test('implements company details, contact information and qualification workflow
|
|||||||
assert.match(profileSource, /class="qualification-upload-grid"/)
|
assert.match(profileSource, /class="qualification-upload-grid"/)
|
||||||
assert.match(profileSource, /v-for="item in qualificationDrafts"/)
|
assert.match(profileSource, /v-for="item in qualificationDrafts"/)
|
||||||
assert.match(profileSource, /保存全部资质/)
|
assert.match(profileSource, /保存全部资质/)
|
||||||
assert.match(profileSource, /for \(const item of pendingItems\)/)
|
assert.match(profileSource, /uploadMerchantRequiredQualificationPackage/)
|
||||||
|
assert.match(profileSource, /const requiredPackageFilesSelected = computed/)
|
||||||
|
assert.match(profileSource, /item\.uploadState = 'uploading'/)
|
||||||
|
assert.match(profileSource, /for \(const item of failures\.length \? \[\] : optionalItems\)/)
|
||||||
assert.match(profileSource, /item\.fileName = ''[\s\S]*item\.uploadState = 'uploaded'/)
|
assert.match(profileSource, /item\.fileName = ''[\s\S]*item\.uploadState = 'uploaded'/)
|
||||||
assert.match(profileSource, /const canSaveQualificationBatch = computed/)
|
assert.match(profileSource, /const canSaveQualificationBatch = computed/)
|
||||||
assert.match(profileSource, /const qualificationCreditCodeReady = computed/)
|
assert.match(profileSource, /const qualificationCreditCodeReady = computed/)
|
||||||
|
|||||||
Reference in New Issue
Block a user