uniapp微信小程序电子签名

发布于:2024-07-05 ⋅ 阅读:(17) ⋅ 点赞:(0)

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


新建成单独的组件,然后具体功能引入,具体功能点击签名按钮,把当前功能页面用样式隐藏掉,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>

继续加班了.....

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