<script lang="ts" setup>
|
import type { ErpPurchaseInvoiceAiApi } from '#/api/erp/purchase/invoice/ai';
|
|
import { ref } from 'vue';
|
|
import { IconifyIcon } from '@vben/icons';
|
|
import {
|
Alert,
|
Button,
|
Descriptions,
|
Modal,
|
Tag,
|
Upload,
|
message,
|
} from 'ant-design-vue';
|
|
import { ocrPurchaseInvoice } from '#/api/erp/purchase/invoice/ai';
|
import { uploadFile } from '#/api/system/storage';
|
|
defineOptions({ name: 'ErpPurchaseInvoiceOcrUpload' });
|
|
const emit = defineEmits<{
|
success: [data: ErpPurchaseInvoiceAiApi.OcrResultVO];
|
}>();
|
|
const open = ref(false);
|
const step = ref(0); // 0=upload, 1=processing, 2=preview
|
const fileList = ref<any[]>([]);
|
const ocrResult = ref<ErpPurchaseInvoiceAiApi.OcrResultVO>();
|
const processingTip = ref('');
|
|
function handleOpen() {
|
open.value = true;
|
step.value = 0;
|
fileList.value = [];
|
ocrResult.value = undefined;
|
}
|
|
/** 自定义上传 */
|
async function handleUpload(options: any) {
|
const { file, onSuccess, onError } = options;
|
try {
|
const result = await uploadFile([file as File]);
|
const blobId = result?.[0]?.id;
|
if (!blobId) {
|
onError(new Error('上传失败'));
|
return;
|
}
|
onSuccess({ blobId }, file);
|
// 开始 OCR 识别
|
step.value = 1;
|
processingTip.value = '正在进行发票识别,请稍候...';
|
const ocrData = await ocrPurchaseInvoice(blobId);
|
// 检查错误:rawText 为错误提示,且业务字段全空
|
const hasData = !!(
|
ocrData.invoiceNo ||
|
ocrData.invoiceTitle ||
|
ocrData.supplierName ||
|
ocrData.price != null ||
|
ocrData.invoiceTime ||
|
ocrData.invoiceType ||
|
ocrData.taxRate != null ||
|
ocrData.remark
|
);
|
if (ocrData.rawText && !hasData) {
|
processingTip.value = '';
|
step.value = 0;
|
message.error(ocrData.rawText);
|
return;
|
}
|
if (!hasData) {
|
processingTip.value = '';
|
step.value = 0;
|
message.warning('未识别到发票数据,请手动录入或重新上传');
|
return;
|
}
|
ocrResult.value = ocrData;
|
step.value = 2;
|
} catch {
|
processingTip.value = '';
|
step.value = 0;
|
onError(new Error('上传失败'));
|
}
|
}
|
|
function handleConfirm() {
|
if (ocrResult.value) {
|
emit('success', ocrResult.value);
|
}
|
handleClose();
|
}
|
|
function handleReset() {
|
step.value = 0;
|
fileList.value = [];
|
ocrResult.value = undefined;
|
processingTip.value = '';
|
}
|
|
function handleClose() {
|
open.value = false;
|
handleReset();
|
}
|
|
defineExpose({ open: handleOpen });
|
</script>
|
|
<template>
|
<Modal
|
v-model:open="open"
|
title="识别录入发票"
|
width="680px"
|
:footer="null"
|
@cancel="handleClose"
|
>
|
<!-- Step 0: 上传 -->
|
<template v-if="step === 0">
|
<Upload.Dragger
|
v-model:file-list="fileList"
|
:max-count="1"
|
:custom-request="handleUpload"
|
accept=".pdf,.png,.jpg,.jpeg"
|
@remove="handleReset"
|
>
|
<p class="text-4xl text-gray-400">
|
<IconifyIcon icon="ant-design:inbox-outlined" />
|
</p>
|
<p class="text-base text-gray-500">点击或拖拽发票文件到此区域上传</p>
|
<p class="text-sm text-gray-400">支持 PDF、JPG、PNG 格式,与来票附件上传格式一致</p>
|
</Upload.Dragger>
|
</template>
|
|
<!-- Step 1: 识别中 -->
|
<template v-else-if="step === 1">
|
<div class="flex flex-col items-center gap-4 py-12">
|
<a-spin size="large" />
|
<span class="text-base text-gray-500">{{ processingTip }}</span>
|
</div>
|
</template>
|
|
<!-- Step 2: 预览结果 -->
|
<template v-else-if="step === 2 && ocrResult">
|
<div class="mb-4">
|
<Descriptions :column="2" bordered size="small">
|
<Descriptions.Item v-if="ocrResult.invoiceNo" label="发票号码">
|
{{ ocrResult.invoiceNo }}
|
</Descriptions.Item>
|
<Descriptions.Item v-if="ocrResult.invoiceType" label="发票类型">
|
{{ ocrResult.invoiceType }}
|
</Descriptions.Item>
|
<Descriptions.Item v-if="ocrResult.invoiceTitle" label="发票抬头">
|
{{ ocrResult.invoiceTitle }}
|
</Descriptions.Item>
|
<Descriptions.Item v-if="ocrResult.supplierName" label="销售方(需核对)">
|
<Tag color="orange">{{ ocrResult.supplierName }}</Tag>
|
</Descriptions.Item>
|
<Descriptions.Item v-if="ocrResult.price != null" label="来票金额(元)">
|
{{ ocrResult.price }}
|
</Descriptions.Item>
|
<Descriptions.Item v-if="ocrResult.invoiceTime" label="开票日期">
|
{{ ocrResult.invoiceTime }}
|
</Descriptions.Item>
|
<Descriptions.Item v-if="ocrResult.taxRate != null" label="税率(%)">
|
{{ ocrResult.taxRate }}
|
</Descriptions.Item>
|
<Descriptions.Item v-if="ocrResult.remark" label="备注" :span="2">
|
{{ ocrResult.remark }}
|
</Descriptions.Item>
|
</Descriptions>
|
</div>
|
|
<Alert
|
message="销售方名称仅为发票文本识别结果,请在表单中选择采购订单后核对订单供应商与销售方是否一致;识别数据可手动修改。"
|
type="info"
|
show-icon
|
class="mb-4"
|
/>
|
|
<div class="flex justify-end gap-2">
|
<Button @click="handleReset">重新上传</Button>
|
<Button type="primary" @click="handleConfirm">确认并填入表单</Button>
|
</div>
|
</template>
|
</Modal>
|
</template>
|