159 lines
4.4 KiB
Vue
159 lines
4.4 KiB
Vue
<!-- src/components/IdCardUpload.vue -->
|
|
<template>
|
|
<div class="id-card-upload">
|
|
<a-upload
|
|
list-type="picture-card"
|
|
:show-upload-list="false"
|
|
:before-upload="handleBeforeUpload"
|
|
:disabled="!channelNo || uploading"
|
|
>
|
|
<div v-if="previewUrl" class="thumb-wrapper">
|
|
<img :src="previewUrl" class="preview-img" alt="预览" />
|
|
<div class="hover-mask">
|
|
<EyeOutlined class="mask-icon" title="预览" @click.stop="handlePreview" />
|
|
<template v-if="!readonly">
|
|
<span class="mask-divider"></span>
|
|
<EditOutlined class="mask-icon" title="重新上传" />
|
|
<span class="mask-divider"></span>
|
|
<DeleteOutlined class="mask-icon" title="删除" @click.stop="handleDelete" />
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<LoadingOutlined v-if="uploading" />
|
|
<PlusOutlined v-else />
|
|
<div class="upload-text">{{ label }}</div>
|
|
</div>
|
|
</a-upload>
|
|
<div v-if="!channelNo" class="hint">请先选择所属渠道</div>
|
|
|
|
<a-modal v-model:open="previewVisible" title="证件照片预览" :footer="null" width="480px">
|
|
<img :src="previewUrl" alt="预览" style="width: 100%; display: block" />
|
|
</a-modal>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, watch } from 'vue'
|
|
import { showErrorModal } from '@/utils/errorModal'
|
|
import { PlusOutlined, LoadingOutlined, EyeOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons-vue'
|
|
import { ocrIdCardApi, uploadCustomerFileApi } from '@/api/customer'
|
|
|
|
const props = defineProps({
|
|
side: { type: String, required: true }, // 'front'人像面 | 'back'国徽面
|
|
channelNo: { type: String, default: undefined },
|
|
// 只读态(查看详情):图片区域只保留预览,隐藏重新上传/删除操作
|
|
readonly: { type: Boolean, default: false }
|
|
})
|
|
const emit = defineEmits(['update:fileNo', 'ocr-result', 'ocr-status', 'update:uploading'])
|
|
|
|
const uploading = ref(false)
|
|
const previewUrl = ref('')
|
|
const previewVisible = ref(false)
|
|
watch(uploading, (val) => emit('update:uploading', val))
|
|
const label = computed(() => (props.side === 'front' ? '上传身份证人像面' : '上传身份证国徽面'))
|
|
const fileType = computed(() => (props.side === 'front' ? '01' : '02'))
|
|
|
|
function readAsBase64(file) {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader()
|
|
reader.onload = () => resolve(reader.result)
|
|
reader.onerror = reject
|
|
reader.readAsDataURL(file)
|
|
})
|
|
}
|
|
|
|
async function handleBeforeUpload(file) {
|
|
if (!props.channelNo) {
|
|
showErrorModal('请先选择所属渠道')
|
|
return false
|
|
}
|
|
uploading.value = true
|
|
try {
|
|
const fileBase64 = await readAsBase64(file)
|
|
previewUrl.value = fileBase64
|
|
const [ocrRes, uploadRes] = await Promise.all([
|
|
ocrIdCardApi({ fileBase64, channelNo: props.channelNo, fileType: fileType.value }, props.channelNo),
|
|
uploadCustomerFileApi({ fileBase64, channelNo: props.channelNo }, props.channelNo)
|
|
])
|
|
if (uploadRes.data.code === 200) {
|
|
emit('update:fileNo', uploadRes.data.data.fileNo)
|
|
}
|
|
if (ocrRes.data.code === 200 && ocrRes.data.data && Object.keys(ocrRes.data.data).length > 0) {
|
|
emit('ocr-result', ocrRes.data.data)
|
|
emit('ocr-status', 'SUCCESS')
|
|
} else {
|
|
showErrorModal('识别失败,请手动填写')
|
|
emit('ocr-status', 'FAILED')
|
|
}
|
|
} catch (e) {
|
|
showErrorModal('识别失败,请手动填写')
|
|
emit('ocr-status', 'FAILED')
|
|
} finally {
|
|
uploading.value = false
|
|
}
|
|
return false
|
|
}
|
|
|
|
function handlePreview() {
|
|
previewVisible.value = true
|
|
}
|
|
|
|
function handleDelete() {
|
|
previewUrl.value = ''
|
|
emit('update:fileNo', '')
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.id-card-upload {
|
|
display: inline-block;
|
|
}
|
|
.preview-img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
.thumb-wrapper {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
.hover-mask {
|
|
position: absolute;
|
|
inset: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
opacity: 0;
|
|
transition: opacity 0.2s;
|
|
}
|
|
.thumb-wrapper:hover .hover-mask {
|
|
opacity: 1;
|
|
}
|
|
.mask-icon {
|
|
color: #fff;
|
|
font-size: 16px;
|
|
cursor: pointer;
|
|
}
|
|
.mask-icon:hover {
|
|
color: #1f5f4e;
|
|
}
|
|
.mask-divider {
|
|
width: 1px;
|
|
height: 14px;
|
|
background: rgba(255, 255, 255, 0.5);
|
|
}
|
|
.upload-text {
|
|
margin-top: 8px;
|
|
font-size: 12px;
|
|
}
|
|
.hint {
|
|
color: #ff4d4f;
|
|
font-size: 12px;
|
|
margin-top: 4px;
|
|
}
|
|
</style>
|