客户那边文档相当的多,目前需要协助其将文档转为数据写入数据库,并与其他系统进行数据共享及建设,所以不得不搞一个识别的功能,用户上传PDF文档后,对于关键信息点进行识别入库!
以下为核心代码,直接分享,到中午吃饭时间了,就大概分享一下。
<canvas id="imgCanvas" style="border:1px solid rgb(230,230,230)"></canvas>
initCanvas() { let rectArr = [] let currAreas = [] let canvasEle = document.getElementById('imgCanvas') let elRef = this.$refs.canvaxbox // canvasEle.width = elRef.clientWidth canvasEle.height = elRef.clientHeight canvasEle.width = (210 / 297) * elRef.clientHeight let ctx = canvasEle.getContext('2d') // 给矩形的设置颜色 ctx.strokeStyle = '#448ef7' this.saveCtx = ctx let drawRect = (x1, y1, x2, y2) => { let rectWidth = Math.abs(x2 - x1) let rectHeight = Math.abs(y2 - y1) let endX = Math.min(x1, x2) let endY = Math.min(y1, y2) // 绘制之前先清空之前实时移动产生的多余的矩形路径 ctx.clearRect(0, 0, canvasEle.width, canvasEle.height) ctx.strokeStyle = '#448ef7' // 绘制之前那些存储在 this.drawedAreas 数组中的矩形 if (this.img) { ctx.drawImage(this.img, 0, 0, canvasEle.width, canvasEle.height) } currAreas = [endX, endY, rectWidth, rectHeight] this.drawedAreas.forEach(element => { ctx.beginPath(); ctx.strokeRect(...element) ctx.stroke(); }); // 开始本次路径 ctx.beginPath(); // 绘制本次的矩形路径 ctx.rect(...currAreas); // 开始填充矩形 ctx.stroke(); } let canvasMoveHandler = (e) => { drawRect(rectArr[0], rectArr[1], e.offsetX, e.offsetY) } let canvasMouseUpHandler = () => { this.drawedAreas.push(currAreas) canvasEle.removeEventListener('mousemove', canvasMoveHandler) canvasEle.removeEventListener('mouseup', canvasMouseUpHandler) } // 给canvas注册事件按下事件 let canvasDownHandler = (e) => { if (this.toolsIndex == 1) { rectArr = [e.offsetX, e.offsetY] // 按下的时候需要注册移动事件 canvasEle.addEventListener('mousemove', canvasMoveHandler) // 抬起事件 canvasEle.addEventListener('mouseup', canvasMouseUpHandler) } } canvasEle.addEventListener('mousedown', canvasDownHandler) },
以上是核心代码,绑定点击及拖动事件绘制待定区域!
页面整体代码,包含一些测试数据,我没有删除,你自己进行分析删除即可.
<template> <div class="app-container" style="padding: 0px;"> <div class="tool-box flex-row w-100"> <el-button type="primary" icon="el-icon-upload" @click="uploadVisible = !uploadVisible">上传PDF文档</el-button> <el-button type="primary" icon="el-icon-upload2" @click="backOneStep">回退</el-button> <el-button type="danger" @click="clearAll">重置</el-button> <!-- <el-button size="mini" type="success" @click="savePoints">保存</el-button> --> </div> <div class="container-view flex-row"> <div class="left-view flex-row jc-around"> <div class="cover-view"> <el-scrollbar class="w-100 h-100 flex-row"> <div class="w-100 flex-col" style="height: auto;background-color:rgb(245,245,245)"> <div class="cover-item-view flex-col" v-for="(item, index) in coverList" :key="index" @click="selOneItemAction(index)" v-loading="item.loading"> <el-image style="width: 100%; height: auto;background-color:rgb(230,230,230)" :src="item.url" fit="scale-down" :class="{ 'border-hi': index == crrentIndex }"></el-image> <span v-if="index < coverList.length - 1" style="height: 5px;display:inline-block" class="w-100"></span> </div> </div> </el-scrollbar> <div v-if="coverList.length == 0" class="place-text flex-row jc-center"> <span class="place-span">未上传文档</span> </div> </div> <div class="canvas-wrap flex-row jc-center" ref="canvaxbox" v-loading="crrentIndex >= 0 && coverList[crrentIndex].loading"> <canvas id="imgCanvas" style="border:1px solid rgb(230,230,230)"></canvas> <div class="view-tools flex-col"> <el-tooltip effect="dark" content="全页识别" placement="right"> <div class="w-100 flex-row jc-center" style="height: 50%;" @click="reconizerAction(0)"> <i class="el-icon-full-screen" style="font-size:20px" :class="{ 'toolsHili': toolsIndex == 0 }"></i> </div> </el-tooltip> <el-tooltip effect="dark" content="区域识别" placement="right"> <div class="w-100 flex-row jc-center" style="height: 50%;border-top:1px solid rgb(200,200,200)" @click="reconizerAction(1)"> <i class="el-icon-crop" style="font-size:20px" :class="{ 'toolsHili': toolsIndex == 1 }"></i> </div> </el-tooltip> </div> </div> </div> <div class="right-view"> <el-scrollbar class="result-view"> <div class="w-100 flex-col" v-for="(item, idex) in resultList"> <div class="card-view top-margin"> <div class="flex-row jc-end"> <i class="el-icon-close" style="font-size: 20px;padding:0px 0px 15px 0px" @click="closeItem(item)"></i> </div> <el-form :ref="`resultForm-${idex}`" :model="item" label-width="80px" class="w-100"> <el-form-item label="字段名称"> <el-input class="w-100" v-model="item.name"></el-input> </el-form-item> <el-form-item label="字段类型"> <el-select class="w-100" v-model="item.optionsValue" placeholder="请选择"> <el-option v-for="btem in item.optionsList" :label="btem.label" :value="btem.value"></el-option> </el-select> </el-form-item> </el-form> <div class="flex-row"> <span class="el-form-item__label" style="width:80px;">识别结果</span> <span style="width:calc(100% - 100px);font-size:14px;">识别结果</span> </div> </div> </div> </el-scrollbar> </div> </div> <!--表单组件--> <el-dialog append-to-body :close-on-click-modal="false" :visible.sync="uploadVisible" title="上传PDF文档" width="500px"> <el-upload class="w-100" ref="upload" :limit="1" :before-upload="beforeUpload" :auto-upload="false" drag :headers="headers" :on-success="handleSuccess" :on-error="handleError" :action="pdfUploadApi"> <i class="el-icon-upload"></i> <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> <div class="el-upload__tip" slot="tip">只能上传txt doc pdf ppt pps xlsx xls docx文件,且不超过10M</div> </el-upload> <div slot="footer" class="dialog-footer"> <el-button type="text" @click="uploadVisible = false">取消</el-button> <el-button :loading="loading" type="primary" @click="doUpload">确认</el-button> </div> </el-dialog> </div> </template> <script> import { mapGetters } from 'vuex' import { getToken } from '@/utils/auth' //https://www.cnblogs.com/IwishIcould/p/18360209 export default { components: {}, mixins: [], data() { return { id: null, name: '', headers: { 'Authorization': getToken() }, coverList: [{ url:'https://gd-hbimg.huaban.com/ba2dd60c93a9ef06595825d48c71e863a58f2cbe3f9f0-EQK5rk_fw1200', width:1200, height:1697 },{ url:'https://gd-hbimg.huaban.com/ba2dd60c93a9ef06595825d48c71e863a58f2cbe3f9f0-EQK5rk_fw1200', width:1200, height:1697 },{ url:'https://gd-hbimg.huaban.com/ba2dd60c93a9ef06595825d48c71e863a58f2cbe3f9f0-EQK5rk_fw1200', width:1200, height:1697 },{ url:'https://gd-hbimg.huaban.com/ba2dd60c93a9ef06595825d48c71e863a58f2cbe3f9f0-EQK5rk_fw1200', width:1200, height:1697 },{ url:'https://gd-hbimg.huaban.com/ba2dd60c93a9ef06595825d48c71e863a58f2cbe3f9f0-EQK5rk_fw1200', width:1200, height:1697 },{ url:'https://gd-hbimg.huaban.com/ba2dd60c93a9ef06595825d48c71e863a58f2cbe3f9f0-EQK5rk_fw1200', width:1200, height:1697 }], submitData: [ // {"polygon":{"x1":0,"y1":0,"x2":1920,"y2":0,"x3":1920,"y3":1080,"x4":0,"y4":1080}}, { "polygon": { "x1": 700, "y1": 273, "x2": 975, "y2": 278, "x3": 1107, "y3": 368, "x4": 718, "y4": 354 } }, { "polygon": { "x1": 49, "y1": 32, "x2": 183, "y2": 35, "x3": 181, "y3": 100, "x4": 55, "y4": 97 } }, { "polygon": { "x1": 433, "y1": 250, "x2": 706, "y2": 253, "x3": 707, "y3": 392, "x4": 435, "y4": 393 } }, { "polygon": { "x1": 45, "y1": 539, "x2": 193, "y2": 538, "x3": 192, "y3": 622, "x4": 41, "y4": 623, "x5": 42, "y5": 623 } } ], resultList: [ { label: '', value: '', optionsValue: '', optionsList: [{ label: '常规', value: '' }] }, { label: '', value: '', optionsValue: '', optionsList: [{ label: '常规', value: '' }] }, { label: '', value: '', optionsValue: '0', optionsList: [{ label: '常规', value: '0' }] }], loading: false, toolsIndex: 0, uploadVisible: false, // 所有的矩形信息 drawedAreas: [], crrentIndex: 0 } }, computed: { ...mapGetters([ 'baseApi', 'pdfUploadApi' ]), scrollWrapper() { return this.$refs.scrollbar.$refs.wrap } }, created() { }, mounted() { this.initCanvas() this.renderImgCanvas(0) }, methods: { initCanvas() { let rectArr = [] let currAreas = [] let canvasEle = document.getElementById('imgCanvas') let elRef = this.$refs.canvaxbox // canvasEle.width = elRef.clientWidth canvasEle.height = elRef.clientHeight canvasEle.width = (210 / 297) * elRef.clientHeight let ctx = canvasEle.getContext('2d') // 给矩形的设置颜色 ctx.strokeStyle = '#448ef7' this.saveCtx = ctx let drawRect = (x1, y1, x2, y2) => { let rectWidth = Math.abs(x2 - x1) let rectHeight = Math.abs(y2 - y1) let endX = Math.min(x1, x2) let endY = Math.min(y1, y2) // 绘制之前先清空之前实时移动产生的多余的矩形路径 ctx.clearRect(0, 0, canvasEle.width, canvasEle.height) ctx.strokeStyle = '#448ef7' // 绘制之前那些存储在 this.drawedAreas 数组中的矩形 if (this.img) { ctx.drawImage(this.img, 0, 0, canvasEle.width, canvasEle.height) } currAreas = [endX, endY, rectWidth, rectHeight] this.drawedAreas.forEach(element => { ctx.beginPath(); ctx.strokeRect(...element) ctx.stroke(); }); // 开始本次路径 ctx.beginPath(); // 绘制本次的矩形路径 ctx.rect(...currAreas); // 开始填充矩形 ctx.stroke(); } let canvasMoveHandler = (e) => { drawRect(rectArr[0], rectArr[1], e.offsetX, e.offsetY) } let canvasMouseUpHandler = () => { this.drawedAreas.push(currAreas) canvasEle.removeEventListener('mousemove', canvasMoveHandler) canvasEle.removeEventListener('mouseup', canvasMouseUpHandler) } // 给canvas注册事件按下事件 let canvasDownHandler = (e) => { if (this.toolsIndex == 1) { rectArr = [e.offsetX, e.offsetY] // 按下的时候需要注册移动事件 canvasEle.addEventListener('mousemove', canvasMoveHandler) // 抬起事件 canvasEle.addEventListener('mouseup', canvasMouseUpHandler) } } canvasEle.addEventListener('mousedown', canvasDownHandler) }, // 上传文件 doUpload() { this.loading = true this.$refs.upload.submit() }, beforeUpload(file) { let isLt2M = true isLt2M = file.size / 1024 / 1024 < 100 if (!isLt2M) { this.loading = false this.$message.error('上传文件大小不能超过 100MB!') } return isLt2M }, handleSuccess(response, file, fileList) { this.loading = false this.uploadVisible = false this.$modal.msgSuccess('上传成功') this.$refs.upload.clearFiles() response.documents.forEach(p => { p.loading = true p.url = this.baseApi + "/" + p.url }) this.coverList = response.documents this.renderImgCanvas(0) }, // 监听上传失败 handleError(e, file, fileList) { const msg = JSON.parse(e.message) this.$notify({ title: msg.message, type: 'error', duration: 2500 }) this.loading = false }, renderImgCanvas(index) { // 计算宽高比 this.crrentIndex = index let canvasEle = document.getElementById('imgCanvas') let ww = canvasEle.width // 画布宽度 let wh = canvasEle.height // 画布高度 let e = this.coverList.objectAtIndex(index) let iw = e.width // 图片宽度 let ih = e.height // 图片高度 if (iw / ih < ww / wh) { // 以高为主 e.ratio = ih / wh e.canvasHeight = wh e.canvasWidth = wh * iw / ih } else { // 以宽为主 e.ratio = iw / ww e.canvasWidth = ww e.canvasHeight = ww * ih / iw } // 初始化画布大小 canvasEle.width = e.canvasWidth canvasEle.height = e.canvasHeight e.loading = true // 图片加载绘制 let img = document.createElement('img') img.src = e.url img.onload = () => { e.loading = false this.saveCtx.drawImage(img, 0, 0, e.canvasWidth, e.canvasHeight) } this.img = img }, clearAll() { // 清空所有绘制区域 let canvasEle = document.getElementById('imgCanvas') this.saveCtx.clearRect(0, 0, canvasEle.width, canvasEle.height); if (this.img) { this.saveCtx.drawImage(this.img, 0, 0, canvasEle.width, canvasEle.height) } this.drawedAreas = [] }, savePoints() { // 将画布坐标数据转换成提交数据 let objectPoints = [] // "object": [{"polygon": {"x1":700,"y1":273,"x2":975,"y2":278,"x3":1107,"y3":368,"x4":718,"y4":354} }] objectPoints = currAreas.map(area => { let polygon = {} area.forEach((point, i) => { polygon[`x${i + 1}`] = Math.round(point[0] * this.ratio) polygon[`y${i + 1}`] = Math.round(point[1] * this.ratio) }) return { "polygon": polygon } }) this.submitData = objectPoints console.log('最终提交数据', objectPoints) }, handleScroll(e) { const eventDelta = e.wheelDelta || -e.deltaY * 40 const $scrollWrapper = this.scrollWrapper let scrolled = $scrollWrapper.scrollLeft + eventDelta / 4 $scrollWrapper.scrollLeft = scrolled this.$emit("scrolled", scrolled) }, selOneItemAction(index) { this.crrentIndex = index }, reconizerAction(tag) { this.toolsIndex = tag if (tag == 0) { this.clearAll() } }, backOneStep() { let canvasEle = document.getElementById('imgCanvas') this.saveCtx.clearRect(0, 0, canvasEle.width, canvasEle.height) // 绘制之前那些存储在 this.drawedAreas 数组中的矩形 if (this.img) { this.saveCtx.drawImage(this.img, 0, 0, canvasEle.width, canvasEle.height) } this.drawedAreas.removeLastObject() this.drawedAreas.forEach(element => { this.saveCtx.beginPath(); this.saveCtx.strokeRect(...element) this.saveCtx.stroke(); }); } } } </script> <style lang="scss" scoped> @import "./dicomStyles/aiStyle.scss"; ::v-deep { .result-view { .is-horizontal { height: 0px; left: 0px; display: none; } } .view-content { .is-horizontal { display: none; } .el-scrollbar__wrap { overflow-x: hidden; margin-bottom: 0px !important; } //横向滚动 .el-scrollbar__view { display: flex; flex-direction: column; justify-content: flex-start; align-items: center; } ::-webkit-scrollbar-thumb { background-color: #888; } ::-webkit-scrollbar { height: 8px; } } .el-form-item { margin-bottom: 10px; } .el-input__inner { border-radius: 0px; } .el-scrollbar__wrap { overflow-x: hidden; } .el-image-viewer__wrapper { top: 55px; } .el-image__error, .el-image__placeholder { background: none; } .el-form-item__label { font-weight: 500; } .el-upload { width: 100%; } .el-upload-dragger { width: 100%; } } </style>
样式文件:
.app-container{ background-color: rgb(245, 245, 245); } .cover-view{ position: relative; width: 180px; height: 100%; background-color: white; } .cover-item-view{ width: 100%; height: auto; } .tool-box { width: 100%; height: 54px; padding: 5px 30px; border-bottom: 5px solid rgb(245, 245, 245); background-color: white; } .toolsHili{ color: #0286df; } .view-tools{ position: absolute; left: 0px; top: calc(50% - 50px); width: 34px; height: 100px; background-color: white; border-top-right-radius: 10px; border-bottom-right-radius: 10px; border: 1px solid rgb(200,200,200); border-left: none; } .container-view{ width: 100%; height: calc(100% - 64px); } .left-view{ position: relative; width: 70%; height: 100%; background-color: rgb(245, 245, 245); } .right-view{ width: 30%; height: 100%; } .flex-row{ display: flex; flex-direction: row; justify-content: flex-start; align-items: center; } .jc-end{ justify-content: flex-end; } .jc-center{ justify-content: center; align-items: center; } .jc-around{ justify-content: space-around; } .jc-between{ justify-content: space-between; } .result-view{ position: relative; width: 100%; height: 100%; background-color: rgb(245, 245, 245) } .flex-col{ display: flex; flex-direction: column; justify-content: flex-start; align-items: center; } .top-margin{ margin-top: 15px; } .card-view{ width: 90%; background-color: white; padding: 15px; border-radius: 10px; } .w-100{ width: 100%; } .h-100{ height: 100%; } .view-content{ width: 100%; height: calc(100% - 10px); } .canvas-wrap { position: relative; width: calc(100% - 190px); height: 100%; background-color: white; } .place-text{ position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .place-span { font-size: 15px; color: #666666; } .border-hi{ border: 1px solid #0286df; } .canvas-view{ position: absolute; left: 0; top: 0; }