先上效果图,不满意可以直接关闭这页签
新建成单独的组件,然后具体功能引入,具体功能点击签名按钮,把当前功能页面用样式隐藏掉,v-show和v-if也行,然后再把这个组件显示出来。
【签名-撤销】原理是之前绘画时把全部轨迹路径都记录下来,然后点击撤销时,清空画布,路径数组去掉最后一次绘画动作,然后再把剩余路径又全部画上去,这么干后会路径会出现锯齿,我也莫得办法了,将就着用了。
【签名-完成】点击完成会判断路径数组是否有值,如果没有则说明没有签名,有则把画布保存成图片,然后再把这个图片以指定尺寸画入另一个画布,避免保存下来的图片分辨率过大,导致文件太大。画上另一个画布之后,这个画布再保存成图片,然后图片再转成base64格式返回给主页面,要是不想转base64可以把具体代码去掉。生成的图片是垂直,应该以逆时针旋转90度保存的,奈何前端实力不过关,怎么都处理不好这个逆时针旋转90度,只能在上传到后端后,用后端旋转了(丢人).........如果有人能处理好这个,麻烦评论留下代码
主页面引入组件并注册,然后用v-show控制是否显示,主页面样式自己调整好,让电子签名可以覆盖整个页面。
[具体组件代码]
<template>
<view class="panel" :style="{top: `${top}px`}">
<canvas canvas-id="signCanvas" class="sign-canvas" @touchstart="handleTouchStart"
@touchmove="handleTouchMove" @touchend="handleTouchEnd"></canvas>
<!-- 另一个画布,用来缩小签名图片尺寸的,加个样式让他飞到外太空去 -->
<canvas canvas-id="signCanvasReduce" class="sign-canvas-reduce"></canvas>
<view class="panel-bottom">
<view class="panel-bottom-btn btn-gray" @click="onCancel">取消</view>
<view class="panel-bottom-btn btn-gray" @click="onUndo">撤销</view>
<view class="panel-bottom-btn btn-gray" @click="onClearRect(true)">重写</view>
<view class="panel-bottom-btn" @click="onSaveSign">完成</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
// 距离顶部高度
top: void (0),
isDrawing: false,
startX: 0,
startY: 0,
strokes: [],
canvasWidth: 0,
canvasHeight: 0
}
},
mounted() {
// 获取手机状态栏和导航栏高度,和手机屏幕可用宽高度
const _this = this
uni.getSystemInfo({
success: function(res) {
_this.canvasWidth = res.windowWidth
_this.canvasHeight = res.windowHeight
const custom = uni.getMenuButtonBoundingClientRect()
// 导航栏胶囊高度 + (胶囊距离顶部高度 - 状态栏高度) * 2 + 状态栏高度 + 20内边距
_this.top = custom.height + (custom.top - res.statusBarHeight) * 2 + res.statusBarHeight + 4
}
})
// 创建画布
const ctx = uni.createCanvasContext('signCanvas', this)
ctx.setStrokeStyle('#000')
ctx.setLineWidth(4)
ctx.setLineCap('round')
ctx.setLineJoin('round')
ctx.draw()
this.canvasContext = ctx
},
methods: {
handleTouchStart(e) {
// 阻止默认滚动行为
e.preventDefault()
const touch = e.touches[0]
this.isDrawing = true
this.startX = touch.x
this.startY = touch.y
this.strokes.push({
type: 'start',
x: touch.x,
y: touch.y
})
},
handleTouchMove(e) {
e.preventDefault() // 阻止默认滚动行为
if (!this.isDrawing) {
return
}
const touch = e.touches[0]
this.canvasContext.moveTo(this.startX, this.startY)
this.canvasContext.lineTo(touch.x, touch.y)
this.canvasContext.stroke()
this.canvasContext.draw(true)
this.startX = touch.x
this.startY = touch.y
this.strokes.push({
type: 'move',
x: touch.x,
y: touch.y
})
},
handleTouchEnd(e) {
e.preventDefault() // 阻止默认滚动行为
this.isDrawing = false
},
// 撤销
onUndo () {
// 先清空当前画布
this.onClearRect(false)
if (this.strokes.length) {
// 去掉最后一次绘画的路径
while (this.strokes.pop().type !== 'start'){}
// 剩余路径全部绘制到画布上
for (let i = 0; i < this.strokes.length; i++) {
const item = this.strokes[i]
if(item.type === 'start') {
// 绘制起始点
this.canvasContext.beginPath()
this.canvasContext.moveTo(item.x, item.y)
} else if(item.type === 'move') {
// 绘制线条
this.canvasContext.lineTo(item.x, item.y)
this.canvasContext.stroke()
}
}
this.canvasContext.draw(true)
}
},
// 清空
onClearRect (clearLine) {
this.canvasContext.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
this.canvasContext.draw(true)
clearLine && (this.strokes = [])
},
// 取消
onCancel () {
this.onClearRect(true)
this.$emit('cancel')
},
// 保存签名 signFlag 是否已签名
onSaveSign() {
if (!this.strokes.length) {
// 未签名
this.$emit('ok', { signFlag: false })
return
}
// 签名保存为图片
uni.canvasToTempFilePath({
canvasId: 'signCanvas',
quality: 0.1,
success: (res) => {
const tempPath = res.tempFilePath
// 然后写入另一个画布
const signCanvas = uni.createCanvasContext('signCanvasReduce', this)
signCanvas.translate(0, 0) // 修改原点坐标(这里是已左上角为坐标原点)
signCanvas.drawImage(tempPath, 0, 0, 80, 160)
signCanvas.draw(false, () => {
setTimeout(() => {
// 另一个画布再保存为图片,目的就是缩小图片的尺寸
uni.canvasToTempFilePath({
canvasId: 'signCanvasReduce',
quality: 0.2,
success: (res) => {
// 清空画布
this.onClearRect(true)
// 转成base64
this.imagePathToBase64(res.tempFilePath).then(base64 => {
this.$emit('ok', { base64, signFlag: true })
})
},
fail: () => {
// toast('生成签名图片失败')
}
}, this)
}, 200)
})
},
fail: (res) => {
// toast('生成签名失败')
}
}, this)
},
// 根据上传后的图片转成Base64格式
imagePathToBase64(url) {
if (!url) {
return url
}
return new Promise((resolve, reject) => {
uni.getFileSystemManager().readFile({
filePath: url,
encoding: 'base64',
success: fileRes => {
const base64 = 'data:image/png;base64,' + fileRes.data
resolve(base64)
},
fail: err => {
// toast('生成签名失败')
resolve()
}
})
})
}
}
}
</script>
<style lang="scss" scoped>
.panel {
width: 100%;
position: absolute;
left: 0;
bottom: 0;
right: 0;
top: 0;
overflow-y: hidden;
background-color: #FFF;
}
.sign-canvas {
width: 100%;
height: 85%;
}
.sign-canvas-reduce {
width: 80px;
height: 160px;
position: absolute;
top: -10000rpx;
}
.panel-bottom {
height: 15%;
display: flex;
justify-content: center;
padding-top: 50rpx;
}
.panel-bottom-btn {
transform: rotate(90deg);
height: 40rpx;
padding: 14rpx 36rpx;
font-size: 30rpx;
border-radius: 20rpx;
color: #FFF;
background: linear-gradient(90deg, rgba(250, 197, 22, 1), rgba(255, 141, 26, 1));
}
.btn-gray {
background: #d4d4d4;
}
</style>
<style>
page {
overflow-y: hidden;
}
</style>
继续加班了.....
码字不易,于你有利,勿忘点赞