当前位置: 首页 > news >正文

使用canvas实现图纸标记及回显

图纸
在这里插入图片描述
图纸标记后的效果图
在这里插入图片描述
最近做的一个qms项目里面,需要前端图纸上实现标记标记后的内容还要能够回显,然后后端通过标记的点,去读取标记图纸的内容,如一些公式数据之类的,目前实现的功能有 在图纸上面进行矩形标记已保存的标记回显单个标记点清除一键清除所有标记保存标记图片
一开始听领导说,小段啊,咱们做的这个项目模块,有个功能图纸标记的功能,需要前端做下,大概就是在图纸上面框住或者圈住一部分,然后传给后端,后端根据你传过去的数据,去读取标记的内容,实现一些其他的业务逻辑。。我一开始是懵逼的,这是啥呀,又搞骚操作??唉,打工人一切向钱看齐,想办法吧,正好前端同时之前做过类似的demo,可以借鉴,同时加上自己的一些琢磨,功能是基本实现,目前还没和后端对接。
说一下思路:我研究了下,目前在图片上面做一些操作的,大多是通过canvas实现的,canvas的默认背景是黑色的,然后下载后的图片背景就是乌黑黑的一片加一些自己画的线,然后就想着把图纸作为canvas的背景图片,这样下载后的背景图片就是图纸,挺好的,哈哈,剩下的具体代码实现的逻辑,都在贴在下面的代码示例里面了!!

以下是操作视频

图纸标记组件视频

代码如下

<template><div><div><a-button @click="clearBtn" style="margin-right:15px;">清除当前标记</a-button><a-button @click="clearAllBtn" style="margin-right:15px;">清除所有标记</a-button><a-button @click="saveBtn" type="primary" style="margin-right:15px;">保存标记</a-button><a-button @click="saveImage" type="primary">下载标记图片</a-button></div><div style="margin:10px 0"><a-form :model="formInfo" ref="formRef" name="basic" :label-col="{ span: 6 }":wrapper-col="{ span: 18 }"><a-row :gutter="20"><a-col :span="8"><a-form-item label="标记名称" name="rectName"><a-input v-model:value="rectName" placeholder="请输入标记名称" allowClear></a-input></a-form-item></a-col></a-row></a-form></div><div id="canvasDiv"><canvas ref="canvas"  @contextmenu.prevent="canvasRight" @mousedown="startMark" @click="canvasClick" @mousemove="draw" @mouseup="endMark"></canvas></div></div>
</template><script>
import cardPng from '/@/assets/images/card.png';export default {data() {return {isMarking: false,//是否开始在标记的标识startX:0,//点击时初始位置x轴startY:0,//点击时初始位置y轴markedRectangles: [{x:162, y:253, width:46, height:76,name:'过渡板'},{x:70, y:253, width:47, height:70,name:'电磁阀'},{x:447, y:928, width:114, height:39,name:'软管'},], // 存储已标记矩形的数组rectObj:'',//记录最后一次的矩形rectName:'',//标记名称};},mounted() {//设置canvas背景this.loadCanvas();window.addEventListener('resize', this.loadCanvas);},beforeDestroy() {window.removeEventListener('resize', this.loadCanvas);},methods: {canvasRight(e){e.preventDefault();// 阻止默认的右键菜单},//下载标记并且保存的canvas图片saveImage(){this.drawBackground();//重绘canvas及回显保存的标记,若是标记未保存,则不会在下载的图片里面显示const img = this.canvas.toDataURL('image/png');const link = document.createElement('a');link.href = img;link.download = 'canvas.png';console.log(img,'img')console.log(link,'link')link.click();},canvasClick(event){// cavans点击事件监听console.log(event,'event')const canvas = this.$refs.canvas;const ctx = canvas.getContext('2d');const rect = canvas.getBoundingClientRect();const x = event.clientX - rect.left;const y = event.clientY - rect.top;// 遍历已标记的矩形,检查点击位置,若是在标记数组的矩形范围内,则从数组中进行比对,进行标记名称回显for (let i = this.markedRectangles.length - 1; i >= 0; i--) {const markedRect = this.markedRectangles[i];if (x >= markedRect.x &&x <= markedRect.x + markedRect.width && y >= markedRect.y && y <= markedRect.y + markedRect.height){this.rectObj=markedRect;this.rectName=markedRect.name;}}},//绘制canvas、添加背景图片、把标记数组里面的点回显drawBackground(){this.canvas.width = this.image.width;this.canvas.height = this.image.height;this.ctx.drawImage(this.image, 0, 0);this.ctx.strokeStyle = '#99eb1e';//改变画线的颜色和宽度this.ctx.lineWidth = 2;this.markedRectangles.forEach((item,index)=>{item.resetid=new Date().getTime()+index;})console.log(this.markedRectangles,'markedRectangles')for(let i=0;i<this.markedRectangles.length;i++){this.ctx.strokeRect(this.markedRectangles[i].x, this.markedRectangles[i].y, this.markedRectangles[i].width, this.markedRectangles[i].height)//起点/终点/宽度/高度}},//赋值初始值x轴的点、y轴的点startMark(event) {this.isMarking = true;//开始标记,记录此时开始标记的初始点const rect = this.canvas.getBoundingClientRect();this.startX = event.offsetX;this.startY = event.offsetY;},//标记中draw(event) {if (this.isMarking) {//获取最后标记的位置,拿最后标记的位置的x轴值、y轴值与初始位置的x轴值,与y轴值进行计算,获取标记矩形的宽高const rect = this.canvas.getBoundingClientRect();const x = event.offsetX - this.startX;const y = event.offsetY - this.startY;//canvas绘制时,会把背景图清掉,需要再次调用下添加背景图的方法this.drawBackground();this.ctx.strokeRect(this.startX, this.startY, x, y);this.rectObj={x:this.startX, y:this.startY,width: x,height:y};//把最后的标记矩形的位置及宽高记录,若是保存此时的标记,会用到}},//标记结束endMark() {this.isMarking = false;//重置标记标识为false},// 初始化canvasloadCanvas(){this.canvas = this.$refs.canvas;this.ctx = this.canvas.getContext('2d');this.image = new Image();this.image.src = cardPng; // 替换为你的图片路径let canvasDiv=document.getElementById("canvasDiv");this.image.onload = () => {this.drawBackground();};},saveBtn(){// 保存时,若是此时的矩形存在,则添加到标记数组里面console.log(this.rectObj,'rectObj')if(this.rectObj!=''){let that=this;//防止同一个标记被保存多次for(let i=0;i<this.markedRectangles.length;i++){if(this.markedRectangles[i].x!=that.rectObj.x&&this.markedRectangles[i].y!=that.rectObj.y&&this.markedRectangles[i].width!=that.rectObj.width&&this.markedRectangles[i].height!=that.rectObj.height){if(this.rectName){that.rectObj.name=this.rectName;that.markedRectangles.push(that.rectObj)that.$message.success('该标记已保存')that.rectObj='';return false}else{this.$message.warning('请输入标记名称')}}}}else{this.$message.warning('暂无标记可以保存')return false}console.log(this.markedRectangles,'markedRectangles')},//清除标记clearBtn(){//重绘canvas// 若是有resetid,则是已保存过的,若是没有resetid,则是后来新增的if(this.rectObj!=''){if(this.rectObj.resetid){// this.rectObj='';this.$confirm('确定删除?', '提示', {okText: '确定',cancelText: '取消',type: 'warning'}).then(() => {for(let i=0;i<this.markedRectangles.length;i++){if(this.markedRectangles[i].x==this.rectObj.x&&this.markedRectangles[i].y==this.rectObj.y&&this.markedRectangles[i].width==this.rectObj.width&&this.markedRectangles[i].height==this.rectObj.height){this.rectName='';this.rectObj='';this.markedRectangles.splice(i, 1); // 从数组中移除this.$message.success('标记已清除')this.drawBackground();return false}}}).catch(() => {this.$message({type: 'warning',message: '已取消删除'});          });}else{this.drawBackground();this.rectObj='';this.$message.warning('标记已清除')return false}}else{this.$message.warning('暂无标记可以清除')}},//一键清除所有标记clearAllBtn(){//把标记数组清空,同时重绘canvasif(this.markedRectangles.length>0){this.markedRectangles=[];this.drawBackground();}else{this.$message.warning('暂无标记可以清除')return false}},}
};
</script>
<style scoped>
#canvasDiv{width:800px;height: auto;
}
</style>

相关文章:

使用canvas实现图纸标记及回显

图纸 图纸标记后的效果图 最近做的一个qms项目里面&#xff0c;需要前端在图纸上实现标记及标记后的内容还要能够回显&#xff0c;然后后端通过标记的点&#xff0c;去读取标记图纸的内容&#xff0c;如一些公式、数据之类的&#xff0c;目前实现的功能有 在图纸上面进行矩形…...

鸿蒙-自定义组件的生命周期

目录 自定义组件的生命周期 1.aboutToAppear 2.aboutToDisappear 3.onPageShow 4.onPageHide 5.onBackPress 日志输出 1.显示页面 2.页面点击返回按钮 3.页面跳转 4.页面返回 自定义组件的生命周期 先来一段列子 import router from ohos.router Entry Component…...

【Linux】自动化构建工具-make/Makefile

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 前言2. 认识make/Makefile3. 了解make/Makefile原理3.1 依赖关系和依赖方法3.2 make检测的顺序3.3 PHONY:XXX 4. makefile内置符号 1. 前言 在上一篇中已经了解了【Linux】编译器-gcc/g使用&#xff0c;这次来一起…...

week07day03(power bi dax公式 零售数据业务分析)

一. 切片器(筛选)相关的三个函数 1.all &#xff08;all后面的数据意思是 不受其影响&#xff09; #ALL 筛选的是 筛选器 或 切片器#计算 销售金额 &#xff0c;并且 不受到 门店ID 控制 计算金额 CALCULATE(SUM(销售表[金额]),ALL(销售表[门店ID]))#计算 销售金额 &#x…...

rembg报错onnxruntime_providers_tensorrt.dll

报错&#xff1a; 2024-03-16 04:16:59.4413827 [E:onnxruntime:Default, provider_bridge_ort.cc:1534 onnxruntime::TryGetProviderInfo_TensorRT] D:\a_work\1\s\onnxruntime\core\session\provider_bridge_ort.cc:1209 onnxruntime::ProviderLibrary::Get [ONNXRuntimeErro…...

精酿啤酒:一口啤酒,一份享受

在繁华的都市生活中&#xff0c;我们总是匆匆忙忙&#xff0c;追求着各种目标和成就。然而&#xff0c;在这个过程中&#xff0c;我们往往忽略了生活的本质&#xff0c;那就是享受。而Fendi Club 啤酒&#xff0c;正是为那些追求品质生活的都市精英们量身打造的。 Fendi Club啤…...

git报: “fatal: detected dubious ownership in repository“

“fatal: detected dubious ownership in repository”的中文翻译是&#xff1a;“致命错误&#xff1a;检测到仓库中存在可疑的所有权问题”。 这句话意味着 Git 在检查代码仓库时发现所有权存在问题&#xff0c;可能是由于文件或目录的所有权与 Git 仓库预期的所有权不匹配。…...

代码随想录算法训练营第27天|93.复原IP地址、78.子集、90.子集二

目录 一、力扣93.复原IP地址1.1 题目1.2 思路1.3 代码1.4 总结 二、力扣78.子集2.1 题目2.2 思路2.3 代码2.4 总结 三、力扣90.子集二3.1 题目3.2 思路3.3 代码3.4 总结 一、力扣93.复原IP地址 &#xff08;比较困难&#xff0c;做起来很吃力&#xff09; 1.1 题目 1.2 思路 …...

Java微服务轻松部署服务器

我们在日常开发微服务之后需要再服务器上面部署&#xff0c;那么如何进行部署呢&#xff0c;先把微服务的各个服务和中间件以及对应的端口列举出来&#xff0c;都打包成镜像&#xff0c;以及前端代码部署的nginx&#xff0c;使用docker-compose启动&#xff0c;访问服务器nginx…...

Wordpress站点通过修改.htaccess 设置重定向实现强制 https 访问

要在WordPress站点上通过修改.htaccess文件实现强制HTTPS访问&#xff0c;您可以按照以下步骤进行操作&#xff1a; 登录到WordPress站点管理后台。 在文件管理器或通过FTP访问网站根目录&#xff0c;找到并打开名为 .htaccess 的文件。 在打开的文件中添加以下代码&#xf…...

人大金仓助力国家电网调度中心培养国产数据库专家人才

近日,为进一步提升调度自动化安全可靠水平,提高电网数据应用效能,人大金仓助力国家电网调度中心培养国产数据库专家人才。 调度自动化系统拥有海量电网数据资源,是支撑电网安全经济优质运行的重要保障。数据库是调度自动化系统稳定运行的基石之一,其结构严谨、运行稳定、扩展灵…...

什么是增强型SSL证书?购买一张需要多少钱?

增强型SSL证书是一种提供更高级别安全验证与用户信任度的网络安全工具&#xff0c;也被称为EV证书。相较于DV&#xff08;域名验证&#xff09;和OV&#xff08;组织验证&#xff09;证书&#xff0c;它通过严格的身份核实流程确保网站所有者的合法性和真实性。 首先&#xff0…...

C++:函数传参到函数执行结束发生了什么

首先要明确两个概念 函数实参的入栈从右向左栈区从高地址向低地址偏移 接下来看下面一段代码 void fun(int a,int b,int c){std::cout<<&a<<" "<<&b<<" "<<&c<<std::endl; } int main(){fun(1,2,3); }…...

QT中dumpcpp以及dumpdoc使用

qt中调用COM的方式方法有四种&#xff0c;参考解释在 Qt 中使用 ActiveX 控件和 COM (runebook.dev) 介绍dumpcpp的使用方法Qt - dumpcpp 工具 (ActiveQt) (runebook.dev)&#xff1a; 在安装好了的qt电脑上&#xff0c;通过powershell窗口来实现&#xff0c;powershell比cmd要…...

RPM与DNF的操作实践

这几课有三个目标&#xff1a; 第一步&#xff1a;先配置软件源 跳转到yum.repos.d目录&#xff0c;用vim创建一个openeuler_x84_64.repo文件。这个文件就是我们将会用到的软件源。 我们在里面添加这些东西&#xff0c;保存并退出即可。 然后&#xff0c;我们用yum list all就…...

车道线检测之LaneNet

论文&#xff1a;Towards End-to-End Lane Detection: an Instance Segmentation Approach Github&#xff1a;https://github.com/MaybeShewill-CV/lanenet-lane-detection?tabreadme-ov-file 论文提出一种车道线检测网络LaneNet&#xff0c;该网络以enet为主干网络结构&…...

MySQL连接数不足导致服务异常GetConnectionTimeoutException

文章目录 场景复现解决方案一、调整连接数二、优化程序 场景复现 已经上线正常运行的项目突然很多功能无法使用&#xff0c;查看程序日志发现MySQL报错&#xff0c;异常信息: Could not open JDBC Connection for transaction; nested exception is com.alibaba.druid.pool.Ge…...

软考76-上午题-【面向对象技术3-设计模式】-创建型设计模式01

一、创建型设计模式一览 二、创建型设计模式 2-1、创建型设计模式的概念 一个类创建型模式使用继承改变被实例化的类&#xff1b; 一个对象创建型模式将实例化委托给另一个对象。 对应java的new一个对象。 2-2、简单工厂模式&#xff08;静态工厂方法&#xff09; 简单工厂…...

Matlab 双目相机标定(内置函数)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 相机标定的目的就是要找到从世界坐标转换为图像坐标所用到的投影P矩阵各个系数(即相机的内参与外参)。具体过程如下所述: 1、首先我们需要获取一个已知图形的图像(这里我们使用MATLAB所提供的数据)。 2、找到同…...

【博客7.4】缤果Qt5_TWS串口调试助手V2.0 (高级篇)

超级好用的Qt5_TWS耳机串口调试助手 开发工具: qt-opensource-windows-x86-5.14.2 (编程语言C) 目录 前言 一、软件概要&#xff1a; 二、软件界面&#xff1a; 1.App演示 三、获取 >> 源码以及Git记录&#xff1a; 总结 前言 串口调试助手支持常用的50bps - 10M…...

【Python】 -- 趣味代码 - 小恐龙游戏

文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

linux 错误码总结

1,错误码的概念与作用 在Linux系统中,错误码是系统调用或库函数在执行失败时返回的特定数值,用于指示具体的错误类型。这些错误码通过全局变量errno来存储和传递,errno由操作系统维护,保存最近一次发生的错误信息。值得注意的是,errno的值在每次系统调用或函数调用失败时…...

C++八股 —— 单例模式

文章目录 1. 基本概念2. 设计要点3. 实现方式4. 详解懒汉模式 1. 基本概念 线程安全&#xff08;Thread Safety&#xff09; 线程安全是指在多线程环境下&#xff0c;某个函数、类或代码片段能够被多个线程同时调用时&#xff0c;仍能保证数据的一致性和逻辑的正确性&#xf…...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI&#xff08;https://spring.io/projects/spring-ai&#xff09;作为Spring生态中的AI集成框架&#xff0c;其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似&#xff0c;但特别为多语…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !

我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

[特殊字符] 手撸 Redis 互斥锁那些坑

&#x1f4d6; 手撸 Redis 互斥锁那些坑 最近搞业务遇到高并发下同一个 key 的互斥操作&#xff0c;想实现分布式环境下的互斥锁。于是私下顺手手撸了个基于 Redis 的简单互斥锁&#xff0c;也顺便跟 Redisson 的 RLock 机制对比了下&#xff0c;记录一波&#xff0c;别踩我踩过…...