fix(pl): 调整证件建档弹窗布局并按所选类型解析单个附件。

This commit is contained in:
wuzilong committed 2026-11-01 20:05:06 +08:00
1 parent c16681a16f
commit 4dbd48d827
4 files changed
+478 -32

No files matched your search

@@ -25,7 +25,7 @@
class="att-upload"
:auto-upload="false"
:show-file-list="false"
:multiple="true"
:multiple="limit > 1"
:accept="accept"
@change="handleAttachmentChange"
>
@@ -85,7 +85,7 @@
ref="uploadRef"
:auto-upload="false"
:show-file-list="false"
:multiple="true"
:multiple="limit > 1"
:accept="accept"
@change="handleAttachmentChange"
>
+195 -30
View File
@@ -104,10 +104,10 @@
</section>
<el-dialog
v-model="formVisible"
:title="`${editingId ? '编辑' : '新增'}${kind === 'purchase' ? '购买证' : '运输证'}`"
width="960px"
:title="editingId ? `编辑${formKind === 'purchase' ? '购买证' : '运输证'}` : '新增证件'"
width="1200px"
align-center
class="dialog-form-layout"
class="dialog-form-layout quota-cert-form-dialog"
:close-on-click-modal="false"
:close-on-press-escape="!saving"
:show-close="!saving"
@@ -117,47 +117,77 @@
ref="formRef"
:model="form"
:rules="rules"
:validate-on-rule-change="false"
label-position="top"
scroll-to-error
>
<section class="view-section">
<el-form-item label="建档方式"
><el-radio-group v-model="form.entryMode" :disabled="!!editingId"
<section class="view-section form-grid-2">
<el-form-item>
<template #label><span class="dialog-section-title">建档方式</span></template>
<el-radio-group v-model="form.entryMode" :disabled="!!editingId || recognizing"
><el-radio value="manual">手工录入(可后补图片)</el-radio
><el-radio value="image_parse">图片解析后核对</el-radio></el-radio-group
></el-form-item
>
<el-form-item label="证件附件"
><AttachmentUploadField
<el-form-item>
<template #label><span class="dialog-section-title">证件类型</span></template>
<el-radio-group
v-model="selectedFormKind"
:disabled="!!editingId"
@change="changeFormKind"
>
<el-radio value="purchase">购买证</el-radio>
<el-radio value="transport">运输证</el-radio>
</el-radio-group>
</el-form-item>
</section>
<section class="view-section">
<el-form-item>
<template #label><span class="dialog-section-title">证件附件</span></template>
<AttachmentUploadField
ref="certAttachments"
:key="editSession"
:disabled="saving"
:disabled="saving || recognizing"
v-model="form.attachmentUrls"
folder="pl/quota-cert"
:limit="editingId ? 15 : 1"
accept="image/*,.pdf"
:hint="
editingId
? '支持图片、PDF;每次仅解析一个附件'
: '支持图片、PDF;每次仅上传并解析一个附件,更换请先删除原附件'
"
@update:model-value="edited.add('attachmentUrls')"
/></el-form-item>
<template v-if="form.entryMode === 'image_parse' || !!form.attachmentUrls">
<el-select v-model="recognitionImage" placeholder="选择一张已上传的证件图片"
><el-option
v-for="url in (form.attachmentUrls || '').split(',').filter(Boolean)"
:key="url"
:value="url"
:label="url.split('/').pop()"
/></el-select>
<el-button :loading="recognizing" :disabled="!recognitionImage" @click="recognize"
><el-icon><Document /></el-icon>解析为候选字段</el-button
>
<div class="recognition-actions">
<el-select
v-if="editingId && attachmentOptions.length > 1"
v-model="recognitionImage"
:disabled="recognizing"
placeholder="选择一个附件进行解析"
><el-option
v-for="url in attachmentOptions"
:key="url"
:value="url"
:label="url.split('/').pop()"
/></el-select>
<el-button :loading="recognizing" :disabled="!recognitionImage" @click="recognize"
><el-icon><Document /></el-icon>解析{{
formKind === 'purchase' ? '购买证' : '运输证'
}}</el-button
>
</div>
<p class="form-tip"
>请核对证号、数量、单位、有效次数和引用关系。解析只补充未编辑的空字段。</p
>
</template>
</section>
<section class="view-section"
><h3>证面信息</h3
><div class="form-grid-2">
><h3 class="dialog-section-title">证面信息</h3
><div class="form-grid-3">
<el-form-item
v-for="field in fields"
v-for="field in formFields"
:key="field[0]"
:label="field[1]"
:prop="field[0]"
@@ -179,7 +209,7 @@
/>
</el-form-item> </div
></section>
<section v-if="kind === 'transport'" class="view-section">
<section v-if="formKind === 'transport'" class="view-section">
<el-form-item label="车牌号(多车共用额度)"
><el-select
v-model="form.vehicleNumbersJson"
@@ -189,7 +219,7 @@
default-first-option
@change="edited.add('vehicleNumbersJson')"
/></el-form-item>
<h3>证面引用</h3>
<h3 class="dialog-section-title">证面引用</h3>
<el-table :data="form.purchaseRefs" row-key="id" stripe>
<el-table-column label="引用类型" min-width="160"
><template #default="{ row }"
@@ -397,6 +427,8 @@
} from './model'
const route = useRoute()
const kind = ref<CertType>('purchase')
const formKind = ref<CertType>('purchase')
const selectedFormKind = ref<CertType>('purchase')
const keyword = ref(''),
page = ref(1)
const rows = ref<QuotaCert[]>([]),
@@ -430,6 +462,24 @@
...commonFields,
...(kind.value === 'purchase' ? purchaseFields : transportFields)
])
const formFields = computed(() => [
...commonFields,
...(formKind.value === 'purchase' ? purchaseFields : transportFields)
])
const attachmentOptions = computed<string[]>(() =>
(form.value.attachmentUrls || '')
.split(',')
.map((url: string) => url.trim())
.filter(Boolean)
)
watch(
attachmentOptions,
(urls) => {
if (urls.length === 1) recognitionImage.value = urls[0]
else if (!urls.includes(recognitionImage.value)) recognitionImage.value = ''
},
{ flush: 'sync' }
)
const statusLabels = { draft: '待核对', active: '已启用', suspended: '已停用', void: '已作废' }
const refLabels = {
@@ -455,7 +505,7 @@
'totalQty',
'quantityUnit',
'validUseText',
...(kind.value === 'purchase' ? ['buyerName', 'sellerName'] : ['shipperName'])
...(formKind.value === 'purchase' ? ['buyerName', 'sellerName'] : ['shipperName'])
].map((k) => [k, [{ required: true, message: '请填写此项', trigger: 'blur' }]])
)
)
@@ -507,6 +557,8 @@
const options = targetKind === 'transport' ? await quotaCertApi.list('purchase') : []
if (session !== editSession || kind.value !== targetKind) return
editingId.value = row?.id || ''
formKind.value = targetKind
selectedFormKind.value = targetKind
edited.clear()
recognitionImage.value = ''
form.value = value
@@ -514,6 +566,54 @@
recognizing.value = false
formVisible.value = true
}
async function changeFormKind(value: string | number | boolean | undefined) {
try {
if (
(value !== 'purchase' && value !== 'transport') ||
value === formKind.value ||
editingId.value ||
saving.value
)
return
if (certAttachments.value?.hasUploading()) {
ElMessage.warning('请等待附件上传完成')
return
}
const session = editSession
const hasContent =
formFields.value.some(([key]) => form.value[key] != null && form.value[key] !== '') ||
form.value.purchaseRefs?.length ||
form.value.vehicleNumbersJson?.length
if (hasContent) {
try {
await ElMessageBox.confirm(
'切换证件类型将清空证面信息,保留建档方式、附件和系统备注。是否继续?',
'切换证件类型',
{ type: 'warning' }
)
} catch {
return
}
}
if (session !== editSession || !formVisible.value || saving.value) return
const nextSession = ++editSession
recognizing.value = false
const { entryMode, attachmentUrls, remark, orderNum } = form.value
formKind.value = value
selectedFormKind.value = value
form.value = { ...newCert(value), entryMode, attachmentUrls, remark, orderNum }
purchases.value = []
edited.clear()
formRef.value?.clearValidate()
if (value === 'transport') {
const options = await quotaCertApi.list('purchase')
if (nextSession === editSession && formVisible.value && formKind.value === value)
purchases.value = options
}
} finally {
selectedFormKind.value = formKind.value
}
}
async function save() {
if (saving.value) return
if (certAttachments.value?.hasUploading()) {
@@ -521,6 +621,7 @@
return
}
const session = editSession
const targetKind = formKind.value
saving.value = true
if (!(await formRef.value.validate().catch(() => false)) || session !== editSession) {
saving.value = false
@@ -528,21 +629,34 @@
}
try {
await quotaCertApi.save(
kind.value,
certPayload(kind.value, form.value),
targetKind,
certPayload(targetKind, form.value),
editingId.value || undefined
)
formVisible.value = false
ElMessage.success('证件已保存')
await load()
if (kind.value !== targetKind) kind.value = targetKind
else await load()
} finally {
saving.value = false
}
}
async function recognize() {
if (recognizing.value || saving.value) return
if (certAttachments.value?.hasUploading()) {
ElMessage.warning('请等待附件上传完成')
return
}
if (
!recognitionImage.value ||
!attachmentOptions.value.includes(recognitionImage.value) ||
(!editingId.value && attachmentOptions.value.length !== 1)
) {
ElMessage.warning('请选择一个已上传的证件附件')
return
}
const session = editSession,
targetKind = kind.value,
targetKind = formKind.value,
targetImage = recognitionImage.value
recognizing.value = true
try {
@@ -550,12 +664,15 @@
if (
session !== editSession ||
!formVisible.value ||
kind.value !== targetKind ||
formKind.value !== targetKind ||
recognitionImage.value !== targetImage ||
!(form.value.attachmentUrls || '').split(',').includes(targetImage)
)
return
mergeCandidates(form.value as any, result.candidates, edited)
await nextTick()
if (session !== editSession || !formVisible.value) return
formRef.value?.clearValidate()
ElMessage.success(
Object.keys(result.invalidFields).length
? '部分字段未识别,请手工核对补全'
@@ -727,3 +844,51 @@
tableRef
})
</script>
<style lang="scss">
.quota-cert-form-dialog {
max-width: calc(100vw - 40px);
--dialog-viewport-inset: 50px;
--dialog-max-height: calc(100vh - 100px);
.view-section {
padding: 0 0 20px;
}
.dialog-section-title {
margin: 0 0 14px;
padding-left: 8px;
border-left: 3px solid var(--section-bar-border-color);
color: var(--section-bar-title-color);
font-size: 14px;
font-weight: 600;
}
span.dialog-section-title {
margin-bottom: 0;
}
.form-grid-2,
.form-grid-3 {
.el-form-item {
min-width: 0;
margin-bottom: 0;
}
}
.el-date-editor.el-input {
width: 100%;
}
.recognition-actions {
display: flex;
flex-wrap: wrap;
gap: 12px;
.el-select {
width: 320px;
max-width: 100%;
}
}
}
</style>