由于业务需求需要做一个发票识别内容,然后就有了这个文章。首先这个思路是识别图片上的二维码信息,扫码二维码之后,我们就可以得到发票的基础信息:1、发票类型;2、发票代码;3、发票号码;4、发票效验码;5、发票开具时间;6、发票金额。这些信息就是发票二维码包含的所有的信息了。
其中使用的前端插件:qrcode-decoder,pdfjs-dist(如果你不考虑识别pdf的内容也可以忽略此内容)。
- 监听文件发生改变
html"><el-upload
class="upload-demo mt5"
style="height: 268px; width: 99%"
drag
:show-file-list="false"
:auto-upload="false"
action="#"
method="POST"
ref="fileUploadRef"
a***ept=".pdf,.jpg,.png,.jpeg"
:on-change="fileChange"
>
<el-icon class="el-icon--upload" style="font-size: 90px; line-height: 90px">
<upload-filled />
</el-icon>
<div class="el-upload__text">拖拽或点击上传发票 <em>仅能自动识别支持带有二维码的发票</em></div>
<div class="el-upload__text">支持格式:pdf、jpg、png等文件</div>
</el-upload>
2.监听方法
async function fileChange(rawFile) {
let fileTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/jpg', 'application/pdf'];
if (!fileTypes.includes(rawFile.raw.type)) {
console.log('存在不支持的格式');
return false;
}
// ts写法 可换成this
imgUrl.value = null;
pdfUrl.value = null;
// 可以用于在浏览器上预览本地图片或者视频,文件
let fileUrl = getObjectURL(rawFile.raw);
// 判断是否是pdf,如果是pdf就需要转成图片在扫码
if (rawFile.raw.type === 'application/pdf') {
pdfUrl.value = fileUrl;
await pdfToImg(rawFile.raw, img => {
dealScanQrCode(img);
});
} else {
// 如果是图片就直接扫描二维码
imgUrl.value = fileUrl;
dealScanQrCode(imgSrc.value);
}
return true;
}
3.封装扫描二维码
// 扫描识别二维码的方法封装
function dealScanQrCode(imgSrc) {
// 具体的扫描方法
scanQrCode(null, imgSrc, data => {
let dealCode = [];
// data 是返回的数据 判断data是否有异常
if (data) {
//解析后的数据是01,10,044002015551,94449434,548.13,20221117,7649489616555713831,7W7C,
dealCode = data.split(',');
// 如果分个后小于6说明不是发票信息
if (dealCode.length < 6) {
dealCode = [];
}
}
// [0]占位符 [1]、发票类型{01增值税专用发票,04增值税普通发票,10增值税普通发票(电子),08增值税专用发票(电子)};
// [2]、发票代码;[3]、发票号码;[4]开票金额;[5]、发票开具时间;[6]表示发票校验码,增值税专用发票是没有发票校验码的,没有则为空字符串;
// 解析数据
let row ={};
// 发票类型
row .invoiceType = dealCode[1] || null;
// 发票代码
row.invoiceCode = dealCode[2] || null;
// 发票号码
row.invoiceNo = dealCode[3] || null;
let invoiceTotal = dealCode[4] || null;
if (invoiceTotal) {
invoiceTotal = parseFloat(invoiceTotal);
}
// 开票金额
row.invoiceTotal = invoiceTotal;
// 这里是价税合计的算法 首先你得有税率 一般都是13%
let invoiceTaxRatio = row.invoiceTaxRatio || null;
if (invoiceTaxRatio && invoiceTaxRatio > 0 && invoiceTotal) {
// costInvoiceTotal价税合计
let num = invoiceTaxRatio / 100;
let invoiceTaxTotal = (invoiceTotal * num).toFixed(2);
row.costInvoiceTotal = parseFloat(invoiceTotal) + parseFloat(invoiceTaxTotal);
// invoiceTaxTotal税额
row.invoiceTaxTotal = parseFloat(invoiceTaxTotal);
}
// 发票开具时间
let invoiceTime = dealCode[5] || null;
if (invoiceTime) {
// 转换开票金额
invoiceTime = invoiceTime.replace(/^(\d{4})(\d{2})(\d{2})$/, '$1-$2-$3');
}
row.invoiceTime = invoiceTime;
});
}
4.封装工具 utils.js
// 扫码插件
import QrCode from 'qrcode-decoder';
// pdf转图片
import * as pdfJs from 'pdfjs-dist';
// pdfworker地址
import * as pdfWorkerMin from 'pdfjs-dist/build/pdf.worker.min?url';
// 这里要初始化一下路由防止异常,不然转换的时候会报错
pdfJs.GlobalWorkerOptions.workerSrc = pdfWorkerMin.default;
/**
* 文件转成预览链接
* @param file
* @returns {null}
*/
export const getObjectURL = file => {
let url = null;
if (window.createObjectURL !== undefined) {
// basic
url = window.createObjectURL(file);
} else if (window.URL !== undefined) {
// mozilla(firefox)
url = window.URL.createObjectURL(file);
} else if (window.webkitURL !== undefined) {
// webkit or chrome
url = window.webkitURL.createObjectURL(file);
}
return url;
};
/**
* 识别二维码
* @param file 文件
* @param url 地址
* @param callback 回调函数
*/
export const scanQrCode = (file, url, callback) => {
// 获取临时路径
let tempUrl = url || '';
if (file) {
tempUrl = getObjectURL(file);
}
// 初始化
const qr = new QrCode();
// 解析二维码,返回promise
qr.decodeFromImage(tempUrl).then(res => {
if (callback) {
callback(res.data);
}
});
};
/**
* pdf转图片
* @param file 文件信息
* @param callBack 回调函数
*/
export const pdfToImg = (file, callBack) => {
// 文件转获取pdf地址
let url = getObjectURL(file);
//这里是重点,然后将流数据转换为url,CMapReaderFactory方法在进行处理
pdfJs.getDocument(url).promise.then(pdf => {
// pdf多个文件的情况下
for (let num = 1; num <= pdf.numPages; num++) {
// 分页查询
pdf.getPage(num).then(async page => {
// 获取pdf具体数据
const viewport = page.getViewport({ scale: 1.0 });
// 创建画布
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
// 处理画布高宽
canvas.height = viewport.height;
canvas.width = viewport.width;
// 读取数据进行画布转换
page.render({ canvasContext: context, viewport: viewport }).promise.then(() => {
if (callBack) {
// 出发回调函数
callBack(canvas.toDataURL('image/jpeg'));
// 释放资源
canvas.remove();
}
});
});
}
});
};
注意!注意!注意 !
这里pdf转图片的时候是没有处理字符库的(会抛出异常信息,不影响操作),转换的后的发票图片是空白的没有具体的内容但是有二维码信息,如果你需要处理的话,请自行查询方法。
异常截图:
Warning: loadFont - translateFont failed: "UnknownErrorException: The CMap "baseUrl" parameter must be specified, ensure that the "cMapUrl" and "cMapPacked" API parameters are provided.".
5.预览
<!-- 图片直接用element的图片插件-->
<el-image v-if="imgUrl" :src="imgUrl" style="width: 100%; height: 268px" fit="contain">
<template #placeholder>
<div class="image-slot">加载中<span class="dot">...</span></div>
</template>
</el-image>
<!-- pdf直接调用浏览器的预览-->
<iframe v-if="pdfUrl" :src="pdfUrl + `#toolbar=0&zoom=50 `" width="100%" height="268" frameborder="0" />
- 图片直接用element的图片插件
- pdf直接调用浏览器的预览 这里携带了参数信息toolbar=0&zoom=50 ,toolbar=0代表不需要工具栏,zoom=50 是代表缩放了50%
End;
由于项目实际应用中,发现识别成功率过低,而且扫描件经常识别不到二维码,或者带有icon的二维码,识别率也惨不忍睹,后面引入了opencv-js-qrcode进行再次优化提升,提高了识别率。
vue,js,html 根据 opencv-js-qrcode 识别发票二维码信息-CSDN博客