feat: 新增个人客户新增/编辑/查看表单页

main
halo 2026-07-09 21:38:59 +08:00
parent 96437e7bc4
commit 6c35fbebdd
1 changed files with 222 additions and 0 deletions

View File

@ -0,0 +1,222 @@
<!-- src/views/customer/personal/PersonalForm.vue -->
<template>
<div class="personal-customer-form-page">
<a-page-header :title="pageTitle" @back="goBack" />
<a-card>
<a-form ref="formRef" layout="vertical" :model="form" :rules="rules" :disabled="isDetail">
<a-form-item v-if="!isDetail" label="所属渠道" required>
<ChannelSelect v-model:model-value="channelNo" />
</a-form-item>
<a-form-item label="身份证照片" required>
<a-space size="large">
<IdCardUpload
side="front"
:channel-no="channelNo"
v-model:file-no="form.idCardFrontFileNo"
@ocr-result="handleOcrResult"
@ocr-status="(status) => (ocrStatus = status)"
/>
<IdCardUpload
side="back"
:channel-no="channelNo"
v-model:file-no="form.idCardBackFileNo"
@ocr-result="handleOcrResult"
@ocr-status="(status) => (ocrStatus = status)"
/>
</a-space>
</a-form-item>
<a-form-item label="所属机构" name="organizationId">
<OrgTreeSelect v-model="form.organizationId" placeholder="请选择所属机构" />
</a-form-item>
<a-form-item label="客户名称" name="customerName">
<a-input v-model:value="form.customerName" placeholder="请输入客户名称(OCR自动填充)" />
</a-form-item>
<a-form-item label="证件类型">
<a-input value="身份证" disabled />
</a-form-item>
<a-form-item label="证件号码" name="certificateNumber">
<a-input v-model:value="form.certificateNumber" placeholder="请输入证件号码(OCR自动填充)" />
</a-form-item>
<a-form-item label="出生日期" name="birthDate">
<a-date-picker v-model:value="birthDateValue" style="width: 100%" value-format="YYYY-MM-DD" />
</a-form-item>
<a-form-item label="性别" name="gender">
<a-select v-model:value="form.gender">
<a-select-option value="MALE"></a-select-option>
<a-select-option value="FEMALE"></a-select-option>
<a-select-option value="UNKNOWN">未知</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="民族" name="ethnicity">
<a-input v-model:value="form.ethnicity" placeholder="请输入民族(OCR自动填充)" />
</a-form-item>
<a-form-item label="证件生效日" name="certificateEffectiveDate">
<a-date-picker v-model:value="effectiveDateValue" style="width: 100%" value-format="YYYY-MM-DD" />
</a-form-item>
<a-form-item label="证件到期日" name="certificateExpiryDate">
<a-date-picker v-model:value="expiryDateValue" style="width: 100%" value-format="YYYY-MM-DD" />
<div v-if="isExpired" class="expired-hint">,</div>
</a-form-item>
<a-form-item label="签发机关" name="issuingAuthority">
<a-input v-model:value="form.issuingAuthority" placeholder="请输入签发机关(OCR自动填充)" />
</a-form-item>
<a-form-item label="证件地址" name="certificateAddress">
<a-input v-model:value="form.certificateAddress" placeholder="请输入证件地址(OCR自动填充)" />
</a-form-item>
<a-form-item label="职业" name="occupation">
<a-input v-model:value="form.occupation" placeholder="请输入职业" />
</a-form-item>
<a-form-item label="手机号码" name="mobilePhone">
<a-input v-model:value="form.mobilePhone" placeholder="请输入11位手机号" />
</a-form-item>
</a-form>
<a-space v-if="!isDetail">
<a-button type="primary" :loading="submitting" @click="handleSubmit"></a-button>
<a-button @click="goBack"></a-button>
</a-space>
</a-card>
</div>
</template>
<script setup>
import { ref, reactive, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
import ChannelSelect from '@/components/ChannelSelect.vue'
import IdCardUpload from '@/components/IdCardUpload.vue'
import OrgTreeSelect from '@/components/OrgTreeSelect.vue'
import {
fetchPersonalCustomerDetailApi,
createPersonalCustomerApi,
updatePersonalCustomerApi
} from '@/api/customer'
import { mapOcrIdCardResult } from '@/utils/ocrMapping'
import { phoneValidatorRule, idCardValidatorRule } from '@/utils/validators'
const route = useRoute()
const router = useRouter()
const mode = computed(() => route.query.mode || 'create')
const isDetail = computed(() => mode.value === 'detail')
const pageTitle = computed(() => ({ create: '新增个人客户', edit: '编辑个人客户', detail: '查看个人客户' }[mode.value]))
const channelNo = ref(undefined)
const ocrStatus = ref('PENDING')
const submitting = ref(false)
const formRef = ref(null)
const form = reactive({
id: '',
organizationId: undefined,
customerName: '',
certificateNumber: '',
birthDate: '',
gender: 'UNKNOWN',
ethnicity: '',
certificateEffectiveDate: '',
certificateExpiryDate: '',
issuingAuthority: '',
certificateAddress: '',
occupation: '',
mobilePhone: '',
idCardFrontFileNo: '',
idCardBackFileNo: ''
})
const birthDateValue = computed({
get: () => (form.birthDate ? dayjs(form.birthDate) : undefined),
set: (val) => (form.birthDate = val ? val.format('YYYY-MM-DD') : '')
})
const effectiveDateValue = computed({
get: () => (form.certificateEffectiveDate ? dayjs(form.certificateEffectiveDate) : undefined),
set: (val) => (form.certificateEffectiveDate = val ? val.format('YYYY-MM-DD') : '')
})
const expiryDateValue = computed({
get: () => (form.certificateExpiryDate ? dayjs(form.certificateExpiryDate) : undefined),
set: (val) => (form.certificateExpiryDate = val ? val.format('YYYY-MM-DD') : '')
})
const isExpired = computed(
() => !!form.certificateExpiryDate && dayjs(form.certificateExpiryDate).isBefore(dayjs(), 'day')
)
const rules = {
organizationId: [{ required: true, message: '请选择所属机构' }],
customerName: [{ required: true, message: '请输入客户名称' }],
certificateNumber: [idCardValidatorRule()],
mobilePhone: [phoneValidatorRule()]
}
function handleOcrResult(ocrData) {
Object.assign(form, mapOcrIdCardResult(ocrData))
}
async function loadDetail(id) {
const res = await fetchPersonalCustomerDetailApi({ id })
if (res.data.code === 200) {
Object.assign(form, res.data.data)
}
}
onMounted(() => {
if (route.query.id) {
loadDetail(route.query.id)
}
})
function goBack() {
router.push('/customer/personal')
}
async function handleSubmit() {
try {
await formRef.value.validate()
} catch {
return
}
submitting.value = true
try {
const toFullDate = (d) => (d ? `${d} 00:00:00` : undefined)
const payload = {
organizationId: form.organizationId,
customerName: form.customerName,
certificateType: 'ID_CARD',
certificateNumber: form.certificateNumber,
birthDate: toFullDate(form.birthDate),
gender: form.gender,
ethnicity: form.ethnicity,
certificateEffectiveDate: toFullDate(form.certificateEffectiveDate),
certificateExpiryDate: toFullDate(form.certificateExpiryDate),
issuingAuthority: form.issuingAuthority,
mobilePhone: form.mobilePhone,
certificateAddress: form.certificateAddress,
idCardFrontFileNo: form.idCardFrontFileNo,
idCardBackFileNo: form.idCardBackFileNo,
occupation: form.occupation,
ocrStatus: ocrStatus.value
}
const res =
mode.value === 'create'
? await createPersonalCustomerApi(payload)
: await updatePersonalCustomerApi({ id: form.id, ...payload })
if (res.data.code === 200) {
message.success(mode.value === 'create' ? '新增成功' : '修改成功')
goBack()
}
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.expired-hint {
color: #ff4d4f;
font-size: 12px;
margin-top: 4px;
}
</style>