SignaturePad.vue 6.99 KB
<template>
	<view class="signature-container">
		<view class="signature-header">
			<text class="signature-title">会员签字</text>
			<view class="signature-actions">
				<button @click="clearSignature" class="btn-clear">清除</button>
				<button @click="confirmSignature" class="btn-confirm">确认</button>
			</view>
		</view>
		<!-- 	 -->
		<view class="signature-pad" id="signaturePad">
			<canvas 
				canvas-id="signature-canvas"
				style="width: 100%; height: 100%;"
				@touchstart="startDrawing"
				@touchmove="draw"
				@touchend="stopDrawing"
				class="signature-canvas"
			></canvas>
		</view>
		
		<view class="signature-tips">
			<text class="tips-text">请在上方区域进行签字</text>
		</view>
	</view>
</template>

<script>
export default {
	name: 'SignaturePad',
	props: {
		width: {
			type: Number,
			default: 300
		},
		height: {
			type: Number,
			default: 150
		},
		lineWidth: {
			type: Number,
			default: 2
		},
		strokeColor: {
			type: String,
			default: '#000000'
		}
	},
	data() {
		return {
			canvasWidth: 300,
			canvasHeight: 150,
			isDrawing: false,
			lastX: 0,
			lastY: 0,
			ctx: null,
			hasSignature: false,
			// 存储所有绘制的路径点
			paths: []
		}
	},
	mounted() {
		this.initCanvas();
	},
	methods: {
		// 初始化画布
		initCanvas() {
			// this.canvasWidth = this.width;
			// this.canvasHeight = this.height;
			// 获取signature-pad的宽高
			const query = uni.createSelectorQuery().in(this);
			// 选择需要查询的元素(支持类名、ID等选择器)
			query.select('#signaturePad')
				.boundingClientRect(data => {
				if (data) {
					this.canvasWidth = data.width;
					this.canvasHeight = data.height;
				} else {
					console.log('未找到元素');
				}
				})
				.exec(); // 执行查询
			
			// console.error(this.canvasWidth, this.canvasHeight);
			this.$nextTick(() => {
				this.ctx = uni.createCanvasContext('signature-canvas', this);
				this.drawBackground();
			});
		},
		
		// 绘制背景
		drawBackground() {
			if (!this.ctx) return;
			
			// 清除画布
			this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
			
			// 设置背景色
			this.ctx.fillStyle = '#ffffff';
			this.ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
			
			// 绘制边框
			this.ctx.strokeStyle = '#e0e0e0';
			this.ctx.lineWidth = 2;
			this.ctx.strokeRect(0, 0, this.canvasWidth, this.canvasHeight);
			
			// 绘制提示文字
			this.ctx.fillStyle = '#999';
			this.ctx.font = '16px Arial';
			// this.ctx.fillText('请在此区域签字', 20, 30);
			
			// 重新绘制所有已保存的路径
			this.redrawPaths();
			
			// 显示
			this.ctx.draw();
		},
		
		// 重新绘制所有路径
		redrawPaths() {
			if (!this.ctx || this.paths.length === 0) return;
			
			this.ctx.strokeStyle = this.strokeColor;
			this.ctx.lineWidth = this.lineWidth;
			this.ctx.lineCap = 'round';
			this.ctx.lineJoin = 'round';
			
			this.paths.forEach(path => {
				if (path.length < 2) return;
				
				this.ctx.beginPath();
				this.ctx.moveTo(path[0].x, path[0].y);
				
				for (let i = 1; i < path.length; i++) {
					this.ctx.lineTo(path[i].x, path[i].y);
				}
				
				this.ctx.stroke();
			});
		},
		
		// 开始绘制
		startDrawing(e) {
			this.isDrawing = true;
			this.hasSignature = true;
			
			const touch = e.touches[0];
			const x = touch.x;
			const y = touch.y;
			
			// 开始新的路径
			this.paths.push([{ x, y }]);
			
			this.lastX = x;
			this.lastY = y;
		},
		
		// 绘制
		draw(e) {
			if (!this.isDrawing) return;
			
			const touch = e.touches[0];
			const x = touch.x;
			const y = touch.y;
			
			// 添加点到当前路径
			this.paths[this.paths.length - 1].push({ x, y });
			
			// 重新绘制整个画布
			this.drawBackground();
			
			this.lastX = x;
			this.lastY = y;
		},
		
		// 停止绘制
		stopDrawing() {
			this.isDrawing = false;
		},
		
		// 清除签字
		clearSignature() {
			this.paths = [];
			this.hasSignature = false;
			this.drawBackground();
			this.$emit('clear');
		},
		
		// 确认签字
		confirmSignature() {
			if (!this.hasSignature) {
				uni.showToast({
					title: '请先进行签字',
					icon: 'none'
				});
				return;
			}
			
			this.getSignatureData().then(dataUrl => {
				this.$emit('confirm', {
					dataUrl: dataUrl,
					hasSignature: this.hasSignature
				});
			}).catch(error => {
				console.error('获取签字数据失败:', error);
				uni.showToast({
					title: '获取签字数据失败',
					icon: 'none'
				});
			});
		},
		
		// 获取签字数据
		getSignatureData() {
			return new Promise((resolve, reject) => {
				uni.canvasToTempFilePath({
					canvasId: 'signature-canvas',
					success: (res) => {
						// 在H5环境下,直接使用tempFilePath
						// #ifdef H5
						const dataUrl = res.tempFilePath;
						resolve(dataUrl);
						// #endif
						
						// 在微信小程序环境下,需要读取文件
						// #ifdef MP-WEIXIN
						uni.getFileSystemManager().readFile({
							filePath: res.tempFilePath,
							encoding: 'base64',
							success: (fileRes) => {
								const dataUrl = `data:image/png;base64,${fileRes.data}`;
								resolve(dataUrl);
							},
							fail: reject
						});
						// #endif
					},
					fail: reject
				}, this);
			});
		},
		
		// 设置签字数据(用于编辑模式)
		setSignatureData(dataUrl) {
			if (!dataUrl || !this.ctx) return;
			
			// 如果有签字数据,设置hasSignature为true
			this.hasSignature = true;
			
			// 这里可以添加从dataUrl恢复签字的逻辑
			// 暂时简化处理
		}
	}
}
</script>

<style scoped>
.signature-container {
	background: #fff;
	border-radius: 8px;
	overflow: hidden;
	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
	border: 3rpx solid #c8e6c9;
	height: 100%;
	display: flex;
	flex-direction: column;
	justify-content: space-between;
}

.signature-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	padding: 12px 16px;
	background: linear-gradient(135deg, #e8f5e9 0%, #b2dfdb 100%);
	border-bottom: 1px solid #c8e6c9;
}

.signature-title {
	font-size: 16px;
	font-weight: 500;
	color: #2e7d32;
}

.signature-actions {
	display: flex;
	gap: 8px;
}

.btn-clear, .btn-confirm {
	padding: 6px 12px;
	border: none;
	border-radius: 4px;
	font-size: 14px;
	cursor: pointer;
	transition: all 0.2s;
}

.btn-clear {
	background: #f9fff9;
	color: #6a9c6a;
	border: 1px solid #c8e6c9;
}

.btn-clear:hover {
	background: #e8f5e9;
}

.btn-confirm {
	background: linear-gradient(120deg, #43e97b 0%, #38f9d7 100%);
	color: #fff;
}

.btn-confirm:hover {
	background: linear-gradient(120deg, #38d96b 0%, #2ee5c7 100%);
}

.signature-pad {
	/* padding: 16px; */
	display: flex;
	justify-content: center;
	flex: 1;
	box-sizing: border-box;
	width: 100%;
	height: 100%;
}

.signature-canvas {
	/* border: 1px solid #c8e6c9; */
	border-radius: 4px;
	background: #fff;
	box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
}

.signature-tips {
	padding: 8px 16px;
	background: #f9fff9;
	text-align: center;
	border-top: 1px solid #c8e6c9;
}

.tips-text {
	font-size: 12px;
	color: #6a9c6a;
}
</style>