先上效果图,不满意可以直接关闭这页签
新建成单独的组件,然后具体功能引入,具体功能点击签名按钮,把当前功能页面用样式隐藏掉,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 = thisuni.getSystemInfo({success: function(res) {_this.canvasWidth = res.windowWidth_this.canvasHeight = res.windowHeightconst 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 = truethis.startX = touch.xthis.startY = touch.ythis.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.xthis.startY = touch.ythis.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)// 转成base64this.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.dataresolve(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>
继续加班了.....
码字不易,于你有利,勿忘点赞