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>

Semi-supervised classification with graph convolutional networks (GCNs) is a method for predicting labels for nodes in a graph. GCNs are a type of neural network that operates on graph-structured data, where each node in the graph represents an entity (such as a person, a product, or a webpage) and edges represent relationships between entities. The semi-supervised classification problem arises when we have a graph where only a small subset of nodes have labels, and we want to predict the labels of the remaining nodes. GCNs can be used to solve this problem by learning to propagate information through the graph, using the labeled nodes as anchors. The key idea behind GCNs is to use a graph convolution operation to aggregate information from a node's neighbors, and then use this aggregated information to update the node's representation. This operation is then repeated over multiple layers, allowing the network to capture increasingly complex relationships between nodes. To train a GCN for semi-supervised classification, we use a combination of labeled and unlabeled nodes as input, and optimize a loss function that encourages the network to correctly predict the labels of the labeled nodes while also encouraging the network to produce smooth predictions across the graph. Overall, semi-supervised classification with GCNs is a powerful and flexible method for predicting labels on graph-structured data, and has been successfully applied to a wide range of applications including social network analysis, drug discovery, and recommendation systems.
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

monkey01127

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

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

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

打赏作者

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

抵扣说明:

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

余额充值