客户那边文档相当的多,目前需要协助其将文档转为数据写入数据库,并与其他系统进行数据共享及建设,所以不得不搞一个识别的功能,用户上传PDF文档后,对于关键信息点进行识别入库!
以下为核心代码,直接分享,到中午吃饭时间了,就大概分享一下。
<canvas id="imgCanvas" style="border:1px solid rgb(230,230,230)"></canvas>
javascript">initCanvas() {let rectArr = []let currAreas = []let canvasEle = document.getElementById('imgCanvas')let elRef = this.$refs.canvaxbox// canvasEle.width = elRef.clientWidthcanvasEle.height = elRef.clientHeightcanvasEle.width = (210 / 297) * elRef.clientHeightlet ctx = canvasEle.getContext('2d')// 给矩形的设置颜色ctx.strokeStyle = '#448ef7'this.saveCtx = ctxlet 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) },
以上是核心代码,绑定点击及拖动事件绘制待定区域!
页面整体代码,包含一些测试数据,我没有删除,你自己进行分析删除即可.
javascript"><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.clientWidthcanvasEle.height = elRef.clientHeightcanvasEle.width = (210 / 297) * elRef.clientHeightlet ctx = canvasEle.getContext('2d')// 给矩形的设置颜色ctx.strokeStyle = '#448ef7'this.saveCtx = ctxlet 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 = truethis.$refs.upload.submit()},beforeUpload(file) {let isLt2M = trueisLt2M = file.size / 1024 / 1024 < 100if (!isLt2M) {this.loading = falsethis.$message.error('上传文件大小不能超过 100MB!')}return isLt2M},handleSuccess(response, file, fileList) {this.loading = falsethis.uploadVisible = falsethis.$modal.msgSuccess('上传成功')this.$refs.upload.clearFiles()response.documents.forEach(p => {p.loading = truep.url = this.baseApi + "/" + p.url})this.coverList = response.documentsthis.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 = indexlet 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 / whe.canvasHeight = whe.canvasWidth = wh * iw / ih}else { // 以宽为主e.ratio = iw / wwe.canvasWidth = wwe.canvasHeight = ww * ih / iw}// 初始化画布大小canvasEle.width = e.canvasWidthcanvasEle.height = e.canvasHeighte.loading = true// 图片加载绘制let img = document.createElement('img')img.src = e.urlimg.onload = () => {e.loading = falsethis.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 = objectPointsconsole.log('最终提交数据', objectPoints)},handleScroll(e) {const eventDelta = e.wheelDelta || -e.deltaY * 40const $scrollWrapper = this.scrollWrapperlet scrolled = $scrollWrapper.scrollLeft + eventDelta / 4$scrollWrapper.scrollLeft = scrolledthis.$emit("scrolled", scrolled)},selOneItemAction(index) {this.crrentIndex = index},reconizerAction(tag) {this.toolsIndex = tagif (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; }