最近项目中需要做一个电子签名的控件,踩坑不少。现记录一下。

一、控件效果

vue中使用vue-signature-pad实现电子签名的效果_github

 这是一个之前的项目中的手写签名的一个控件,现在要改成vue版本的。

大概功能就是点击页面中的标题文字“手写签批”,有一个弹框,里面可以手写签名,底部是功能操作,包括撤销、清屏、橡皮擦功能、调节笔刷的粗细、保存等。

二、插件选择

之前因为没有接触过手写签名的功能,所以就去上 ​​ github ​​ 上面搜相关的代码,然后把代码下载下来,一个个的安装 node_modules 文件夹,查看代码效果。但是插件要不就是缺少撤销功能,要不就是没有调节画笔的粗细功能。最后筛选出来的一些主要的代码网址如下:

1、https://github.com/neighborhood999/vue-signature-pad
  // 带撤销效果,保存功能为在控制台打印出图片信息,可调节画笔粗细
2、https://github.com/razztyfication/vue-drawing-canvas
  // 锁定,撤销,清除,线条颜色、粗细,背景色

最后经过筛选,确定用 vue-signature-pad 插件进行开发。网址2用的是 vue-drawing-canvas 插件,放出来这个网址主要是参考了里面的手写签名的回显功能。

github 上对于插件 vue-signature-pad 的介绍和 npm 官网上(​​https://www.npmjs.com/package/vue-signature-pad​​)的一模一样,参考 github 和 npm 官网都可以。

三、开发过程记录

1、如果项目中用的是 vue2 的版本,那么安装 2.0.5版本:

npm install --save vue-signature-pad@2.0.5

如果项目中用的是 vue3 版本,那么安装最新的版本即可:

npm install --save vue-signature-pad

2、 main.js 中引用 vue-signature-pad :

import Vue from 'vue'

import VueSignaturePad from "vue-signature-pad";

Vue.use(VueSignaturePad);

3、完整的 vue 代码如下:

<template>
<div class="handwritten-name-wrap">
<el-button plain @click="handleClick">
手写签名
</el-button>
<div class="img-wrap">
<img :src="imgSrc" alt="" v-if="imgSrc">
</div>
<vxe-modal
v-model="panelVisible"
title="手写签名"
width="600"
height="400"
size="large"
:destroy-on-close="true"
class="signNameModel"
>
<template v-slot>
<div class="signWrap">
<VueSignaturePad
width="100%"
height="100%"
ref="signaturePad"
:options="options"
/>
<footer>
<div class="gtnGroup">
<el-button type="primary" size="mini" @click="undo">撤销</el-button>
<el-button type="primary" size="mini" style="margin-left:20px" @click="clear">清屏</el-button>
<el-button type="primary" size="mini" style="margin-left:20px" @click="save">保存</el-button>
</div>
<div class="otherSet">
<div class="penTxt">笔刷大小:</div>
<div class="circleWrap" :class="{ active: isActive1 }" @click="selSize(1)"><b class="b1"></b></div>
<div class="circleWrap" :class="{ active: isActive2 }" @click="selSize(2)"><b class="b2"></b></div>
<div class="circleWrap" :class="{ active: isActive3 }" @click="selSize(3)"><b class="b3"></b></div>
</div>
</footer>
</div>
</template>
</vxe-modal>
</div>
</template>
<script>
export default {
data(){
return {
panelVisible:false,
panelTitle:"",
options: {
penColor: "#000",
minWidth: 1, //控制画笔最小宽度
maxWidth: 1, //控制画笔最大宽度
},
isActive1:true,
isActive2:false,
isActive3:false,
imgSrc:"",
}
},
methods: {
//手写签名按钮的点击
handleClick(){
this.panelVisible=true;
this.isActive1=true;
this.isActive2=false;
this.isActive3=false;
this.options = {
penColor: "#000",
minWidth: 1,
maxWidth: 1,
}
},
//撤销
undo(){
this.$refs.signaturePad.undoSignature();
},
//清除
clear(){
this.$refs.signaturePad.clearSignature();
},
//保存
save(){
console.log( this.$refs.signaturePad.saveSignature() );
const { isEmpty, data } = this.$refs.signaturePad.saveSignature();
this.imgSrc = data;
this.panelVisible = false;
},
//调节画笔粗细大小
selSize(val){
this.options = {
penColor: "#000",
minWidth: val,
maxWidth: val,
};
if(val==1){
this.isActive1=true;
this.isActive2=false;
this.isActive3=false;
}else if(val==2){
this.isActive1=false;
this.isActive2=true;
this.isActive3=false;
}else if(val==3){
this.isActive1=false;
this.isActive2=false;
this.isActive3=true;
}
}
},
}
</script>
<style lang="scss">
.handwritten-name-wrap{
.img-wrap{
width:100%;
height:164px;
margin-top:2px;
border:1px solid #ccc;
img{
width:70%;
height:100%;
}
}
.signWrap{
height:100%;
display:flex;
flex-direction:column;
justify-content:center;
.signName{
flex:1;
border-top:1px solid #ccc;
}
footer{
height:40px;
border-top:1px solid #ccc;
display:flex;
justify-content: space-between;
align-items: center;
.gtnGroup{
width:50%;
margin-left: 20px;
}
.otherSet{
width:50%;
display:flex;
align-items: center;
.penTxt{
width:70px;
}
.selSize{
width:70px;
}
.el-select__caret{
position: absolute;
right: -3px;
}
.b1,.b2,.b3{
display: inline-block;
background: #000;
border-radius: 50%;
}
.circleWrap{
display: flex;
justify-content: center;
align-items: center;
width:18px;
height:18px;
cursor:pointer;
margin-right:20px;
}
.active{ border:1px dashed #0074d9; }
.b1{width:4px;height:4px}
.b2{width:6px;height:6px}
.b3{width:8px;height:8px}
}
}

}
}
.signNameModel{
.vxe-modal--content{
padding:0 !important;
}
}
</style>

最后的效果:

vue中使用vue-signature-pad实现电子签名的效果_github_02

代码注解:

img 标签加一个 v-if 的判断条件,不然没有图片的时候,会显示一个加载失败的图片,如下图:

vue中使用vue-signature-pad实现电子签名的效果_官网_03

 

调节画笔的粗细是根据 options 属性里面的 minWidth 和 maxWidth 来设置的。因为 vue-signature-pad 插件会根据用户签名时候操作鼠标的速度不同,画笔的线条粗线不一样,所以我设置的时候,把 minWidth 和 maxWidth 都设置为同样的值,来保证画笔的粗细一致。这里注意调节画笔粗细的时候,在方法 selSize 中不能直接设置 this.options.minWidth 的值,要对整体的 this.options 设置才能起效果(此处参考了文章: ​​vue-signature-pad在vue中实现电子签名效果​​ )。

项目到这里基本就结束了,目前还没有橡皮擦的功能,暂时没有添加,个人感觉橡皮擦功能用处不大。后续如果添加了,会续更文章(可参考 ​​vue手写签名组件_Vue签名板组件​​)。

vue-signature-pad 插件中的 options 属性在 github 和 npm 官网中都没有详细的解释,关于 options 中的详细属性可参考 https://vuejsexamples.com/vue-signature-pad-component/ 

 

----------------------------  分割线  --------------------------

这里记录一下关于签名回显的一个踩坑记录。

最终是参考了  ​​https://github.com/razztyfication/vue-drawing-canvas​​ 这里面的代码,才知道是通过 img 标签回显的。

一开始随意上网搜的时候看到 这里面有介绍回显的方法,是通过插件 vue-signature-pad 的 fromData 方法回显的。

但是这样就造就了一个问题:

我们在弹框里面手写签名的地方一般都比较大,回显的区域比较小,所以就造成了回显的时候,签名有往下偏离,造成显示不全的情况。如下图:

vue中使用vue-signature-pad实现电子签名的效果_官网_04

这种方法的实现过程的代码如下:

1、定义回显的标签:

<VueSignaturePad
width="100%"
height="100%"
ref="signaturePadShow"
/>

2、data 中定义 signData ,用来存储插件内置方法 toData() 返回的数据。

data(){
return {
signData:null
}
},

3、js 有关代码处理

save(){
const { isEmpty, data } = this.$refs.signaturePad.saveSignature();
console.log(isEmpty);
console.log(data);

this.panelVisible = false;

this.signData=this.$refs.signaturePad.toData();
console.log(this.signData)
this.$refs.signaturePadShow.fromData(this.signData);
},

通过在控制台打印 this.signData 可以看出来端倪:

vue中使用vue-signature-pad实现电子签名的效果_官网_05

 

通过控制台的数据,可以看出来最主要的是 x、y轴的坐标值。因为插件底层是通过 canvas 绘制的,我们签名的时候,canvas 画布比较大,回显的时候画布比较小,而我们的 this.signData 数据是签名的时候的数据,所以就造成了回显的时候图片往下偏离的情况。

解决办法就是等比例的缩小 this.signData 数据中的 x 和 y 的值。

例如我们签名的时候,签名区域的 canvas 元素的高度为 300px,回显区域的 canvas 元素的高度为 150px,那么我们就应该把 this.signData 数据中的 x、y值等比例的缩小,缩小的比例为 (300-150)/300 = 50% ,即缩减比例为 0.5,所以代码更改为:

save(){
const { isEmpty, data } = this.$refs.signaturePad.saveSignature();
console.log(isEmpty);
console.log(data);

this.panelVisible = false;

this.signData=this.$refs.signaturePad.toData();
console.log(this.signData)
if(this.signData && this.signData.length>0){
this.signData.map(val=>{
val.points.map(list=>{
list.x = list.x*0.5
list.y = list.y*0.5
})
})
}
this.$refs.signaturePadShow.fromData(this.signData);
},

至此,签名的回显就没有问题了。

但是不建议用插件 vue-signature-pad 中的内置方法 fromData() 回显我们的签名,因为我们终究要把签名的图片数据存储到后端,回显的时候通过接口获取签名的图片相关数据进行回显。所以我们最后保存的时候给后端传递的数据为 saveSignature() 方法中返回的 data 属性值,data为通过 base64 编码的图片信息,获取的时候也是获取相关的图片信息,然后通过设置 img 标签的 src 属性值进行回显。

 

完结!撒花~~~