vue2中signature_pad签名组件

在项目中下载安装npm install signature_pad。

Sign.vue组件代码:


<template>
    <div class="sign">
        <div class="sign-cont" :class="isLandscape ? 'landscape' : ''">
            <div ref="canvasBox" class="canvasBox">
                <canvas class="canvasId" id="canvasId" />
            </div>
            <div class="btns">
                <div class="btn-tips">
                    请在上方空白处书写您的姓名~
                </div>
                <van-button plain round type="primary" @click="$emit('back')">
                    取消
                </van-button>
                <van-button round type="primary" @click="seaveImages" :loading="isLoading">
                    确定并保存
                </van-button>
            </div>
            <div class="top-btns">
                <div class="btn-tips">
                    <van-icon name="edit" size="24" color="#323232" />
                    <div class="btn-color red" @click="changeColor('#D9001B')"></div>
                    <div class="btn-color blue" @click="changeColor('#0000FF')"></div>
                    <div class="btn-color black" @click="changeColor('#323232')"></div>
                </div>
                <div class="btn-text-clean" @click="overwrite">一键清除</div>
            </div>
        </div>

        <!-- <van-popup v-model="showTip">内容</van-popup> -->
    </div>
</template>
  
  
<script>
import { fileUpload } from "@/api/mine";
import SignaturePad from 'signature_pad';
import { Toast } from "vant";
// import { Toast } from 'vant'
export default {
    name: "sign",
    props: [],
    components: {},
    data() {
        return {
            SignaturePad: null,
            config: {
                penColor: "#000000",   //笔刷颜色
                minWidth: 3,       //最小宽度
            },
            isLoading: false,
            showTip: false,
            isLandscape: false,
            canvas: null,
        }
    },
    mounted() {
        this.init();
    },
    methods: {
        init() {
            this.isLoading = false;
            this.canvas = document.getElementById('canvasId');
            this.signaturePad = new SignaturePad(this.canvas, this.config);

            this.landscapeEvent();
            this.showTip = true;
            this.winResize();
        },
        changeColor(color) {
            if (this.signaturePad) {
                this.signaturePad.penColor = color;
                this.signaturePad.clear();
            }
        },

        winResize() {
            const evt
                = 'onorientationchange' in window ? 'orientationchange' : 'resize'

            window.addEventListener(evt, () => {
                setTimeout(() => {
                    this.landscapeEvent();
                }, 10)
            })
        },
        // 旋转操作
        landscapeEvent() {
            this.isLandscape = document.documentElement.clientWidth > document.documentElement.clientHeight;
            this.$nextTick(() => {
                this.canvas.height = this.$refs.canvasBox.clientHeight;;
                this.canvas.width = this.$refs.canvasBox.clientWidth;
            })
            console.log(this.isLandscape)
        },
        overwrite() {
            this.signaturePad && this.signaturePad.clear();
        },
        seaveImages() {
            if (this.signaturePad.isEmpty()) {
                // Toast('签名不能为空');
                Toast({ message: "签名不能为空", duration: 2000, className: this.isLandscape ? "" : "sign-toast" })
            } else {

                this.rotateBase64Img(this.signaturePad.toDataURL(), this.isLandscape ? 0 : -90, async (dataBase64) => {
                    let file = this.dataURLtoFile(dataBase64, new Date().getTime() + ".png");
                    console.log(file, "file");
                    let formData = new FormData();
                    formData.append("file", file);
                    this.isLoading = true;
                    fileUpload(formData)
                        .then(res => {
                            if (res) this.$emit("sign", res.imageUrl)
                            this.isLoading = true;
                        })
                        .catch(err => {
                            this.isLoading = false;
                            console.log(err);
                        });
                })
            }
        },
        //将base64转换为文件..
        dataURLtoFile(dataurl, filename) {
            let arr = dataurl.split(","),
                mime = arr[0].match(/:(.*?);/)[1],
                // eslint-disable-next-line no-undef
                bstr = window.atob(arr[1]),
                n = bstr.length,
                // eslint-disable-next-line no-undef
                u8arr = new Uint8Array(n);
            while (n--) {
                // eslint-disable-next-line no-undef
                u8arr[n] = bstr.charCodeAt(n);
            }
            // eslint-disable-next-line no-undef
            return new File([u8arr], filename, { type: mime });
        },
        // 签完名的图片旋转处理
        rotateBase64Img(src, edg, callback) {
            const canvas = document.createElement('canvas')
            const ctx = canvas.getContext('2d')
            let imgW // 图片宽度
            let imgH // 图片高度
            let size // canvas初始大小
            if (edg % 90 !== 0) {
                console.error('旋转角度必须是90的倍数!')
                return
            }
            edg < 0 && (edg = (edg % 360) + 360)
            const quadrant = (edg / 90) % 4 // 旋转象限
            const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 } // 裁剪坐标
            const image = new Image()
            image.crossOrigin = 'anonymous'
            image.src = src
            image.onload = function () {
                console.log('加载了')

                imgW = image.width
                imgH = image.height
                size = imgW > imgH ? imgW : imgH
                canvas.width = size * 2
                canvas.height = size * 2
                switch (quadrant) {
                    case 0:
                        cutCoor.sx = size
                        cutCoor.sy = size
                        cutCoor.ex = size + imgW
                        cutCoor.ey = size + imgH
                        break
                    case 1:
                        cutCoor.sx = size - imgH
                        cutCoor.sy = size
                        cutCoor.ex = size
                        cutCoor.ey = size + imgW
                        break
                    case 2:
                        cutCoor.sx = size - imgW
                        cutCoor.sy = size - imgH
                        cutCoor.ex = size
                        cutCoor.ey = size
                        break
                    case 3:
                        cutCoor.sx = size
                        cutCoor.sy = size - imgW
                        cutCoor.ex = size + imgH
                        cutCoor.ey = size + imgW
                        break
                }
                ctx?.translate(size, size)
                ctx?.rotate((edg * Math.PI) / 180)
                // drawImage向画布上绘制图片
                ctx?.drawImage(image, 0, 0)
                // getImageData() 复制画布上指定矩形的像素数据
                const imgData = ctx?.getImageData(cutCoor.sx, cutCoor.sy, cutCoor.ex, cutCoor.ey)
                if (quadrant % 2 === 0) {
                    canvas.width = imgW
                    canvas.height = imgH
                }
                else {
                    canvas.width = imgH
                    canvas.height = imgW
                }
                // putImageData() 将图像数据放回画布
                ctx?.putImageData(imgData, 0, 0)
                callback(canvas.toDataURL('image/png'))
            }
        }

    },
};
</script>
  
  
<style lang="less" scoped>
* {
    box-sizing: border-box;
}

.sign {
    width: 100vw;
    height: 100vh;
    background-color: #fff;
    padding: 12px;

    .sign-cont {
        position: relative;
        border-radius: 4px;
        border: 1px solid #D7D9E7;
        width: calc(100vw - 24px);
        height: calc(100vh - 24px);

        .canvasBox {
            width: calc(100vw - 24px - 60px -40px);
            height: calc(100vh - 24px);
            margin-left: 58px;
            margin-right: 38px;
        }

        .top-btns {
            position: absolute;
            left: 312px;
            top: 0;
            width: calc(100vh - 6.4vw);
            transform: rotate(90deg) translateY(-100%);
            transform-origin: 0 0;
            display: flex;
            align-items: center;
            padding: 11px 0px 11px 11px;
            border-bottom: 1Px solid #D7D9E7;
            flex-shrink: 0;
            height: 40Px;
            flex-wrap: wrap;
            font-size: 14px;

            .btn-text-clean {
                font-size: 14Px;
                font-family: PingFangSC-Regular, PingFang SC;
                font-weight: 400;
                color: #4F80FF;
                line-height: 20Px;
                text-align: left;
                margin-right: 16px;
                text-decoration-line: underline;
            }

            .btn-tips {
                font-size: 14Px;
                font-family: PingFangSC-Regular, PingFang SC;
                font-weight: 400;
                line-height: 20Px;
                display: flex;
                align-items: center;
                flex: 2;
            }

            .btn-color {
                width: 12px;
                height: 12px;
                box-shadow: 0px 2px 4px 0px rgba(185, 185, 185, 0.5);
                border-radius: 50%;
                margin-left: 16px;
            }

            .red {
                background: #D9001B;
            }

            .blue {
                background: #0000FF;
            }

            .black {
                background: #323232;
            }
        }

        .btns {
            // border: 1px solid red;
            position: absolute;
            left: 0;
            top: 0;
            width: calc(100vh - 6.4vw);
            transform: rotate(90deg) translateY(-100%);
            transform-origin: 0 0;
            display: flex;
            align-items: center;
            padding: 11Px 0 11Px 11Px;
            border-top: 1Px solid #D7D9E7;
            flex-shrink: 0;
            height: 60Px;
            flex-wrap: wrap;

            .btn-tips {
                font-size: 14Px;
                font-family: PingFangSC-Regular, PingFang SC;
                font-weight: 400;
                color: #363A44;
                line-height: 20Px;
                text-align: left;
                margin-right: 20px;
                flex: 1;
            }

            .van-button {
                height: 36Px;
                width: 183Px;
                // flex: 1;
                margin-right: 12Px;
                font-size: 12Px;
            }

            .van-button--plain.van-button--primary {
                color: #4F80FF;
                border-color: #4F80FF;
                background-color: #fff;
            }

            .van-button--primary {
                background-color: #4F80FF;
                border-color: #4F80FF;
            }

        }

        &.landscape {
            position: relative;

            .canvasBox {
                width: calc(100vw - 24px);
                height: calc(100vh - 24px - 30px -30px);
                margin-left: 0;
                margin-right: 0;
            }

            .btns {
                position: absolute;
                left: 0;
                height: 30px;
                top: calc(100vh - 24px - 30px);
                width: calc(100vw - 24px);
                transform: rotate(0deg) translateY(0);
            }

            .top-btns {
                position: absolute;
                right: 0;
                height: 30px;
                top: calc(100vh - 24px - 30px);
                width: calc(100vw - 24px);
                transform: rotate(0deg) translateY(0);
            }

            // .btn-tips {
            //     flex: none;
            // }
            // .van-button {
            //     flex:1;
            // }
        }
    }
}
</style>

<style lang="less">
.sign-toast {
    transform-origin: 50% 0;
    transform: rotate(90deg);
}
</style>

引入

<!-- 签名 -->
<sign v-if="isShowSign" class="sign-cont" @sign="signEvent" @back="backEvent" />

import Sign from "./components/Sign.vue";
    components: {
        Sign
    },


方法:

        signEvent(url) {
            console.log(url)
            this.isShowSign = false;
            this.$nextTick(() => {
                document.getElementsByClassName("sign-box")[0].scrollIntoView();
            })
        },
        backEvent() {
            this.isShowSign = false;
            this.$nextTick(() => {
                document.getElementsByClassName("sign-box")[0].scrollIntoView();
            })
        },

css:

.sign-cont {
    position: fixed;
    top: 0;
    left: 0;
    transform-origin: center center;
    // .safe-area(16px);
    width: 100vw;
    height: 100vh;

    // z-index: 9999;
}

效果:

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值