vue实践:构建高效的电子签名功能

前言

在现代数字化时代,电子签名成为了一种方便、高效且安全的签署文件的方式。本文将介绍电子签名的原理和实现方法,帮助你快速掌握这一重要的工具。


电子签名是什么?

电子签名是一种数字化的签名方式,用于验证和确认电子文档、合同或其他电子信息的真实性和完整性。它是传统纸质签名的数字化替代品,具有更高的效率、便捷性和安全性。大家看下面这个例子就会一目了然了。


一、手动实现一个简单的电子签名

下面的示例中,我们使用了 Canvas 元素来绘制用户的签名。当用户按下鼠标左键时,startDrawing 方法会被调用,开始绘制路径。当用户移动鼠标时,draw 方法会被调用,绘制路径。当用户释放鼠标左键时,stopDrawing 方法会被调用,停止绘制路径。点击"清除"按钮会清除 Canvas 上的内容,点击"保存"按钮会将签名保存为 Base64 编码的图片数据。

<template><div><canvas ref="canvas" @mousedown="startDrawing" @mousemove="draw" @mouseup="stopDrawing"></canvas><div><button @click="clearCanvas">清除</button><button @click="saveSignature">保存</button></div></div>
</template><script>
export default {data() {return {isDrawing: false, // 是否正在绘制context: null, // Canvas上下文};},mounted() {this.context = this.$refs.canvas.getContext("2d"); // 获取Canvas上下文this.$refs.canvas.width = 500; // 设置Canvas的宽度this.$refs.canvas.height = 300; // 设置Canvas的高},methods: {// 鼠标按下时触发startDrawing(event) {this.isDrawing = true; // 开始绘制const { offsetX, offsetY } = event; // 获取鼠标相对于Canvas的偏移量this.context.beginPath(); // 开始新的路径this.context.moveTo(offsetX, offsetY); // 将路径移动到鼠标位置},// 当鼠标在 Canvas 上移动时触发draw(event) {if (!this.isDrawing) return; // 如果没有在绘制中,则返回const { offsetX, offsetY } = event; // 获取鼠标相对于Canvas的偏移量this.context.lineTo(offsetX, offsetY); // 绘制路径this.context.stroke(); // 绘制路径的边框},// 当鼠标松开时触发,用于停止绘制签名stopDrawing() {this.isDrawing = false; // 停止绘制},// 清除clearCanvas() {this.context.clearRect(0,0,this.$refs.canvas.width,this.$refs.canvas.height); // 清除Canvas上的内容},// 保存saveSignature() {const dataURL = this.$refs.canvas.toDataURL(); // 获取签名图片的Base64编码// 在这里可以将dataURL发送到服务器保存,或者进行其他操作console.log(dataURL); // 输出签名的Base64编码到控制台},},
};
</script>
<style scoped>
canvas {border: 1px solid red;
}
</style>

实现效果
在这里插入图片描述


二、vue-esign 插件

vue-esign 是一个基于 vue.js 框架的电子签名组件库,它提供了一套现成的 UI 组件,包括签名面板、工具栏等,可以方便地在 vue.js 应用中实现电子签名功能。

2.1 安装

npm install vue-esign --save

2.2 引用

  • 全局引用

    import vueEsign from 'vue-esign'
    Vue.use(vueEsign)
    
  • 局部引用

    import vueEsign from 'vue-esign'
    components: { vueEsign }
    

2.3 使用

<template><div class="box"><vue-esign ref="esign" :width="300" :height="150" :isCrop="isCrop" :lineWidth="lineWidth" :lineColor="lineColor":bgColor.sync="bgColor" /><div><button @click="handleReset">清空画板</button><button @click="handleGenerate">生成图片</button></div><img :src="resultImg" alt=""></div>
</template><script>
import vueEsign from "vue-esign";
export default {components: { vueEsign },data() {return {lineWidth: 6,lineColor: "#000000",bgColor: "",resultImg: "",isCrop: false,};},mounted() {},methods: {handleReset() {this.$refs.esign.reset();},handleGenerate() {this.$refs.esign.generate({ format: "png", quality: 0.8 }).then((res) => {this.resultImg = res;}).catch((err) => {console.error(err);alert("生成图片失败:" + err.message);});},},
};
</script>
<style scoped>
.box {width: 300px;height: 150px;border: 1px solid red;
}
</style>

实现效果

在这里插入图片描述

此外,vue-esign 插件还支持画笔粗细自定义、画笔颜色自定义等等,感兴趣的同学可以看下面的属性试试。

2.4 常用的属性

属性类型默认值描述
widthNumber800画布宽度,即导出图片的宽度
heightNumber300画布高度,即导出图片的高度
lineWidthNumber4画笔粗细
lineColorString#000000画笔颜色
bgColorString画布背景色,为空时画布背景透明,支持多种格式 ‘#ccc’,‘#E5A1A1’,‘rgb(229, 161, 161)’,‘rgba(0,0,0,.6)’,‘red’
isCropBooleanfalse是否裁剪,在画布设定尺寸基础上裁掉四周空白部分
isClearBgColorBooleantrue清空画布时(reset)是否同时清空设置的背景色(bgColor)
formatStringimage/png生成图片格式 image/jpeg(jpg格式下生成的图片透明背景会变黑色请慎用或指定背景色)、 image/webp
qualityNumber1生成图片质量;在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。

三、vue-signature-pad 插件

vue-signature-pad 同样是一个基于 vue.js 框架的电子签名组件库,它提供了一个可定制的签名面板,可以方便地在 vue.js 应用中实现电子签名功能。

注意: 如果仍在使用 vue2 ,请安装 2.0.5 版本,对于 vue3,可以安装最新的发布版本。

3.1 安装

npm i vue-signature-pad@2.0.5

3.2 全局引用

import VueSignature from "vue-signature-pad";
Vue.use(VueSignature);

3.3 使用

<template><div class="box"><VueSignaturePad width="300px" height="150px" ref="signaturePad" /><div><button @click="save">保存</button><button @click="undo">撤销</button></div></div>
</template><script>
export default {data() {return {};},mounted() {},methods: {// 撤销操作undo() {this.$refs.signaturePad.undoSignature();},// 保存操作save() {const { isEmpty, data } = this.$refs.signaturePad.saveSignature();console.log(data);},},
};
</script>
<style scoped>
.box {width: 300px;height: 150px;border: 1px solid red;
}
</style>

实现效果

在这里插入图片描述

3.4 常用的属性

属性类型默认值描述
widthString100%画布宽度
heightString100%画布高度
optionsObject{}设置画笔的选项,包括线条颜色、宽度、透明度等
imagesArray[]设置背景图片。可以是 [‘1.png’, ‘2.png’] 或者 [{ src: ‘1.png’, x: 0, y: 0 }, { src: ‘2.png’, x: 0, y: 10 }]
customStyleObject{}设置画布的自定义样式。可以是一个CSS样式对象
scaleToDevicePixelRatioBooleantrue设置是否将画布缩放到设备像素比

3.5 常用的方法

方法参数描述
saveSignature(type, encoderOptions)(String, Number)保存当前的签名,并返回一个包含签名是否为空和签名数据的对象
undoSignature()-撤销上一步的签名操作
clearSignature()-清除当前的签名
mergeImageAndSignature(signature)Object 或者 String将指定的背景图片与当前的签名合并
addImages(images)Array添加多个背景图片
lockSignaturePad()-锁定签名画布,禁止用户进行签名操作
openSignaturePad()-解锁签名画布,允许用户进行签名操作
getPropImagesAndCacheImages()-获取图像的信息
clearCacheImages()-清除缓存的图片
fromDataURL(data, options, callback)(String, Object, Callback)从指定的DataURL加载签名数据
fromData(data)String从指定的数据加载签名数据
toData()-获取当前的签名数据
isEmpty()-检查当前的签名是否为空

完整功能代码

<template><div class="box"><!-- VueSignaturePad 组件 --><VueSignaturePad :width="width" :height="height" :options="options" :images="images" :customStyle="customStyle":scaleToDevicePixelRatio="scaleToDevicePixelRatio" ref="signaturePad" /><div><!-- 操作按钮 --><button @click="save">保存</button><button @click="undo">撤销</button><button @click="clear">清除</button><button @click="mergeImage">合并图片</button><button @click="addImages">添加图片</button><button @click="lock">锁定</button><button @click="unlock">解锁</button><button @click="getData">获取数据</button><button @click="isEmpty">是否为空</button><button @click="getPropImagesAndCacheImages">获取并缓存背景图片</button><button @click="clearCacheImages">清除缓存的背景图片</button><button @click="loadSignatureFromDataURL">从 Data URL 加载签名</button><button @click="loadSignatureFromData">从 JSON 数据加载签名</button></div></div>
</template><script>
export default {data() {return {width: "1000px",height: "500px",options: {minWidth: 2, // 画笔最小宽度maxWidth: 5, // 画笔最大宽度throttle: 16, // 画笔移动事件的时间间隔,单位为毫秒minDistance: 5, // 画笔移动的最小距离,单位为像素backgroundColor: "rgba(255, 255, 255, 0)", // 画布背景颜色penColor: "red", // 画笔颜色velocityFilterWeight: 0.7, // 画笔速度过滤器的权重onBegin: () => {}, // 开始签名时的回调函数onEnd: () => {}, // 结束签名时的回调函数},images: [],customStyle: {signatureCanvas: {border: "1px solid #ccc", // 画布边框borderRadius: "5px", // 画布圆角},signaturePad: {boxShadow: "0 0 5px rgba(0, 0, 0, 0.1)", // 画笔阴影},},scaleToDevicePixelRatio: true,};},methods: {// 保存签名save() {const { isEmpty, data } = this.$refs.signaturePad.saveSignature();console.log("签名是否为空:", isEmpty);console.log("签名数据:", data);},// 撤销签名undo() {this.$refs.signaturePad.undoSignature();},// 清除签名clear() {this.$refs.signaturePad.clearSignature();},// 合并图片和签名mergeImage() {const image = "image.jpg"; // 替换为你的背景图片路径this.$refs.signaturePad.mergeImageAndSignature(image);},// 添加图片addImages() {const images = ["image.jpg", "image.jpg"]; // 替换为你的背景图片路径数组this.$refs.signaturePad.addImages(images);},// 锁定签名画布lock() {this.$refs.signaturePad.lockSignaturePad();},// 解锁签名画布unlock() {this.$refs.signaturePad.openSignaturePad();},// 获取签名数据getData() {const data = this.$refs.signaturePad.toData();console.log("获取签名数据:", data);},// 检查签名是否为空isEmpty() {const empty = this.$refs.signaturePad.isEmpty();if (!empty) {alert("画布不为空!");} else {alert("画布为空!");}},// 获取并缓存背景图片getPropImagesAndCacheImages() {const images = ["image1.jpg", "image2.jpg"]; // 替换为你的背景图片路径数组this.$refs.signaturePad.getPropImagesAndCacheImages(images);},// 清除缓存的背景图片clearCacheImages() {this.$refs.signaturePad.clearCacheImages();},// 从 Data URL 加载签名loadSignatureFromDataURL() {const dataURL = "data:image/png;base64,iVBORw0KG..."; // 替换为你的 Data URLthis.$refs.signaturePad.fromDataURL(dataURL);},// 从 JSON 数据加载签名loadSignatureFromData() {const signatureData = {width: 500,height: 500,data: [{color: "#000",points: [{ x: 100, y: 100 },{ x: 200, y: 200 },{ x: 300, y: 300 },],},{color: "#f00",points: [{ x: 400, y: 400 },{ x: 450, y: 450 },{ x: 500, y: 500 },],},], //替换你的数据};const pointGroups = signatureData.data;this.$refs.signaturePad.fromData(pointGroups);},},
};
</script>
<style scoped>
.box {width: 1000px;height: 500px;border: 1px solid red;
}
</style>

四、vue-esign 和 vue-signature-pad 的区别?

4.1 共同点

  • 兼容 PC 和 移动端;
  • 同时支持 vue2vue3;
  • 画布自适应屏幕大小变化(窗口缩放、屏幕旋转时画布无需重置,自动校正坐标);
  • 自定义画布尺寸,画笔粗细、颜色,画布背景色。

4.2 不同点

  • vue-signature-pad 带笔压功能,可以根据笔的速度和方向来调整线条的粗细,不是固定粗细的,vue-esign 不支持此功能;
  • vue-signature-padvue2vue3 依赖安装版本不同;vue-esign 都版本向下兼容,不需要区分 vue2vue3;
  • vue-signature-pad 支持单步撤回,vue-esign 不支持此功能。

笔压对比效果

左边是 vue-signature-pad 插件,可以看到画笔线条不是固定粗细的,右边是 vue-esign 插件,线条是固定的粗细。
在这里插入图片描述


相关推荐

⭐ 浅谈 JSON 对象和 FormData 相互转换,打通前端与后端的通信血脉

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/655316.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

matlab appdesigner系列-app程序打包成可执行exe程序

提供了3种打包方式&#xff1a; 1&#xff09;Matlab App &#xff0c;这种方式是生成Matlab内部使用的小程序&#xff0c;可添加到matlab app菜单栏中的常用程序中&#xff0c;也就是应用该程序之前&#xff0c;你必须安装了matlab&#xff1b; 2&#xff09;Web app 3&…

vs 撤销本地 commit 并保留更改

没想到特别好的办法&#xff0c;我想的是用 vs 打开 git 命令行工具 然后通过 git 命令来撤销提交&#xff0c;尝试之前建议先建个分支实验&#xff0c;以免丢失代码&#xff0c; git 操作见 git 合并多个 commit / 修改上一次 commit

2024.1.29 GNSS 学习笔记

1.假设只对4颗卫星进行观测定位&#xff0c;卫星的截止高度角是15&#xff0c;那么如何布设这四颗卫星的位置&#xff0c;使其围成的四面体的体积得到最大&#xff0c;以获得最好定位精度&#xff1f; 答&#xff1a;3颗卫星均匀分布在最低仰角面上&#xff0c;第4颗卫星在测站…

华为笔记本matebook pro X如何扩容 C 盘空间

一、前提条件 磁盘扩展与合并必须是相邻分区空间&#xff0c;且两个磁盘类型需要相同。以磁盘分区为 C 盘和 D 盘为例&#xff0c;如果您希望增加 C 盘容量&#xff0c;可以先将 D 盘合并到 C 盘&#xff0c;然后重新创建磁盘分区&#xff0c;分配 C 盘和 D 盘的空间大小。 访…

git push后,如何撤销git log上的错误注释

修改了本地的代码&#xff0c;执行了下面的操作&#xff0c;提交之后&#xff0c;怎么样修改 git add ********(文件名)//git add 添加修改文件名之后 git commit //git commit 在当前分支提交&#xff0c;编写提交注释 git push //git push 提交修…

Android T 远程动画显示流程(更新中)

序 本地动画和远程动画区别是什么? 本地动画&#xff1a;自给自足。对自身SurfaceControl矢量动画进行控制。 远程动画&#xff1a;拿来吧你&#xff01;一个app A对另一个app B通过binder跨进程通信&#xff0c;控制app B的SurfaceControl矢量动画。 无论是本地动画还是远程…

C++ Qt开发:运用QJSON模块解析数据

Qt 是一个跨平台C图形界面开发库&#xff0c;利用Qt可以快速开发跨平台窗体应用程序&#xff0c;在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置&#xff0c;实现图形化开发极大的方便了开发效率&#xff0c;本章将重点介绍如何运用QJson组件的实现对JSON文本的灵活解析…

echarts:获取省、市、区/县、镇的地图数据

目录 第一章 前言 第二章 获取地图的数据&#xff08;GeoJSON格式&#xff09; 2.1 获取省、市、区/县地图数据 2.2 获取乡/镇/街道地图数据 第一章 前言 需求&#xff1a;接到要做大屏的需求&#xff0c;其中需要用echarts绘画一个地图&#xff0c;但是需要的地图是区/县…

C语言系列-整数在内存中的存储大小端字节序

&#x1f308;个人主页: 会编程的果子君 ​&#x1f4ab;个人格言:“成为自己未来的主人~” 目录 整数在内存中的存储 大小端字节序和字节序判断 什么是大小端 为什么会有大小端 练习 整数在内存中的存储 在讲解操作符的时候&#xff0c;我们就讲过了下面的内容 整数的2…

高端车规MCU的破局之路

目录 1 低质量的无效内卷 2 高端车规MCU产品共性 2.1 支持标定测量 2.2 低延迟通信加速 2.3 完备的网络安全解决方案 2.4虚拟化 3 国产替代的囚徒困境 1 低质量的无效内卷 近几年&#xff0c;车规MCU国产替代的呼声此消彼长&#xff0c;但仍然集中在低端产品。 从产…

鸿蒙首批原生应用!无感验证已完美适配鸿蒙系统

顶象无感验证已成功适配鸿蒙系统&#xff0c;成为首批鸿蒙原生应用&#xff0c;助力鸿蒙生态的快速发展。 作为全场景分布式操作系统&#xff0c;鸿蒙系统旨在打破不同设备之间的界限&#xff0c;实现极速发现、极速连接、硬件互助、资源共享。迄今生态设备数已突破8亿台&…

软考复习之数据结构篇

算法设计 迭代法&#xff1a;用于求方程的近似根。 1、若方程无解&#xff0c;则算法求出的近似根序列就不会收敛&#xff0c;迭代过程会变成死循环&#xff0c;因此在使用迭代算法前应先考查方程是否有解&#xff0c;并在程序中对迭代的次数给予限制。 2、方程虽有解&#…

第十一篇【传奇开心果系列】BeeWare的Toga开发移动应用示例:Briefcase和Toga 哥俩好

传奇开心果博文系列 系列博文目录BeeWare的Toga开发移动应用示例系列博文目录一、前言二、Briefcase和toga各自的主要功能分别介绍三、使用Toga 开发移动应用Briefcase工具是最佳拍档四、Briefcase搭档Toga创建打包发布联系人移动应用示例代码五、运行测试打包发布六、归纳总结…

RabbitMQ之三种队列之间的区别及如何选型

目录 不同队列之间的区别 Classic经典队列 Quorum仲裁队列 Stream流式队列 如何使用不同类型的队列​ Quorum队列 Stream队列 不同队列之间的区别 Classic经典队列 这是RabbitMQ最为经典的队列类型。在单机环境中&#xff0c;拥有比较高的消息可靠性。 经典队列可以选…

数据库管理-第141期 DG PDB - Oracle DB 23c(20240129)

数据库管理141期 2024-01-29 第141期 DG PDB - Oracle DB 23c&#xff08;20240129&#xff09;1 概念2 环境说明3 操作3.1 数据库配置3.2 配置tnsname3.3 配置强制日志3.4 DG配置3.5 DG配置建立联系3.6 启用所有DG配置3.7 启用DG PDB3.8 创建源PDB的DG配置3.9 拷贝pdbprod1文件…

2023年算法CDO-CNN-BiLSTM-ATTENTION回归预测(matlab)

2023年算法CDO-CNN-BiLSTM-ATTENTION回归预测&#xff08;matlab&#xff09; CDO-CNN-BiLSTM-Attention切诺贝利灾难优化器优化卷积-长短期记忆神经网络结合注意力机制的数据回归预测 Matlab语言。 切诺贝利灾难优化器Chernobyl Disaster Optimizer (CDO)是H. Shehadeh于202…

薅运营商羊毛?封杀!

最近边小缘在蓝点网上看到一则消息 “浙江联通也开始严格排查PCDN和PT等大流量行为 被检测到可能会封停宽带”。 此前中国联通已经在四川和上海等多个省市严查家庭宽带 (部分企业宽带也被查) 使用 PCDN 或 PT&#xff0c;当用户的宽带账户存在大量上传数据的情况&#xff0c;中…

Jupyter notebook文件默认存储路径以及更改方法

目录 1、文件默认存储路径怎么查&#xff1f;2、文件默认存储路径怎么改&#xff1f; 转自&#xff1a;https://blog.csdn.net/fengyeer20120/article/details/109483362 初次使用Jupyter Notebook&#xff0c;确实好用啊&#xff01;但安装Anaconda后&#xff0c;打开Jupyter …

cocos creator 调用预设体Prefab中的方法(调用另一个节点的方法)

调用预设体中的方法 通过cc.instantiate(this.star)创建这个预设体实例这个star预设体中添加了一个脚本组件star.ts 获取到这个脚本组件star.getComponent(‘star’).test()&#xff0c;并调用其中的test()方法同理可以用该方式像另一个节点中传值 //星星预设体property(cc.Pr…

【动态规划】【图论】【C++算法】1575统计所有可行路径

作者推荐 【动态规划】【字符串】【行程码】1531. 压缩字符串 本文涉及知识点 动态规划汇总 图论 LeetCode1575统计所有可行路径 给你一个 互不相同 的整数数组&#xff0c;其中 locations[i] 表示第 i 个城市的位置。同时给你 start&#xff0c;finish 和 fuel 分别表示出…