uniapp微信小程序电子签名

先上效果图,不满意可以直接关闭这页签


新建成单独的组件,然后具体功能引入,具体功能点击签名按钮,把当前功能页面用样式隐藏掉,v-show和v-if也行,然后再把这个组件显示出来。


**【签名-撤销】**原理是之前绘画时把全部轨迹路径都记录下来,然后点击撤销时,清空画布,路径数组去掉最后一次绘画动作,然后再把剩余路径又全部画上去,这么干后会路径会出现锯齿,我也莫得办法了,将就着用了。


**【签名-完成】**点击完成会判断路径数组是否有值,如果没有则说明没有签名,有则把画布保存成图片,然后再把这个图片以指定尺寸画入另一个画布,避免保存下来的图片分辨率过大,导致文件太大。画上另一个画布之后,这个画布再保存成图片,然后图片再转成base64格式返回给主页面,要是不想转base64可以把具体代码去掉。生成的图片是垂直,应该以逆时针旋转90度保存的,奈何前端实力不过关,怎么都处理不好这个逆时针旋转90度,只能在上传到后端后,用后端旋转了(丢人).........如果有人能处理好这个,麻烦评论留下代码


主页面引入组件并注册,然后用v-show控制是否显示,主页面样式自己调整好,让电子签名可以覆盖整个页面。


[具体组件代码]

javascript 复制代码
<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>

继续加班了.....

码字不易,于你有利,勿忘点赞

相关推荐
郭wes代码30 分钟前
Cmd命令大全(万字详细版)
python·算法·小程序
web150850966413 小时前
在uniapp Vue3版本中如何解决webH5网页浏览器跨域的问题
前端·uni-app
.生产的驴5 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven
汤姆yu11 小时前
基于微信小程序的乡村旅游系统
微信小程序·旅游·乡村旅游
计算机徐师兄11 小时前
基于TP5框架的家具购物小程序的设计与实现【附源码、文档】
小程序·php·家具购物小程序·家具购物微信小程序·家具购物
曲辒净12 小时前
微信小程序实现二维码海报保存分享功能
微信小程序·小程序
朽木成才13 小时前
小程序快速实现大模型聊天机器人
小程序·机器人
peachSoda713 小时前
随手记:小程序使用uni.createVideoContext视频无法触发播放
小程序
何极光13 小时前
uniapp小程序样式穿透
前端·小程序·uni-app
小墨&晓末14 小时前
【PythonGui实战】自动摇号小程序
python·算法·小程序·系统安全