vue-esign电子签名,base64转二进制file存储

应用场景

交费的电子签名,可确认和重签(清空内容),手机端默认横屏,并对内容做旋转,如图
在这里插入图片描述

知识点

1.vue-esign签名插件

2.手机的横屏设置

transform: translateX(-50%) translateY(-50%) rotateZ(90deg);

3.生成图片的图片的旋转

rotateBase64Img(src, edg, fileName, fileType, callback)

4.将 base64的图片 Uint8Array转换为 file 对象

/**
 * 将 base64 转换为 file 对象
 *    dataURL:base64 格式
 *    fileName:文件名
 *    fileType:文件格式
 */
export function dataURLtoFile(dataURL, fileName, fileType) {
  const dataArr = dataURL.split(',')
  const byteString = atob(dataArr[1])
  const options = {
    type: 'image/jpeg',
    endings: 'native'
  }
  const u8Arr = new Uint8Array(byteString.length)
  for (let i = 0; i < byteString.length; i++) {
    u8Arr[i] = byteString.charCodeAt(i)
  }
  return new File([u8Arr], fileName + '.jpg', options)
}

5.封装组件 HandSign

使用方法

安装vue-esign与main.js中引入

npm install vue-esign --save
// main.js中引入签名
import vueEsign from 'vue-esign'
Vue.use(vueEsign)

实例代码

父组件

<template>
  <SignCanvas ref="SignCanvasRef" :signName="name" @sureHandler="sureSignHandler" />
</template>

<script>
import SignCanvas from '@/components/SignCanvas/index'
import {addFile} from '@/api/tools/localStorage'
export default {
  name: 'HandSign',
  components: {SignCanvas},
  data() {
    return {
      id:'',
      name:'',
      signFile:null
    }
  },
  created() {
    this.id=this.$route.query.id
    // this.name=decodeURIComponent(this.$route.query.name)
    this.name=this.$route.query.name
    console.log(this.name)
  },
  methods:{
    sureSignHandler(data) {
      console.log(data)
      // addFile({id:this.id,name:this.name,file:data}).then(res=>{
      //   this.$message({
      //     showClose: true,
      //     message: '签名已经保存成功',
      //     type: 'success'
      //   });
      // })
      this.submitHandler(data)
    },
    submitHandler(data) {
      //利用FormData传参
      const MultipartFile = new FormData()
      //file 是后端接受图片的字段
      MultipartFile.append('file', data)
      // 然后调用你的接口 把MultipartFile 传给后端
      // {id:this.id,name:this.name,file:MultipartFile}
      addFile(MultipartFile,{id:this.id}).then(res=>{
        this.$message({
          showClose: true,
          message: '签名已经保存成功',
          type: 'success'
        });
      })
    }
  }
}
</script>

<style  lang='scss' scoped>
</style>

签名组件SignCanvas 路径src/components/SignCanvas/index.vue

<!-- 签名组件 -->
<template>
  <div class="signContainer">
    <div class="btns">
      <el-button type="default" round @click="resetHandler" class="van-button reset">重签</el-button>
      <el-button type="primary" round @click="sureHandler" class="van-button" style="margin-left: 0px;">确认</el-button>
    </div>
    <vue-esign
      ref="VueEsignRef"
      class="vue-esign"
      :width="width"
      :height="height"
      :lineWidth="lineWidth"
      :lineColor="lineColor"
      :bgColor="bgColor"
      :isCrop="isCrop"
      :isClearBgColor="isClearBgColor"
      :format="format"
      :quality="quality"
    />
    <div :style="{ '--width': height + 'px' }" class="tipText"><span v-if="signName">{{ ` ${signName} ` }}</span
      >在此区域内签名
    </div>
  </div>
</template>

<script>
import { rotateBase64Img } from '@/utils/esignFun'

export default {
  name: 'SignCanvas',
  components: {},

  props: {
    // 画布宽度,即导出图片的宽度
    width: {
      type: Number,
      default: () => {
        const dom = document.querySelector('#app')
        const width = dom && dom.offsetWidth
        return width ? width - 60 : 300 // 减去按钮区域的宽度
      }
    },
    // 画布高度,即导出图片的高度
    height: {
      type: Number,
      default: () => {
        const dom = document.querySelector('#app')
        return (dom && dom.offsetHeight) || 800
      }
    },
    // 画笔粗细
    lineWidth: {
      type: Number,
      default: 6
    },
    // 画笔颜色
    lineColor: {
      type: String,
      default: '#000'
    },
    // 画布背景色,为空时画布背景透明,支持多种格式 '#ccc','#E5A1A1','rgb(229, 161, 161)','rgba(0,0,0,.6)','red'
    bgColor: {
      type: String,
      default: ''
    },
    // 是否裁剪,在画布设定尺寸基础上裁掉四周空白部分
    isCrop: {
      type: Boolean,
      default: false
    },
    // 清空画布时(reset)是否同时清空设置的背景色(bgColor)
    isClearBgColor: {
      type: Boolean,
      default: true
    },
    // 生成图片格式 image/jpeg(jpg格式下生成的图片透明背景会变黑色请慎用或指定背景色)、 image/webp
    format: {
      type: String,
      default: 'image/png'
    },
    // 生成图片质量;在指定图片格式为 image/jpeg 或 image/webp的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。
    quality: {
      type: Number,
      default: 1
    },
    // 未签名时提示信息
    noSignTipText: {
      type: String,
      default: '请确保已签名!'
    },
    // 需要签名的姓名
    signName: {
      type: String,
      default: ''
    }
  },

  methods: {
    resetHandler() {
      this.$refs.VueEsignRef.reset() // 清空画布
    },
    sureHandler() {
      // 可选配置参数 ,在未设置format或quality属性时可在生成图片时配置 例如: {format:'image/jpeg', quality: 0.5}
      // this.$refs.esign.generate({format:'image/jpeg', quality: 0.5})
      this.$refs.VueEsignRef.generate()
        .then(res => {
          /**
           * res:base64图片
           */
          rotateBase64Img(res, 270, `${this.signName ? this.signName + '-签名.jpg' : 'sign.jpg'}`, '', data => {
            this.$emit('sureHandler', data)
          })
        })
        .catch(err => {
          console.log('err----', err)
          this.$alert(this.noSignTipText, '签名', {
            confirmButtonText: '确定',
          });
          // this.$message(this.noSignTipText)
        })
    }
  }
}
</script>
<style>
/* .el-message-box{
  width: 300px !important;
}
.el-message-box__wrapper{
  transform: rotate(90deg) translateY(15px);
  left:auto !important;
  right:auto !important;
} */
</style>
<style lang='scss' scoped>
.signContainer {
  width: 100%;
  height: 100vh;
  display: flex;
  background-color: #fff;

  .btns {
    width: 55px;
    background-color: #f8f8f8;
    display: flex;
    flex-direction: column;
    justify-content: center;
    .reset {
      margin-bottom: 70px;
    }
  }
  .vue-esign {
    z-index: 2;
  }
  .tipText {
    position: absolute;
    top: 50%;
    width: var(--width);
    left: calc(50% + 55px);
    transform: translateX(-50%) translateY(-50%) rotateZ(90deg);
    text-align: center;
    color: #ddd;
    letter-spacing: 2px;
  }
}
.van-button {
  width: 85px !important;
  height: 35px;
  transform: rotate(90deg) translateY(15px);
  text-align: center;
  .van-button__text {
    letter-spacing: 5px;
  }
}
::v-deep.el-message-box{
  width: 300px !important;
}
::v-deep.el-message-box__wrapper{
  transform: rotate(90deg) translateY(15px);
  left:auto !important;
  right:auto !important;
}
</style>


图片处理的js import { rotateBase64Img } from ‘@/utils/esignFun’

/**
 * 图片旋转
 */
export function rotateBase64Img(src, edg, fileName, fileType, callback) {
  var canvas = document.createElement('canvas')
  var ctx = canvas.getContext('2d')

  var imgW // 图片宽度
  var imgH // 图片高度
  var size // canvas初始大小

  if (edg % 90 !== 0) {
    console.error('旋转角度必须是90的倍数!')
    return '旋转角度必须是90的倍数!'
  }
  edg < 0 && (edg = (edg % 360) + 360)
  const quadrant = (edg / 90) % 4 // 旋转象限
  const cutCoor = { sx: 0, sy: 0, ex: 0, ey: 0 } // 裁剪坐标

  var image = new Image()
  image.crossOrigin = 'Anonymous'
  image.src = src

  image.onload = () => {
    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)
    ctx.drawImage(image, 0, 0)

    var 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
    }

    ctx.putImageData(imgData, 0, 0)
    // callback(dataURLtoFileBlob(src))
    callback(dataURLtoFile(canvas.toDataURL(), fileName, fileType))
    // callback(canvas.toDataURL())
  }
}
/**
 * 将 base64 转换为 file 对象
 *    dataURL:base64 格式
 *    fileName:文件名
 *    fileType:文件格式
 */
export function dataURLtoFile(dataURL, fileName, fileType) {
  const dataArr = dataURL.split(',')
  const byteString = atob(dataArr[1])
  const options = {
    type: 'image/jpeg',
    endings: 'native'
  }
  const u8Arr = new Uint8Array(byteString.length)
  for (let i = 0; i < byteString.length; i++) {
    u8Arr[i] = byteString.charCodeAt(i)
  }
  return new File([u8Arr], fileName + '.jpg', options)
}

// 将base64转成blob流
export function dataURLtoFileBlob (urlData) {
  const type = 'image/png'
  let bytes = null
  if (urlData.split(',').length > 1) {
    bytes = window.atob(urlData.split(',')[1])
  } else {
    bytes = window.atob(urlData)
  }
  let ab = new ArrayBuffer(bytes.length)
  let ia = new Uint8Array(ab)
  for (let i = 0; i < bytes.length; i++) {
    ia[i] = bytes.charCodeAt(i)
  }
  return new Blob([ab], { type })
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值