vue canvas 绘制选定区域 矩形框

ops/2024/12/14 18:16:13/

 客户那边文档相当的多,目前需要协助其将文档转为数据写入数据库,并与其他系统进行数据共享及建设,所以不得不搞一个识别的功能,用户上传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;
}


http://www.ppmy.cn/ops/141875.html

相关文章

k8s+rancher配置滚动发布更新时服务不可用

问题 配置完了k8s优雅下线后&#xff0c;发现配置了滚动发布后&#xff0c;两个服务同时在running状态&#xff0c;其中旧服务开始下线会导致有三四秒的时间调用该服务的接口会负载均衡到该服务&#xff0c;接口调用就会报错服务异常。 经排查&#xff0c;具体原因是服务虽然…

Coconut:探索大语言模型的连续思维链推理能力

目录 简介&#xff1a; 什么是Coconut&#xff1f; 为什么我们需要Coconut&#xff1f; Coconut如何工作&#xff1f; 实验结果怎么样&#xff1f; Coconut的优势&#xff1a; 结论&#xff1a; 简介&#xff1a; 你有没有想过&#xff0c;计算机是如何像人类一样思考问…

基于ArqMATH 数据集探索大语言模型在数学问题推理解答中的能力

概述 论文地址&#xff1a;https://arxiv.org/pdf/2404.00344 源码地址&#xff1a;https://github.com/gipplab/llm-investig-mathstackexchange 大规模语言模型&#xff08;LLMs&#xff09;因其解决自然语言任务的能力而备受关注&#xff0c;在某些任务中&#xff0c;其准…

rk3568 , openharmony3.2 , 8G , 无法启动

问题&#xff1a; 目前使用 openharmony 3.2 版本的 镜像 &#xff0c;在8G的核心板上 &#xff0c;烧写之后&#xff0c;屏幕没有画面。 报错如下&#xff1a; 现象如下&#xff1a;  &#xff11; &#xff55;&#xff42;&#xff4f;&#xff4f;&#xff54;阶段是 有…

求解球面的一组正交标架

目录 求解球面的一组正交标架 求解球面的一组正交标架 球面 r ( u , v ) ( a cos ⁡ u cos ⁡ v , a cos ⁡ u sin ⁡ v , a sin ⁡ u ) \mathbf{r}(u,v)\left(a\cos u\cos v,a\cos u\sin v,a\sin u\right) r(u,v)(acosucosv,acosusinv,asinu), 求得 r u ( − a sin ⁡ u c…

‌植物神经紊乱患者:科学补充维生素,助力健康恢复

植物神经紊乱&#xff0c;作为一种常见的神经系统疾病&#xff0c;给患者的日常生活带来了诸多困扰。其症状涉及多个系统&#xff0c;如心血管、消化、呼吸等&#xff0c;表现为心悸、心慌、消化不良、失眠多梦等。面对这一挑战&#xff0c;科学合理的营养补充&#xff0c;尤其…

python爬虫--小白篇【爬虫实践】

一、前言 1.1、王者荣耀皮肤爬虫 根据王者荣耀链接&#xff0c;将王者荣耀的全部英雄的全部皮肤图片爬取保存到本地。经过分析得到任务的三个步骤&#xff1a; 根据首页全部英雄列表连接获取全部英雄的名称hero_name以及对应的hero_id&#xff1b;根据单个英雄的hero_name和h…

ctfshow-web 151-170-文件上传

151. 我们首先想到就是上传一句话木马。但是看源代码限制了png。 &#xff08;1&#xff09;改前端代码。 这里是前端限制了上传文件类型&#xff0c;那我们就改一下就好了嘛,改成php。 这里直接修改不行&#xff0c;给大家推荐一篇简短文章&#xff0c;大家就会了&#xff08…