实现形式:html页面中引入vue方式

一、核心代码 - html部分

<div id="app" v-cloak>
        <!-- 旋转前 -->
        <img src="" id="mysignature">
        <!-- 旋转后 -->
        <img :src="signature" id="mysignatureCopy">
        <van-popup v-model="showUserSign" class="buyShow">
            <user-sign :show-user-sign="showUserSign" @changepopup="changePopup" @signature="getSignature"></user-sign>
        </van-popup>
</div>

二、核心代码 - js部分

<script>
    /**
     * 用户签名组件
     */
    /* 注册全局组件 */
    Vue.component('user-sign', {
        template: `
      <div class="signBox">
        <canvas ref="copyCanvas" id="copyCanvas"></canvas>
        <div class="signature">
          <div id="sign" ref="fRoot">
            <div class="mask">
              <div class="qianmings">
                <span>本人签名</span>
              </div>
             
            </div>
          </div>
          <div class="signBtn">
            <button @click="closeUserSign">关闭</button>
            <button @click="clearCanvas">清除</button>
            <button @click="saveCanvas">保存</button>
          </div>
        </div>
      </div>
    `,
        props: ['showUserSign'],
        data() {
            return {
                linewidth: 4,
                color: '#404040',
                background: 'transparent',
                drawStatus: false,
                cxt: '',
                cxtCopy: '',
                copy: '',
                canvasStyle: {
                    width: '',
                    height: ''
                }
            };
        },
        mounted() {
            this.initCanvas()
        },
        methods: {
            closeUserSign() {
                // api.closeWin()
                this.$emit('changepopup', false)
            },
            initCanvas() {
                let canvas = document.createElement("canvas")
                this.canvas = canvas
                this.$refs.fRoot.appendChild(canvas)
                let cxt = canvas.getContext("2d")
                this.cxt = cxt
                let width = this.$refs.fRoot.clientWidth
                let height = this.$refs.fRoot.clientHeight
                cxt.fillStyle = this.background;
                canvas.width = width;
                canvas.height = height;
                this.canvasStyle.width = width;
                this.canvasStyle.height = height;
                // cxt.fillRect(0, 0, width, height);
                cxt.strokeStyle = this.color;
                cxt.lineWidth = this.linewidth;
                cxt.lineCap = "round";
                this.cxtCopy = this.$refs.copyCanvas.getContext("2d");
                this.copy = this.$refs.copyCanvas
                const offset = 24;
                //开始绘制
                canvas.addEventListener(
                    "touchstart",
                    function (e) {
                        this.drawStatus = true
                        cxt.beginPath();
                        cxt.moveTo(
                            e.changedTouches[0].pageX - offset,
                            e.changedTouches[0].pageY - offset
                        );
                    }.bind(this),
                    false
                );
                //绘制中
                canvas.addEventListener(
                    "touchmove",
                    function (e) {
                        cxt.lineTo(
                            e.changedTouches[0].pageX - offset,
                            e.changedTouches[0].pageY - offset
                        );
                        cxt.stroke();
                    }.bind(this),
                    false
                );
                //结束绘制
                canvas.addEventListener(
                    "touchend",
                    function () {
                        cxt.closePath();
                    }.bind(this),
                    false
                );
            },
            clearCanvas() {
                // 清除画布
                this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
                this.drawStatus = false
            },
            saveCanvas() {
                // 保存图片,直接转base64
                let imgBase64Copy = ''
                let signature = ''
                const imgBase64 = this.canvas.toDataURL();
                const img = document.querySelector("#mysignature");
                img.src = imgBase64;
                // console.log(imgBase64)
                img.onload = () => {
                    // 图片宽高
                    let iw = img.width
                    let ih = img.height
                    console.log(iw,ih)
                    // canvas宽高
                    // let cw = 763
                    let cw = 145
                    let ch = 86
                    // let ch = 346
                    const opt = this.getXY(cw, ch, ih, iw)
                    this.cxtCopy.clearRect(0, 0, opt.rw, opt.rh)
                    this.cxtCopy.translate(0, opt.rh + opt.points[1])
                    this.cxtCopy.rotate(-Math.PI / 2)
                    this.cxtCopy.drawImage(img, opt.points[1], opt.points[0], opt.rh, opt.rw)
                    // 将有效部分图片截取
                    let frame = this.cxtCopy.getImageData(0, 0, opt.rw, opt.rh)
                    // 将canvas保持与图片大小相同
                    this.copy.height = opt.rh
                    this.copy.width = opt.rw
                    // 清除画布
                    this.cxtCopy.clearRect(0, 0, opt.rw, opt.rh)
                    // 重新绘制
                    this.cxtCopy.putImageData(frame, 0, 0)
                    imgBase64Copy = this.copy.toDataURL();
                    // console.log(imgBase64Copy)
                    if (this.drawStatus) {
                        signature = imgBase64Copy;
                    } else {
                        signature = ''
                    }
                    this.drawStatus = false
                    this.clearCanvas()
                    this.$emit('signature', signature)
                    setTimeout(() => {
                        // api.closeWin()
                        this.uploadOss(imgBase64Copy)
                    }, 3000)

                }
            },
            /**
           * @description: 
           * @param {*} cw canvas宽度
           * @param {*} ch canvas高度
           * @param {*} iw img宽度
           * @param {*} ih img高度
           * @return {*}
           */
            getXY(cw, ch, iw, ih) {
                // 比较宽高比例,大的一边撑满
                let li = iw / ih
                let lc = cw / ch
                // console.log('图片宽高比', li, 'canvas宽高比', lc);
                // 需要绘制的宽高起点
                let points = [0, 0]
                let rw, rh
                let wall = false // 是否宽度占满
                if (li > lc) {
                    // 宽度100%,等比缩放
                    rw = cw
                    rh = rw / li
                    points[0] = 0
                    points[1] = (ch - rh) / 2
                    wall = true
                } else {
                    rh = ch
                    rw = rh * li
                    points[0] = (cw - rw) / 2
                    points[1] = 0
                    wall = false
                }
                rw = Math.floor(rw)
                rh = Math.floor(rh)
                // console.log('canvas', cw, ch, 'img', iw, ih, '绘制', rw, rh);
                return {
                    points,
                    rw,
                    rh
                }
            },
            uploadOss(imgBase64Copy) {
                // 1.先转为 blob格式  file.content是此文件的base64格式 
                let blob = this.dataURLtoBlob(imgBase64Copy);
                // 拿到文件名
                // let fileName = file.file.name;

                // 2,在转为 file类型
                let file1 = this.blobToFile(blob, '签名.png');
                console.log("file1:", file1);
                const formData = new FormData();//将选择上传的name="pdfFile"的文件赋值给formData
                // formData.append("bizType", '1');//接口还需要一个用户名参数,通过append附加到formData上
                formData.append("file", file1);//接口还需要一个用户名参数,通过append附加到formData上
                var token = api.getPrefs({
                    sync: true,
                    key: 'token'
                })
                console.log(token, url.uploadFile())
                $.ajax({
                    type: 'post',
                    url: url.uploadFile(),
                    data: formData,
                    processData: false,
                    contentType: false,
                    dataType: 'json',
                    headers: {
                        'X-Token': token
                    },
                    success: function (res) {
                        console.log('上传成功', JSON.stringify(res));
                        api.toast({
                            msg: '上传成功',
                            duration: 3000,
                            location: 'middle'
                        });
                    },
                    error: function (err) {
                        api.hideProgress();
                        api.toast({
                            msg: '上传失败',
                            duration: 3000,
                            location: 'middle'
                        });
                    }
                });
            },
            //1,先将base64转换为blob
            dataURLtoBlob(dataurl) {
                var arr = dataurl.split(','),
                    mime = arr[0].match(/:(.*?);/)[1],
                    bstr = atob(arr[1]),
                    n = bstr.length,
                    u8arr = new Uint8Array(n);
                while (n--) {
                    u8arr[n] = bstr.charCodeAt(n);
                }
                return new Blob([u8arr], { type: mime });
            },
            //2,再将blob转换为file
            blobToFile(theBlob, fileName) {
                theBlob.lastModifiedDate = new Date();  // 文件最后的修改日期
                theBlob.name = fileName;       
                console.log(fileName)         // 文件名
                return new File([theBlob], fileName, { type: theBlob.type, lastModified: Date.now() });
            },
        }
    });

</script>
<script>
    new Vue({
        el: '#app',
        data() {
            return {
                showUserSign: true,
                signature: ''
            }
        },
        methods: {
            getSignature(val) {
                this.signature = val
                this.showUserSign = false
            },
            goSign() {
                this.showUserSign = true
            },
            changePopup(val) {
                this.showUserSign = val
                if (!val) {
                    api.closeWin()
                }
            }
        }
    })
</script>

三、全部代码(html文件)

<!DOCTYPE HTML>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport"
        content="maximum-scale=1.0, minimum-scale=1.0, user-scalable=0, initial-scale=1.0, width=device-width" />
    <meta name="format-detection" content="telephone=no, email=no, date=no, address=no">
    <title>Hello APP</title>
    <link rel="stylesheet" type="text/css" href="../../css/api.css" />
    <link rel="stylesheet" type="text/css" href="../../css/aui/aui.css">

    <script type="text/javascript" src="../../script/vue.js"></script>
    <link rel="stylesheet" href="../../css/index.css" />
    <link rel="stylesheet" href="../../css/base.css" />
    <script src="../../script/vant.min.js"></script>
    <style>
        .signature,
        #sign,
        .signature button {
            box-sizing: border-box;
        }

        #sign {
            position: relative;
        }

        .signBtn {
            flex-shrink: 0;
            height: 70px;
            display: flex;
            align-items: center;
            justify-content: space-around;
        }

        #sign canvas {
            width: 100%;
            height: 100%;
        }

        .copyCanvas {
            display: none;
        }

        .address {
            margin: 1rem;
            padding-bottom: 1rem;
            margin-bottom: 0rem;
            display: flex;
            align-items: center;
            justify-content: space-between;
        }

        .qianming img {
            width: 1rem;
            float: right;
            margin-top: 4px;
        }

        .qianming button {
            width: 1rem;
            float: right;
            display: flex;
            justify-content: flex-end;
        }

        .qianming {
            align-items: flex-start;
        }

        .signature {
            background-color: #fff;
            /* width: calc(100% - 2rem);
  height: calc(100% - 2rem);
  margin: 1rem; */
            height: 100%;
            width: calc(100vw - 2rem);
            display: flex;
            flex-direction: column;
            justify-content: space-between;
        }

        .signature>div:first-child {
            height: calc(100% - 70px - (0.6*16*2px));
            border: 1px dashed #c1c1c1;
            margin: 0.6rem;
        }

        .signature button {
            color: #404040;
            font-size: 1.2rem;
            width: initial;
            transform: rotate(90deg);
            text-align: center;
            width: 100%;
            height: 100%;
            border: none;
            outline: none;
            background-color: transparent;
            padding: 0;
        }

        .mask {
            position: absolute;
            width: 100%;
            height: 100%;
            z-index: 1;
            display: flex;
            text-align: right;
        }

        canvas {
            position: relative;
            z-index: 2;
        }

        .mask div {
            flex: 1;
            position: relative;
        }

        .mask div span {
            transform: rotate(90deg);
            display: inline-block;
            color: #c1c1c1;
            position: relative;
            right: 0;
            top: 40px;
            font-weight: 500;
        }

        .qianmings {
            border-right: 1px dashed #c1c1c1;
        }

        .btns button {
            padding: 0.7rem;
            background-color: #5c92ff;
            border-radius: 1.5rem;
            font-size: 1.1rem;
            box-shadow: 0px 2px 5px rgb(0 0 0 / 20%);
            color: #fff;
            width: 100%;
            box-sizing: border-box;
            margin-top: 15px;
        }

        .btns {
            margin: 1rem;
        }

        .signBox {
            width: calc(100vw - 2rem);
            height: calc(100vh - 2rem);
            overflow: hidden;
        }

        #copyCanvas {
            display: none;
        }
    </style>
</head>

<body>
    <div id="app" v-cloak>
        <!-- 旋转前 -->
        <img src="" id="mysignature">
        <!-- 旋转后 -->
        <img :src="signature" id="mysignatureCopy">
        <van-popup v-model="showUserSign" class="buyShow">
            <user-sign :show-user-sign="showUserSign" @changepopup="changePopup" @signature="getSignature"></user-sign>
        </van-popup>
    </div>

</body>
<script type="text/javascript" src="../../script/api.js"></script>
<script type="text/javascript" src="../../script/jquery.min.js"></script>
<script type="text/javascript" src="../../script/mui.min.js"></script>
<script type="text/javascript" src="../../script/config.js"></script>
<script>
    /**
     * 用户签名组件
     */
    /* 注册全局组件 */
    Vue.component('user-sign', {
        template: `
      <div class="signBox">
        <canvas ref="copyCanvas" id="copyCanvas"></canvas>
        <div class="signature">
          <div id="sign" ref="fRoot">
            <div class="mask">
              <div class="qianmings">
                <span>本人签名</span>
              </div>
             
            </div>
          </div>
          <div class="signBtn">
            <button @click="closeUserSign">关闭</button>
            <button @click="clearCanvas">清除</button>
            <button @click="saveCanvas">保存</button>
          </div>
        </div>
      </div>
    `,
        props: ['showUserSign'],
        data() {
            return {
                linewidth: 4,
                color: '#404040',
                background: 'transparent',
                drawStatus: false,
                cxt: '',
                cxtCopy: '',
                copy: '',
                canvasStyle: {
                    width: '',
                    height: ''
                }
            };
        },
        mounted() {
            this.initCanvas()
        },
        methods: {
            closeUserSign() {
                // api.closeWin()
                this.$emit('changepopup', false)
            },
            initCanvas() {
                let canvas = document.createElement("canvas")
                this.canvas = canvas
                this.$refs.fRoot.appendChild(canvas)
                let cxt = canvas.getContext("2d")
                this.cxt = cxt
                let width = this.$refs.fRoot.clientWidth
                let height = this.$refs.fRoot.clientHeight
                cxt.fillStyle = this.background;
                canvas.width = width;
                canvas.height = height;
                this.canvasStyle.width = width;
                this.canvasStyle.height = height;
                // cxt.fillRect(0, 0, width, height);
                cxt.strokeStyle = this.color;
                cxt.lineWidth = this.linewidth;
                cxt.lineCap = "round";
                this.cxtCopy = this.$refs.copyCanvas.getContext("2d");
                this.copy = this.$refs.copyCanvas
                const offset = 24;
                //开始绘制
                canvas.addEventListener(
                    "touchstart",
                    function (e) {
                        this.drawStatus = true
                        cxt.beginPath();
                        cxt.moveTo(
                            e.changedTouches[0].pageX - offset,
                            e.changedTouches[0].pageY - offset
                        );
                    }.bind(this),
                    false
                );
                //绘制中
                canvas.addEventListener(
                    "touchmove",
                    function (e) {
                        cxt.lineTo(
                            e.changedTouches[0].pageX - offset,
                            e.changedTouches[0].pageY - offset
                        );
                        cxt.stroke();
                    }.bind(this),
                    false
                );
                //结束绘制
                canvas.addEventListener(
                    "touchend",
                    function () {
                        cxt.closePath();
                    }.bind(this),
                    false
                );
            },
            clearCanvas() {
                // 清除画布
                this.cxt.clearRect(0, 0, this.canvas.width, this.canvas.height);
                this.drawStatus = false
            },
            saveCanvas() {
                // 保存图片,直接转base64
                let imgBase64Copy = ''
                let signature = ''
                const imgBase64 = this.canvas.toDataURL();
                const img = document.querySelector("#mysignature");
                img.src = imgBase64;
                // console.log(imgBase64)
                img.onload = () => {
                    // 图片宽高
                    let iw = img.width
                    let ih = img.height
                    console.log(iw,ih)
                    // canvas宽高
                    let cw = 145
                    let ch = 86
                    const opt = this.getXY(cw, ch, ih, iw)
                    this.cxtCopy.clearRect(0, 0, opt.rw, opt.rh)
                    this.cxtCopy.translate(0, opt.rh + opt.points[1])
                    this.cxtCopy.rotate(-Math.PI / 2)
                    this.cxtCopy.drawImage(img, opt.points[1], opt.points[0], opt.rh, opt.rw)
                    // 将有效部分图片截取
                    let frame = this.cxtCopy.getImageData(0, 0, opt.rw, opt.rh)
                    // 将canvas保持与图片大小相同
                    this.copy.height = opt.rh
                    this.copy.width = opt.rw
                    // 清除画布
                    this.cxtCopy.clearRect(0, 0, opt.rw, opt.rh)
                    // 重新绘制
                    this.cxtCopy.putImageData(frame, 0, 0)
                    imgBase64Copy = this.copy.toDataURL();
                    // console.log(imgBase64Copy)
                    if (this.drawStatus) {
                        signature = imgBase64Copy;
                    } else {
                        signature = ''
                    }
                    this.drawStatus = false
                    this.clearCanvas()
                    this.$emit('signature', signature)
                    setTimeout(() => {
                        // api.closeWin()
                        this.uploadOss(imgBase64Copy)
                    }, 3000)

                }
            },
            /**
           * @description: 
           * @param {*} cw canvas宽度
           * @param {*} ch canvas高度
           * @param {*} iw img宽度
           * @param {*} ih img高度
           * @return {*}
           */
            getXY(cw, ch, iw, ih) {
                // 比较宽高比例,大的一边撑满
                let li = iw / ih
                let lc = cw / ch
                // console.log('图片宽高比', li, 'canvas宽高比', lc);
                // 需要绘制的宽高起点
                let points = [0, 0]
                let rw, rh
                let wall = false // 是否宽度占满
                if (li > lc) {
                    // 宽度100%,等比缩放
                    rw = cw
                    rh = rw / li
                    points[0] = 0
                    points[1] = (ch - rh) / 2
                    wall = true
                } else {
                    rh = ch
                    rw = rh * li
                    points[0] = (cw - rw) / 2
                    points[1] = 0
                    wall = false
                }
                rw = Math.floor(rw)
                rh = Math.floor(rh)
                // console.log('canvas', cw, ch, 'img', iw, ih, '绘制', rw, rh);
                return {
                    points,
                    rw,
                    rh
                }
            },
            uploadOss(imgBase64Copy) {
                // 1.先转为 blob格式  file.content是此文件的base64格式 
                let blob = this.dataURLtoBlob(imgBase64Copy);
                // 拿到文件名
                // let fileName = file.file.name;

                // 2,在转为 file类型
                let file1 = this.blobToFile(blob, '签名.png');
                console.log("file1:", file1);
                const formData = new FormData();//将选择上传的name="pdfFile"的文件赋值给formData
                // formData.append("bizType", '1');//接口还需要一个用户名参数,通过append附加到formData上
                formData.append("file", file1);//接口还需要一个用户名参数,通过append附加到formData上
                var token = api.getPrefs({
                    sync: true,
                    key: 'token'
                })
                console.log(token, url.uploadFile())
                $.ajax({
                    type: 'post',
                    url: url.uploadFile(),
                    data: formData,
                    processData: false,
                    contentType: false,
                    dataType: 'json',
                    headers: {
                        'X-Token': token
                    },
                    success: function (res) {
                        console.log('上传成功', JSON.stringify(res));
                        api.toast({
                            msg: '上传成功',
                            duration: 3000,
                            location: 'middle'
                        });
                    },
                    error: function (err) {
                        api.hideProgress();
                        api.toast({
                            msg: '上传失败',
                            duration: 3000,
                            location: 'middle'
                        });
                    }
                });
            },
            //1,先将base64转换为blob
            dataURLtoBlob(dataurl) {
                var arr = dataurl.split(','),
                    mime = arr[0].match(/:(.*?);/)[1],
                    bstr = atob(arr[1]),
                    n = bstr.length,
                    u8arr = new Uint8Array(n);
                while (n--) {
                    u8arr[n] = bstr.charCodeAt(n);
                }
                return new Blob([u8arr], { type: mime });
            },
            //2,再将blob转换为file
            blobToFile(theBlob, fileName) {
                theBlob.lastModifiedDate = new Date();  // 文件最后的修改日期
                theBlob.name = fileName;       
                console.log(fileName)         // 文件名
                return new File([theBlob], fileName, { type: theBlob.type, lastModified: Date.now() });
            },
        }
    });

</script>
<script>
    new Vue({
        el: '#app',
        data() {
            return {
                showUserSign: true,
                signature: ''
            }
        },
        methods: {
            getSignature(val) {
                this.signature = val
                this.showUserSign = false
            },
            goSign() {
                this.showUserSign = true
            },
            changePopup(val) {
                this.showUserSign = val
                if (!val) {
                    api.closeWin()
                }
            }
        }
    })
</script>




</html>

四、实现效果

1.签名页面

2.签名结束页面

3.处理后签名图片

GitHub 加速计划 / vu / vue
207.55 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐