123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467 |
- <!-- -->
- <template>
- <el-row :gutter="20">
- <el-col :span="12" class="place">
- <el-row class="small-title">
- <h2>通用表格识别</h2>
- </el-row>
- <el-row style="color: gray; font-size: small;">
- <p>支持识别图片/PDF格式文档中的表格内容,包括有线表格、无线表格、合并单元格表格,同时支持单张图片内的多个表格内容识别,返回各表格的表头表尾内容、单元格文字内容及其行列位置信息。</p>
- </el-row>
- <el-row style="color: gray; font-size: small;">
- <h4>支持jpg, png, bmp, pdf格式</h4>
- </el-row>
- <div class="common-layout">
- <el-container>
- <el-input type="file" v-model="fileName" @change="uploadImg"></el-input>
- <OcrLangList />
- <el-button type="primary" @click="predict">Predict</el-button>
- </el-container>
- </div>
- <el-row v-show="!is_pdf">
- <!-- 用于展示图片 -->
- <img id="show-img" class="show-area" />
- <!-- 用于存放真实图片进行文字识别 -->
- <img id="raw-img" style="display: none" />
- </el-row>
- <div class="pdf-preview" v-show="is_pdf">
- <div class="pdf-wrap">
- <vue-pdf-embed :source="state.source" :scale="3.0" class="vue-pdf-embed" :page="state.pageNum" />
- </div>
- <div class="page-tool">
- <div class="page-tool-item" @click="prePage">上一页</div>
- <div class="page-tool-item" @click="nextPage">下一页</div>
- <div class="page-tool-item">{{ state.pageNum }}/{{ state.numPages }}</div>
- <!-- <div class="page-tool-item" @click="pageZoomOut">放大</div>
- <div class="page-tool-item" @click="pageZoomIn">缩小</div> -->
- <el-input type="number" v-model="pdf_page"></el-input>
- <el-button type="primary" @click="SetPdfPage" plain>页面跳转</el-button>
- </div>
- </div>
- <canvas id="canvas" style="display: none;"></canvas>
- </el-col>
- <el-col :span="12" class="place">
- <el-row class="small-title">
- <h2 style="margin-right: 100;">识别结果展示</h2>
- <el-button type="danger" @click="submitBug" disabled>提交bug</el-button>
- </el-row>
- <el-row v-loading="loading" class="small-title">
- <!-- <p v-if="loading">处理中</p> -->
- </el-row>
- <div class="demo-collapse">
- <el-collapse v-model="activeName" accordion>
- <el-collapse-item title="JSON识别结果" name="1">
- <li v-for="(item, index) in jsonArr" :key="index">
- <el-scrollbar height="600px">
- {{ item }}
- </el-scrollbar>
- </li>
- </el-collapse-item>
- <el-collapse-item title="识别结果" name="2">
- <li v-for="(item, index) in htmlArr" :key="index">
- <el-scrollbar height="auto">
- <div style="display: flex;">
- <span v-html="item"></span>
- </div>
- </el-scrollbar>
- <!-- <div>
- <el-button type="info" :icon="DocumentCopy" @click="copy(index)" v-on:mouseover="showText()" v-on:mouseout="hideText()" plain />
- </div> -->
- </li>
- </el-collapse-item>
- </el-collapse>
- </div>
- <el-row>
- <section> 耗时:{{ predictTime }} ms.</section>
- </el-row>
- </el-col>
- </el-row>
- </template>
- <script lang='ts' setup>
- import { reactive, ref, toRefs, computed } from 'vue'
- import { onMounted } from "vue";
- import { TableRec, SubmitBug } from '../../../../api/api'
- import VuePdfEmbed from "vue-pdf-embed";
- import { createLoadingTask } from "vue3-pdfjs/esm"; // 获得总页数
- // import { useServerIpStore } from '../../../../store/ServerIp';
- import { storeToRefs } from 'pinia'
- import OcrLangList from '../../../../components/OcrLangList.vue'
- import { useOcrLangStore } from '../../../../store/OcrLang';
- import { useBugIdStore } from '../../../../store/BugID';
- import useClipboard from 'vue-clipboard3';
- import {
- DocumentCopy,
- } from '@element-plus/icons-vue'
- const ol = useOcrLangStore();
- const { ocr_lang } = storeToRefs(ol);
- const bi = useBugIdStore();
- const { bug_id } = storeToRefs(bi);
- // const si = useServerIpStore()
- // const { server_ip } = storeToRefs(si);
- const state = reactive({
- source: "", //预览pdf文件地址
- pageNum: 1, //当前页面
- // scale: 4, // 缩放比例
- numPages: 0, // 总页数
- });
- let loading = ref(false)
- let predictTime = ref(0)
- const fileName = ref(null);
- const pdf_page = ref(1);
- const is_pdf = ref(false);
- const activeName = ref('2')
- const pdf_img: any = ref("")
- const canvas = ref(null as unknown as HTMLCanvasElement);
- let jsonArr: any = ref([])
- let htmlArr: any = ref([])
- let loadingTask: any;
- onMounted(async () => {
- canvas.value = document.getElementById("canvas") as HTMLCanvasElement;
- });
- const uploadImg = () => {
- /**
- * 这里由于操作是绑定在 el-input 上;因此需要在内部重新获取 input 再拿到 file
- */
- const reader = new FileReader();
- // 用于展示
- const showImg = document.getElementById("show-img") as HTMLImageElement;
- // 用于识别
- const rawImg = document.getElementById("raw-img") as HTMLImageElement;
- const inputElement = document
- .getElementsByClassName("el-input")[0]
- .getElementsByTagName("input")[0];
- try {
- const file = inputElement.files![0];
- reader.onload = () => {
- // console.log(file.name.substring(file.name.lastIndexOf("."), file.name.length))
- const post_ = file.name.substring(file.name.lastIndexOf("."), file.name.length).toLowerCase();
- if (post_ == ".pdf") {
- state.pageNum = 1;
- // state.scale = 4;
- state.source = URL.createObjectURL(file);
- is_pdf.value = true
- loadingTask = createLoadingTask(state.source);
- getPdfImage(state.pageNum);
- } else if (post_ == ".jpg" || post_ == ".png" || post_ == ".bmp") {
- showImg.src = URL.createObjectURL(file);
- rawImg.src = URL.createObjectURL(file);
- is_pdf.value = false
- } else {
- alert('不支持的文件格式!')
- fileName.value = null
- }
- };
- reader.readAsDataURL(file);
- } catch (err) {
- console.error(err);
- }
- };
- const predict = async () => {
- if (fileName.value == undefined) {
- alert('请上传图片!')
- return;
- }
- if (ocr_lang.value == undefined || ocr_lang.value == "") {
- alert('请先指定语言!')
- return;
- }
- const inputElement = document
- .getElementsByClassName("el-input")[0]
- .getElementsByTagName("input")[0];
- const file = inputElement.files![0];
- loading.value = true
- var data = new FormData();
- data.append('lang', ocr_lang.value);
- // console.log('is_pdf' + is_pdf.value)
- if (is_pdf.value) {
- // console.log(res.code)
- // console.log(pdf_img)
- const file: any = convertCanvasToFile(canvas.value, "pdf.png").then(result => {
- // console.log(result)
- data.append('images', result);
- TableRec(data).then(res => {
- console.log(res.code)
- bug_id.value = res.response_id;
- predictTime.value = res.data.cost;
- jsonArr.value.splice(0);
- let tmp_json = res.data.json_items;
- if (tmp_json.length == 0) {
- alert('未检测到结果!');
- } else {
- for (let i = 0; i < tmp_json.length; i++) {
- jsonArr.value.push(JSON.stringify(tmp_json[i], null, 4));
- }
- htmlArr.value.splice(0);
- let tmp_html = res.data.html_items;
- for (let i = 0; i < tmp_html.length; i++) {
- htmlArr.value.push(tmp_html[i]);
- }
- }
- loading.value = false
- }).catch(function (err) {
- loading.value = false
- bug_id.value = ""
- predictTime.value = 0
- });
- })
- // console.log(file)
- // data.append('images', file.File);
- } else {
- // console.log(file)
- data.append('images', file);
- TableRec(data).then(res => {
- console.log(res.code)
- bug_id.value = res.response_id;
- predictTime.value = res.data.cost;
- jsonArr.value.splice(0);
- let tmp_json = res.data.json_items;
- for (let i = 0; i < tmp_json.length; i++) {
- jsonArr.value.push(JSON.stringify(tmp_json[i], null, 4));
- }
- htmlArr.value.splice(0);
- let tmp_html = res.data.html_items;
- for (let i = 0; i < tmp_html.length; i++) {
- htmlArr.value.push(tmp_html[i]);
- }
- loading.value = false
- }).catch(function (err) {
- loading.value = false
- bug_id.value = ""
- predictTime.value = 0
- });
- }
- };
- function SetPdfPage() {
- if (pdf_page.value >= 1 && pdf_page.value <= state.numPages) {
- console.log(pdf_page.value)
- state.pageNum = Number(pdf_page.value);
- getPdfImage(state.pageNum);
- } else {
- console.log(pdf_page.value)
- alert('输入的pdf页面无效!')
- }
- }
- let copy_text = ref("click this button to copy the html table");
- function showText() {
- copy_text.value = "click it!";
- }
- function hideText() {
- copy_text.value = "click this button to copy the html table";
- }
- const submitBug = async () => {
- if (bug_id.value == undefined || bug_id.value == "") {
- alert('请先预测结果!')
- return;
- }
- SubmitBug(bug_id.value).then(res => {
- console.log(res.code, res.data)
- }).catch(function (err) {
- console.log(err)
- // loading.value = !loading.value;
- });
- };
- async function convertCanvasToFile(canvas: HTMLCanvasElement, fileName: any) {
- // 将 Canvas 转为 Blob 对象
- const blob = await new Promise(resolve => canvas.toBlob(blob => {
- resolve(blob);
- }, pdf_img.value.type, 1.0));
- // 手动构造 File 对象
- let file = null;
- try {
- file = new File([pdf_img.value], fileName, { type: pdf_img.value.type });
- } catch (e) {
- // Safari 浏览器不支持直接通过 new File() 创建文件对象,需要手动构造
- const rawFile = blobToFile(blob, fileName);
- file = Object.assign(rawFile, { lastModifiedDate: new Date(), name: fileName });
- }
- return file;
- }
- // 构建 File 对象
- function blobToFile(blob: any, fileName: any) {
- blob.lastModifiedDate = new Date();
- blob.name = fileName;
- return blob;
- }
- function dataURLtoBlob(dataURL: any) {
- var arr = dataURL.split(','),
- mime = arr[0].match(/:(.*?);/)[1],
- bstr = atob(arr[1]),
- n = bstr.length,
- u8arr = new Uint8Array(n);
- while (n--) {
- u8arr[n] = bstr.charCodeAt(n);
- }
- return new Blob([u8arr], { type: mime });
- }
- function getPdfImage(index: number) {
- // console.log(index, state.pageNum)
- loadingTask.promise.then((pdf: any) => {
- state.numPages = pdf.numPages;
- pdf.getPage(index).then((page: any) => {
- const viewport = page.getViewport({ scale: 4.0 })
- canvas.value.height = viewport.height;
- canvas.value.width = viewport.width;
- // 画布的dom大小, 设置移动端,宽度设置铺满整个屏幕
- // const clientWidth = document.body.clientWidth;
- const destWidth = 398;
- canvas.value.style.width = destWidth + 'px';
- // 根据pdf每页的宽高比例设置canvas的高度
- canvas.value.style.height = destWidth * (viewport.height / viewport.width) + 'px';
- const ctx = canvas.value.getContext('2d');
- page.render({
- canvasContext: ctx,
- viewport,
- });
- canvas.value.toBlob(function (blob) {
- pdf_img.value = dataURLtoBlob(canvas.value.toDataURL('images/png', 1.0))
- // console.log(pdf_img.value);
- console.log(canvas.value.toDataURL(), state.pageNum)
- });
- })
- });
- }
- function prePage() {
- if (state.pageNum > 1) {
- state.pageNum -= 1;
- getPdfImage(state.pageNum);
- }
- }
- function nextPage() {
- if (state.pageNum < state.numPages) {
- state.pageNum += 1;
- getPdfImage(state.pageNum);
- }
- }
- const { toClipboard } = useClipboard()
- function copy(index: any) {
- try {
- toClipboard(htmlArr.value[index])
- copy_text.value = "copied!";
- console.log('Copied to clipboard')
- } catch (e) {
- console.error(e)
- }
- }
- </script>
- <style scoped lang="less">
- .small-title {
- justify-content: space-between;
- align-items: center;
- }
- .show-area {
- width: 100%;
- }
- .place {
- margin-right: auto;
- margin-left: auto;
- border-right: solid 1px #ccc;
- }
- .pdf-preview {
- position: relative;
- height: 100vh;
- padding: 20px 0;
- box-sizing: border-box;
- background: rgb(66, 66, 66);
- }
- .vue-pdf-embed {
- text-align: center;
- width: auto;
- border: 1px solid #e5e5e5;
- margin: 0 auto;
- box-sizing: border-box;
- }
- .pdf-preview {
- position: relative;
- height: 100vh;
- padding: 20px 0;
- box-sizing: border-box;
- background-color: e9e9e9;
- }
- .pdf-wrap {
- overflow-y: auto;
- }
- .vue-pdf-embed {
- text-align: center;
- width: 515px;
- border: 1px solid #e5e5e5;
- margin: 0 auto;
- box-sizing: border-box;
- }
- .page-tool {
- position: absolute;
- /* bottom: 35px; */
- /* padding-left: 15px; */
- /* padding-right: 15px; */
- display: flex;
- align-items: center;
- background: rgb(66, 66, 66);
- color: white;
- border-radius: 19px;
- z-index: 100;
- cursor: pointer;
- margin-left: 50%;
- transform: translateX(-50%);
- }
- .page-tool-item {
- padding: 8px 15px;
- padding-left: 10px;
- cursor: pointer;
- }
- .input_text {
- background-color: #F5F5F5;
- border: 1px solid #CCCCCC;
- padding: 10px;
- margin: 10px;
- border-radius: 5px;
- font-size: 14px;
- color: #333333;
- text-align: left;
- box-shadow: 0px 0px 5px 0px rgba(0, 0, 0, 0.25);
- }
- </style>
|