这是我开发项目中的某一个页面(测试环境,并且无接口调用),只是体验一下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>