H5版的电子签名

这是我开发项目中的某一个页面(测试环境,并且无接口调用),只是体验一下H5的签名demo:http://ibt.ishdr.com/h5/signature/

参考插件

https://github.com/szimek/signature_pad

代码部分

pug的风格,看不懂的人可以使用转换工具:

/*
 * @Author: lijuan.sun
 * @Date: 2019-11-11 15:28:38
 * @Last Modified by: lijuan.sun
 * @Last Modified time: 2020-06-17 13:20:16
 */
<template lang="pug">
.signature
  .signature-content
    dl
      dt 请注意:
      dd(
        v-for="(item, index) in attention"
      ) {{ item }}
    .sign_bg.sign_bg-content(
      :class="{'screen-full': isFullScreen}"
    )
      //- 画布
      canvas(
        id="canvas"
      )
      //- 清除操作
      span.icon.icon-clear(
        v-if="isFullScreen",
        @click="clear()"
      )
      //- 标题
      span.title-orientation(
        v-if="isFullScreen"
      ) 请签名(请把手机横屏)
      //- 完成
      span.icon.icon-finish(
        v-if="isFullScreen",
        @click.stopPropagation="operate()"
      )
    //- 展示页面
    .sign_bg.sign_bg-show(
      @click="operate()"
    )
      //- 展示图片
      img(
        v-if="signUrl",
        id="signImg",
        width="auto",
        height="100%",
        :src="signUrl",
        alt="点击进入全屏",
        @click.stop="operate()"
      )
      //- 进入全屏签名
      span.tips-text(
        v-if="!signUrl"
        @click.stop="operate()"
      ) 点击进入全屏签名
  van-button.van-button--main.van-button--cancle(
    type="primary",
    size="large",
    @click="clear()"
  ) 撤销签名
  van-button.van-button--main(
    type="primary",
    size="large",
    @click="submit()"
  ) 提交
</template>

<script>
import _ from 'lodash'
export default {
  name: 'signature',
  head () {
    return {
      title: 'XXXX',
      script: [{
        src: '//cdn.jsdelivr.net/npm/signature_pad@2.3.2/dist/signature_pad.min.js'
      }]
    }
  },
  components: {
  },
  data () {
    return {
      attention: [
        '1.保证字迹清晰',
        '2.完整签写字迹姓名',
        '3.不准签写他人姓名'
      ],
      // 安卓
      isAndroid: false,
      // IOS
      isIos: false,
      // 协议是否勾选
      agreementChecked: true,
      // 签名图片名称和路径
      signInfo: {
        url: null,
        name: null
      },
      // 签名url
      signUrl: '',
      // 是否时全屏
      isFullScreen: false,
      // token信息
      token: this.$route.query.token,
      // 签名对象
      signaturePad: '',
      // 屏幕宽度
      canvasWidth: window.innerWidth,
      // 屏幕高度
      canvasHeight: window.innerHeight,
      // canvas对象
      canvas: ''
    }
  },
  mounted () {
    this.$nextTick(() => {
      // 检测客户端类型
      this.getClientType()
    })
  },
  methods: {
    /**
     * 获取图片宽度
     */
    getImageWidth () {
      // 获取图片宽度,为设备宽度减去边距50
      return window.innerWidth - 50
    },
    /**
     * 设置客户端类型
     */
    getClientType () {
      const u = window.navigator.userAgent
      // 安卓
      this.isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1 // android终端
      // ios
      this.isIos = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)
    },
    /**
     * 获取图片大小
     */
    getSize (url) {
      return new Promise((resolve) => {
        const img = document.createElement('img')
        img.onload = () => {
          resolve({
            width: img.width,
            height: img.height
          })
        }
        img.src = url
      })
    },
    /**
     * 全屏和退出全屏操作
     */
    async operate () {
      const type = this.isFullScreen ? 'out' : 'enter'
      switch (type) {
        case 'enter':
          // 进入全屏
          this.isFullScreen = true
          // 滚动条至页面顶部
          window.document.body.scrollTop = 0
          // 非空,则不重新加载画布
          if (!this.isEmptyPad()) {
            // 旋转画布-还原
            this.roatePic(this.canvasWidth, this.canvasHeight, 90)
            return
          }
          // 初始化画布
          if (window.SignaturePad) {
            const canvas = document.getElementById('canvas')
            this.canvas = canvas
            canvas.width = window.innerWidth
            canvas.height = window.innerHeight
            this.signaturePad = new window.SignaturePad(canvas, {
              penColor: 'rgb(0, 0, 0)' // 笔的颜色
            })
          }
          break
        case 'out':
          // 非空
          if (!this.isEmptyPad()) {
            // 等待图片加载完毕
            this.signUrl = this.isEmptyPad()
              ? '' : this.signaturePad.toDataURL()
            // 同步生成图片
            await this.getSize(this.signUrl)
            // 旋转画布-横屏
            this.roatePic(this.canvasHeight, this.canvasWidth, -90)
          }
          // 保存当前图片信息,放置页面路径上,用于展示
          this.signUrl = this.isEmptyPad()
            ? '' : this.signaturePad.toDataURL()
          // 退出全屏
          this.isFullScreen = false
          break
      }
    },
    /**
     * 旋转图片
     */
    roatePic (width, height, degree) {
      const d = (degree * Math.PI) / 180
      const rotationCanvas = this.canvas
      const surfaceContext = this.canvas.getContext('2d')
      const image = document.getElementById('signImg')
      rotationCanvas.width = width
      rotationCanvas.height = height
      // 清除一个画布上的矩形
      surfaceContext.clearRect(0, 0, width, height)
      // 保存状态
      surfaceContext.save()
      // 设置矩形为横屏的中心的位置
      surfaceContext.translate(width * 0.5, height * 0.5)
      // 旋转角度;进度全屏后是90度,具体配置值需要(degree * Math.PI) / 180计算
      surfaceContext.rotate(d)
      // 向画布上绘制图像
      surfaceContext.drawImage(image, -height / 2, -width / 2)
      // 返回之前保存过的路径状态和属性
      surfaceContext.restore()
    },
    /**
     * 上传图片
     */
    dataURLtoBlob (dataUrl) {
      const arr = _.split(dataUrl, ',')
      const mime = arr[0].match(/:(.*?);/)[1]
      const bstr = window.atob(arr[1])
      let n = bstr.length
      const u8arr = new Uint8Array(n)
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n)
      }
      return new Blob([u8arr], { type: mime })
    },
    /**
     * 上传图片
     */
    async uploadPic (dataUrl) {
      // TODO 因为上传图片时间较长,此处需要加入loading页面
      // 生成二进制图像数据
      const blobdata = this.dataURLtoBlob(dataUrl)
      const imageInfo = new window.FormData()
      // 拼接form表单的参数
      imageInfo.append('elecSign', blobdata, 'filename')
      // 执行图像上传
      const signPicInfo = await this.$api.upload('elecSign', imageInfo)
      // 生成签名需要的参数
      this.signInfo = _.assign({}, {
        url: _.get(signPicInfo, 'url'),
        name: _.get(signPicInfo, 'name')
      })
    },
    /**
     * 保存签名
     */
    async saveSign () {
      const data = await this.$api.put('myapi/user/updateUser/elecSign', this.signInfo)
      if (data) {
        this.$toast({
          position: 'bottom',
          message: '签约成功'
        })
      }
      // 拉起客户端的方法,通知是否退出此界面
      this.toClientData(!!data)
    },
    /**
     * 清除签名
     */
    clear () {
      if (!this.isFullScreen) {
        if (this.isEmptyPad()) {
          this.$toast({
            position: 'bottom',
            message: '请签名'
          })
          return
        }
      }
      // 清空图片URL
      this.signUrl = ''
      // 清空canvas的数据
      this.signaturePad.clear()
    },
    /**
     * 签名是否为空
     */
    isEmptyPad () {
      if (this.signaturePad) {
        return this.signaturePad.isEmpty()
      }
      return true
    },
    /**
     * 提交签名
     */
    async submit () {
      // 未勾选协议提示
      if (!this.agreementChecked) {
        this.$toast({
          position: 'bottom',
          message: '请勾选协议'
        })
        return
      }
      // 获取图像信息,base64 png格式
      // const { isEmpty, data } = this.$refs.signaturePad.saveSignature()
      // 未签名提示
      if (this.isEmptyPad()) {
        this.$toast({
          position: 'bottom',
          message: '请签名'
        })
        return
      }
      const data = this.signaturePad ? this.signaturePad.toDataURL() : ''
      if (data) {
        // 上传图片
        await this.uploadPic(data)
        // 保存签名
        await this.saveSign()
      }
    },
    /**
     * 通知客户端签名成功
     */
    toClientData (status) {
      // 获取浏览器agent
      const userAgent = window.navigator.userAgent
      if (!_.includes(userAgent, 'dongrui')) {
        return
      }
      if (this.isAndroid) {
        window.dongruijsBridge.toClientSignFlag(status)
        return
      }
      window.webkit.messageHandlers.toClientSignFlag.postMessage(status)
    },
    /**
     * 跳转合同地址
     */
    jumpTo (ev) {
      this.$router.push({
        path: '/contract',
        query: {
          token: this.token
        }
      })
      return false
    }
  }
}
</script>

<style lang="stylus">
  @import "~assets/css/variable"
  .signature
    padding 8px 10px 30px
    .agreement-text
      color $green
      margin-top 10px
      margin-bottom 40px
    &-content
      margin-bottom 25px
      padding 25px 15px 10px
      background $white
      border-radius $radius-5
      dl
        margin-bottom 25px
      dt
        color $yellow
        margin-bottom 12px
      dd
        line-height 22px
    .sign_bg
      height 185px
      background url('~assets/img/sign_bg.png')
      background-size 100%
      margin-bottom 10px
      position relative
      &-content
        margin-top 0px
        opacity 0
        #canvas
          width 100%
          height 100%
          position relative
          left 0
      &-show
        margin-top -200px
        flexCenter()
        z-index 2
        #canvas
          width 100%
          height 100%
        .tips-text
          color $green
          width 100%
          text-align center
          // margin-top -105px
        // > img
        //   transform rotate(-90deg)
    .screen-full
      position fixed
      width 100%
      height 100%
      left 0px
      top 0px
      z-index 99
      opacity 1
      .title-orientation
        transform rotate(90deg)
        position absolute
        right -60px
        top 50%
        margin-top -12px
        font-size $num-size
        color $green
        opacity 0.8
      .icon
        display flex
        position absolute
        width 30px
        height 30px
        transform rotate(90deg)
        &-clear
          top 10px
          right 10px
        &-finish
          bottom 10px
          right 10px
    .van-checkbox
      .van-icon
        position relative
        width 14px
        height 14px
        border 1PX solid $disabled-bg
        &-success:before
          position absolute
          left 1px
          top -3px
          font-size 10px
          color $white
      &__icon--checked .van-icon
        color $yellow-font-color
        background none
        border-color $yellow-font-color
      &__label
        font-size $text-size
        line-height 16px
        color #4E4E4E
        // margin-left 0px
    .van-button--cancle
      margin-bottom 30px
</style>

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

monkey01127

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值