uniapp 实现电子签名功能

1,001 阅读3分钟

uniapp 实现电子签名功能

以下代码仅用于参考,具体功能需要根据实际情况做出改变

<template>
	<view
		class="main-box"
		:class="{
			'main-box-vertical': props.direction == 'vertical',
			'main-box-horizontal': props.direction == 'horizontal',
		}"
		:style="{ height: props.signHeight }"
	>
		<view
			class="operation-box"
			:class="{
				'operation-box-vertical': props.direction == 'vertical',
				'operation-box-horizontal': props.direction == 'horizontal',
			}"
		>
			<view
				class="c-btn cancel"
				@click="cancel"
				>取消</view
			>
			<view
				class="c-btn rewrite"
				@click="rewrite"
				>重写</view
			>
			<view
				class="c-btn confirm"
				@click="confirm"
				>确定</view
			>
		</view>
		<canvas
			:class="{
				'canvas-vertical': props.direction == 'vertical',
				'canvas-horizontal': props.direction == 'horizontal',
			}"
			id="mycanvas"
			canvas-id="mycanvas"
			@touchstart="touchstart"
			@touchmove="touchmove"
			@touchend="touchend"
		></canvas>
	</view>
</template>

<script setup>
	import { ref, onMounted, computed, watchEffect, nextTick } from 'vue';
	import { onLoad, onShow } from '@dcloudio/uni-app';

	const { proxy } = getCurrentInstance();

	const props = defineProps({
		signHeight: {
			type: String,
			default: '100%',
		},
		pictureBoardBackgroundColor: {
			type: String,
			default: '',
		},
		direction: {
			type: String,
			default: 'vertical', // vertical 竖向(默认),horizontal 横向
		},
	});
	const emit = defineEmits(['cancel', 'complete']);

	onMounted(async () => {
		try {
			await new Promise((resolve) => setTimeout(resolve, 500));
			ctx.value = uni.createCanvasContext('mycanvas'); // 创建绘图对象
			await getBoardInfo();
			initCanvas();
		} catch (error) {
			console.log(error);
		}
	});

	const boardWidth = ref(0); // 画板宽度
	const boardHeight = ref(0); // 画板高度
	async function getBoardInfo() {
		await nextTick();
		return new Promise((resolve, reject) => {
			uni
				.createSelectorQuery()
				.in(proxy)
				.select('#mycanvas')
				.boundingClientRect((data) => {
					if (!data) {
						reject('获取画板信息失败');
						return;
					}
					boardWidth.value = data.width;
					boardHeight.value = data.height;
					resolve();
				})
				.exec();
		});
	}

	function initCanvas() {
		// 设置画笔样式
		ctx.value.lineWidth = 4;
		ctx.value.lineCap = 'round';
		ctx.value.lineJoin = 'round';
		ctx.value.setStrokeStyle('#000');

		// 设置画板背景色
		if (props.pictureBoardBackgroundColor) {
			ctx.value.setFillStyle(props.pictureBoardBackgroundColor);
			ctx.value.fillRect(0, 0, boardWidth.value, boardHeight.value);
			ctx.value.draw();
		}
	}

	// 取消
	function cancel() {
		emit('cancel');
	}

	// 签名开始
	let ctx = ref(''); //绘图图像
	let points = ref([]); //路径点集合

	//触摸开始,获取到起点
	function touchstart(e) {
		let startX = e.changedTouches[0].x;
		let startY = e.changedTouches[0].y;
		let startPoint = { X: startX, Y: startY };

		points.value.push(startPoint);

		//每次触摸开始,开启新的路径
		ctx.value.beginPath();
	}

	//触摸移动,获取到路径点
	function touchmove(e) {
		let moveX = e.changedTouches[0].x;
		let moveY = e.changedTouches[0].y;
		let movePoint = { X: moveX, Y: moveY };
		points.value.push(movePoint); //存点
		let len = points.value.length;
		if (len >= 2) {
			draw(); //绘制路径
		}
	}

	// 触摸结束,将未绘制的点清空防止对后续路径产生干扰
	function touchend() {
		points.value = [];
	}

	let isDraw = ref(false); // 是否有绘画

	/* ***********************************************
		#   绘制笔迹
		#	1.为保证笔迹实时显示,必须在移动的同时绘制笔迹
		#	2.为保证笔迹连续,每次从路径集合中区两个点作为起点(moveTo)和终点(lineTo)
		#	3.将上一次的终点作为下一次绘制的起点(即清除第一个点)
	************************************************ */
	function draw() {
		isDraw.value = true;
		let point1 = points.value[0];
		let point2 = points.value[1];
		points.value.shift();
		ctx.value.moveTo(point1.X, point1.Y);
		ctx.value.lineTo(point2.X, point2.Y);
		ctx.value.stroke();
		ctx.value.draw(true);
	}

	//清空画布
	function rewrite() {
		isDraw.value = false;
		ctx.value.clearRect(0, 0, boardWidth.value, boardHeight.value);
		ctx.value.draw();
		initCanvas();
	}

	//完成绘画并保存到本地
	function confirm() {
		if (isDraw.value == false) {
			uni.showToast({
				title: '请先签名',
				icon: 'none',
			});
			return;
		}
		uni.canvasToTempFilePath({
			canvasId: 'mycanvas',
			success: async (res) => {
				let path = res.tempFilePath;
				// console.log(path);
				emit('complete', path);
			},
		});
	}
</script>

<style lang="scss" scoped>
	.main-box {
		background: #f5f5f5;
		padding: 15rpx;
	}
	.main-box-vertical {
		display: flex;
		flex-direction: column;
	}
	.main-box-horizontal {
		display: flex;
	}

	.operation-box {
		.c-btn {
			white-space: nowrap;
			background: #ffffff;
			border-radius: 28rpx;
			display: flex;
			justify-content: center;
			align-items: center;
			font-size: 26rpx;
			font-weight: 500;
			color: #666666;
		}
		.cancel {
			background: #ffffff;
			color: #666666;
		}
		.rewrite {
			background: #959595;
			color: #ffffff;
		}
		.confirm {
			background: #2d99a1;
			color: #ffffff;
		}
	}
	.operation-box-vertical {
		display: flex;
		justify-content: center;
		align-items: center;
		gap: 20rpx;
		margin-bottom: 20rpx;
		.c-btn {
			padding: 10rpx 50rpx;
		}
	}
	.operation-box-horizontal {
		display: flex;
		justify-content: center;
		align-items: center;
		flex-direction: column;
		gap: 20rpx;
		margin-right: 20rpx;
		.c-btn {
			/* 保持竖向排列模式 */
			writing-mode: vertical-rl;
			/* sideways 会让中文像英文一样顺时针旋转90度显示 */
			text-orientation: sideways;
			/* 边距和竖向模式的边距反过来 */
			padding: 50rpx 10rpx;
			/* 微调字间距,让旋转后的文字看起来不那么挤 */
			letter-spacing: 4rpx;
		}
	}

	.canvas-vertical {
		flex: 1;
		width: 100%;
	}
	.canvas-horizontal {
		flex: 1;
		height: 100%;
	}
</style>

参考文章:juejin.cn/post/700063…