实现电子签名

398 阅读1分钟

1.安装vue-esign插件

npm install vue-esign --save

2.引用

  • 全局引用
import vueEsign from "vue-esign";
Vue.use(vueEsign);
  • 局部引用
import vueEsign from "vue-esign"
components: {vueEsign}

3.使用

<template>
  <div class="page sign-page">
    <div class="back-arrow" @click="back">
      <img class="arrow-left" src="../assets/images/arrow-right.png" alt="" />
    </div>
    <div class="sign-box">
      <div
        id="sign"
        class="content"
        style="background-color: white;padding-top:35px"
      >
        <vue-esign
          v-if="platform != 'mobile'"
          ref="esign"
          :height="signHeight"
          :width="375"
          :isCrop="isCrop"
          :lineWidth="lineWidth"
          :lineColor="lineColor"
          :bgColor.sync="bgColor"
        />
        <vue-esign
          v-else
          ref="esign"
          :height="signHeight"
          :isCrop="isCrop"
          :lineWidth="lineWidth"
          :lineColor="lineColor"
          :bgColor.sync="bgColor"
        />
      </div>
      <div class="con-btn" style="background-color: blue">
        <span class="submit-btn size14" @click="handleGenerate()"
          ><span class="rotate-text">确认签名</span></span
        >
        <span class="staging-btn size14" @click="handleReset()"
          ><span class="rotate-text">清除签名</span>
        </span>
      </div>
    </div>
  </div>
</template>

<script>
import { photoUpload } from "../assets/js/getData";
import utils from "../assets/js";
import compress from "../assets/js/compress";
import html2canvas from "html2canvas";
export default {
  name: "esignTest",
  data() {
    return {
      platform: this.$platform,
      lineWidth: 6,
      lineColor: "#000000",
      bgColor: "",
      resultImg: "",
      isCrop: false,
      signHeight: document.body.clientHeight || 700,
    };
  },
 methods: {
    back() {
      this.$emit("back");
    },
    handleReset() {
      this.$refs.esign.reset();
    },
    getUserId() {
      if (this.$route.params.id) {
        return this.$route.params.id;
      } else {
        let dealdata = window.location.search.replace("?", "");
        let item = [];
        let result = "";
        if (dealdata.split("=").length > 1) {
          dealdata = dealdata.split("&");
          dealdata.forEach((v) => {
            item = v.split("=");
            if (item[0] == "userId") {
              result = item[1];
            }
          });
          return result;
        }
      }
    },
    handleGenerate() {
      this.$toast.loading({
        message: "生成图片中...",
        forbidClick: true,
      });
      this.$refs.esign
        .generate()
        .then((res) => {
          this.handleImgRotate().then((r) => {
            this.resultImg = r;
            const blob = utils.convertBase64UrlToBlob(this.resultImg);
            const file = utils.blobToFile(
              blob,
              `images${Math.floor(Math.random() * 100000)}`
            );
            if (file.size > 1048576) {
              //图片太小了,压缩会变大,大于1M才压纹
              // 压缩图片并上传
              compress(
                this.resultImg,
                {
                  width: window.innerWidth,
                  height: window.innerHeight,
                  quality: 0.8,
                },
                (data) => {
                  this.uploadImg();
                }
              );
            } else {
              this.uploadImg();
            }
          });
        })
        .catch((err) => {
          alert(err); // 画布没有签字时会执行这里 'Not Signned'
        })
        .finally(() => {});
    },
    // 上传图片
    uploadImg() {
      // 上传图片
      const blobs = utils.convertBase64UrlToBlob(this.resultImg);
      const files = utils.blobToFile(
        blobs,
        `images${Math.floor(Math.random() * 100000)}`
      );
      let params = new FormData();
      params.append("file", files);
      params.append("ticket", this.getUserId());
      params.append("type", 0);
      if (this.$route.params.id) {
        params.append("ticketType", 0);
      } else {
        params.append("ticketType", 1);
      }
      this.$toast.loading({
        message: "图片上传中...",
        forbidClick: true,
      });
      // 上传
      photoUpload(params, (data) => {
        if (data.data) {
          this.$emit("submitImg", this.resultImg, data.data.picId);
          this.$emit("back");
          this.$toast("上传成功");
          this.$nextTick(() => {
            this.$refs.esign.reset();
            let img = document.getElementById("sign");
            img.style.transform = "rotateZ(0deg)";
          });
        } else {
          this.$toast.fail(data.msg);
          let img = document.getElementById("sign");
          img.style.transform = "rotateZ(0deg)";
        }
      });
    },
    // 处理图片旋转 方法
    handleImgRotate() {
      let img = document.getElementById("sign");
      img.style.transform = "rotateZ(90deg)";
      var w = parseInt(img.offsetWidth);
      var h = parseInt(img.offsetHeight);
      let _this = this;
      var url;
      return new Promise((resolve) => {
        html2canvas(img).then((canvas) => {
          if (canvas) {
            canvas.style.width = w + "px";
            canvas.style.height = h + "px";
            url = canvas.toDataURL();
            const img2 = new Image();
            img2.src = url;
            img2.style.transform = " scale(1.1)";
            img2.onload = () => {
              resolve(url);
            };
          }
        });
      });
    },
  },
};
</script>

<style scoped lang="less">
.page {
  position: fixed;
  width: 100%;
  background-color: white;
  top: 0px;
  right: 0px;
  z-index: 999;
  .sign-box {
    width: 100%;
    display: flex;
  }
  .content {
    width: 87%;
    height: 100vh;
    // background-color: wheat;
    background-size: 100% 100%;
    background-position: center center;
    .sign-wrap {
      width: 100%;
      height: 100%;
    }
  }
  .con-btn {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-content: center;
    justify-content: space-between;
    opacity: 0.75;
    height: 100vh;
    span {
      flex: 1;
      text-align: center;
      font-size: 18px;
      width: 100%;
      //   height: 48px;
      display: flex;
      align-items: center;
      justify-content: center;
      width: 150%;

      .rotate-text {
        transform: rotateZ(-90deg);
        padding-bottom: 23px;
        font-size: 14px;
      }
    }
    .staging-btn {
      color: #007bf6;
      background: #fff;
      border-top: 1px solid #ebebeb;
    }
    .submit-btn {
      color: #fff;
      background: #007bf6;
    }
  }
}
.back-arrow {
  position: fixed;
  top: 10px;
  left: 0px;
  .arrow-left {
    transform: rotateY(-180deg);
    padding-right: 10px;
    width: 15px;
    height: 19px;
  }
}
</style>

引用文件index.js

 // base64 转blob
  convertBase64UrlToBlob(dataurl) {
    let arr = dataurl.split(",");
    let mime = arr[0].match(/:(.*?);/)[1];
    let bstr = atob(arr[1]);
    let n = bstr.length;
    let u8arr = new Uint8Array(n);
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], { type: mime });
  },
  // 将blob转成file
  blobToFile(theBlob, fileName) {
    theBlob.lastModifiedDate = new Date();
    theBlob.name = fileName;
    return theBlob;
  },
  formatTime,
};

export default utils;

引用文件compress.js

import utils from "../../assets/js";

function detectVerticalSquash(img) {
  // 拍照在IOS7或以下的机型会出现照片被压扁的bug
  var data;
  var ih = img.naturalHeight;
  var canvas = document.createElement("canvas");
  canvas.width = 1;
  canvas.height = ih;
  var ctx = canvas.getContext("2d");
  ctx.drawImage(img, 0, 0);
  try {
    data = ctx.getImageData(0, 0, 1, ih).data;
  } catch (err) {
    console.log("Cannot check verticalSquash: CORS?");
    return 1;
  }
  var sy = 0;
  var ey = ih;
  var py = ih;
  while (py > sy) {
    var alpha = data[(py - 1) * 4 + 3];
    if (alpha === 0) {
      ey = py;
    } else {
      sy = py;
    }
    py = (ey + sy) >> 1; // py = parseInt((ey + sy) / 2)
  }
  var ratio = py / ih;
  return ratio === 0 ? 1 : ratio;
}

/**
 * dataURI to blob, ref to https://gist.github.com/fupslot/5015897
 * @param dataURI
 */
function dataURItoBuffer(dataURI) {
  var byteString = atob(dataURI.split(",")[1]);
  var buffer = new ArrayBuffer(byteString.length);
  var view = new Uint8Array(buffer);
  for (var i = 0; i < byteString.length; i++) {
    view[i] = byteString.charCodeAt(i);
  }
  return buffer;
}
function dataURItoBlob(dataURI) {
  var mimeString = dataURI
    .split(",")[0]
    .split(":")[1]
    .split(";")[0];
  var buffer = dataURItoBuffer(dataURI);
  return new Blob([buffer], { type: mimeString });
}

/**
 * 获取图片的orientation
 * ref to http://stackoverflow.com/questions/7584794/accessing-jpeg-exif-rotation-data-in-javascript-on-the-client-side
 */
function getOrientation(buffer) {
  var view = new DataView(buffer);
  if (view.getUint16(0, false) != 0xffd8) return -2;
  var length = view.byteLength,
    offset = 2;
  while (offset < length) {
    var marker = view.getUint16(offset, false);
    offset += 2;
    if (marker == 0xffe1) {
      if (view.getUint32((offset += 2), false) != 0x45786966) return -1;
      var little = view.getUint16((offset += 6), false) == 0x4949;
      offset += view.getUint32(offset + 4, little);
      var tags = view.getUint16(offset, little);
      offset += 2;
      for (var i = 0; i < tags; i++)
        if (view.getUint16(offset + i * 12, little) == 0x0112)
          return view.getUint16(offset + i * 12 + 8, little);
    } else if ((marker & 0xff00) != 0xff00) break;
    else offset += view.getUint16(offset, false);
  }
  return -1;
}

/**
 * 修正拍照时图片的方向
 * ref to http://stackoverflow.com/questions/19463126/how-to-draw-photo-with-correct-orientation-in-canvas-after-capture-photo-by-usin
 */
function orientationHelper(canvas, ctx, orientation) {
  const w = canvas.width,
    h = canvas.height;
  if (orientation > 4) {
    canvas.width = h;
    canvas.height = w;
  }
  switch (orientation) {
    case 2:
      ctx.translate(w, 0);
      ctx.scale(-1, 1);
      break;
    case 3:
      ctx.translate(w, h);
      ctx.rotate(Math.PI);
      break;
    case 4:
      ctx.translate(0, h);
      ctx.scale(1, -1);
      break;
    case 5:
      ctx.rotate(0.5 * Math.PI);
      ctx.scale(1, -1);
      break;
    case 6:
      ctx.rotate(0.5 * Math.PI);
      ctx.translate(0, -h);
      break;
    case 7:
      ctx.rotate(0.5 * Math.PI);
      ctx.translate(w, -h);
      ctx.scale(-1, 1);
      break;
    case 8:
      ctx.rotate(-0.5 * Math.PI);
      ctx.translate(-w, 0);
      break;
  }
}

/**
 * 压缩图片
 */
function compress(path, options, callback) {
  const blobs = utils.convertBase64UrlToBlob(path);
  const file = utils.blobToFile(
    blobs,
    `images${Math.floor(Math.random() * 1000000)}`
  );
  if (file.size < 800000) {
    callback(path);
    return;
  }
  const img = new Image();
  img.src = path;
  img.onload = function() {
    const ratio = detectVerticalSquash(img);
    const orientation = getOrientation(dataURItoBuffer(path));
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d");

    const maxW = options.width;
    const maxH = options.height;
    let w = img.width;
    let h = img.height;
    let dataURL;
    if (w < h && h > maxH) {
      w = parseInt((maxH * img.width) / img.height);
      h = maxH;
    } else if (w >= h && w > maxW) {
      h = parseInt((maxW * img.height) / img.width);
      w = maxW;
    }

    canvas.width = w * 2;
    canvas.height = h * 2;

    if (orientation > 0) {
      orientationHelper(canvas, ctx, orientation);
    }

    ctx.drawImage(img, 0, 0, w * 2, (h * 2) / ratio);
    dataURL = canvas.toDataURL("image/png", options.quality || 0.5);

    if (/;base64,null/.test(dataURL) || /;base64,$/.test(dataURL)) {
      // 压缩失败,以base64上传的,直接报错不上传
      new Error("Compress fail, dataURL is " + dataURL + ".");
    } else {
      const blobNumber = utils.convertBase64UrlToBlob(path);
      const files = utils.blobToFile(
        blobNumber,
        `images${Math.floor(Math.random() * 1000000)}`
      );
      if (files.size > 1000000) {
        compress(dataURL, options);
      } else {
        callback(dataURL);
      }
    }
  };
}

export default compress;

参考博客:blog.csdn.net/shids_/arti…