天天看點

vue中使用vue-signature-pad實作電子簽名的效果

最近項目中需要做一個電子簽名的控件,踩坑不少。現記錄一下。

一、控件效果

vue中使用vue-signature-pad實作電子簽名的效果

 這是一個之前的項目中的手寫簽名的一個控件,現在要改成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 [email protected]      

如果項目中用的是 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實作電子簽名的效果

代碼注解:

img 标簽加一個 v-if 的判斷條件,不然沒有圖檔的時候,會顯示一個加載失敗的圖檔,如下圖:

vue中使用vue-signature-pad實作電子簽名的效果

② 調節畫筆的粗細是根據 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實作電子簽名的效果

這種方法的實作過程的代碼如下:

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實作電子簽名的效果

通過控制台的資料,可以看出來最主要的是 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 屬性值進行回顯。