<template> <div class="signature-pad-container"> <canvas ref="canvasRef" class="signature-pad" :style="{ width: '100%', height: '100%', backgroundColor: '#fff', border: '1px solid #e5e5e5', borderRadius: '4px' }" @touchstart="handleStart" @touchmove="handleMove" @touchend="handleEnd" @mousedown="handleStart" @mousemove="handleMove" @mouseup="handleEnd" @mouseleave="handleEnd" ></canvas> <div class="signature-controls"> <van-button type="default" size="small" @click="clearCanvas" >清除</van-button> <van-button type="primary" size="small" @click="handleConfirm" >确认</van-button> </div> </div> </template> <script setup> import { ref, onMounted, onBeforeUnmount } from 'vue' const props = defineProps({ modelValue: { type: String, default: '' } }) const emit = defineEmits(['update:modelValue', 'change']) const canvasRef = ref(null) const ctx = ref(null) const isDrawing = ref(false) const lastX = ref(0) const lastY = ref(0) const LINE_WIDTH = 2 // 固定画笔粗细 // 初始化画布 const initCanvas = () => { const canvas = canvasRef.value const dpr = window.devicePixelRatio || 1 const rect = canvas.getBoundingClientRect() // 设置画布的实际大小 canvas.width = rect.width * dpr canvas.height = rect.height * dpr ctx.value = canvas.getContext('2d') // 缩放画布以匹配设备像素比 ctx.value.scale(dpr, dpr) ctx.value.lineCap = 'round' ctx.value.lineJoin = 'round' ctx.value.strokeStyle = '#000' ctx.value.lineWidth = LINE_WIDTH } // 开始绘制 const handleStart = (e) => { e.preventDefault() // 防止页面滚动 isDrawing.value = true const point = getPoint(e) lastX.value = point.x lastY.value = point.y } // 绘制中 const handleMove = (e) => { if (!isDrawing.value) return e.preventDefault() // 防止页面滚动 const point = getPoint(e) ctx.value.beginPath() ctx.value.moveTo(lastX.value, lastY.value) ctx.value.lineTo(point.x, point.y) ctx.value.stroke() lastX.value = point.x lastY.value = point.y } // 结束绘制 const handleEnd = () => { isDrawing.value = false } // 获取触点坐标 const getPoint = (e) => { const canvas = canvasRef.value const rect = canvas.getBoundingClientRect() const dpr = window.devicePixelRatio || 1 const event = e.touches ? e.touches[0] : e // 计算实际的触点位置 const x = (event.clientX - rect.left) const y = (event.clientY - rect.top) return { x: x, y: y } } // 清除画布 const clearCanvas = () => { const canvas = canvasRef.value ctx.value.clearRect(0, 0, canvas.width, canvas.height) emit('update:modelValue', '') emit('change', '') } // 确认并生成图片 const handleConfirm = () => { const canvas = canvasRef.value const imageData = canvas.toDataURL('image/png') emit('update:modelValue', imageData) emit('change', imageData) } // 监听屏幕旋转 const handleResize = () => { const canvas = canvasRef.value const imageData = canvas.toDataURL('image/png') initCanvas() // 恢复之前的内容 const img = new Image() img.onload = () => { ctx.value.drawImage(img, 0, 0, canvas.width, canvas.height) } img.src = imageData } onMounted(() => { initCanvas() window.addEventListener('resize', handleResize) }) onBeforeUnmount(() => { window.removeEventListener('resize', handleResize) }) </script> <style scoped> .signature-pad-container { width: 100%; height: 100%; display: flex; flex-direction: column; gap: 10px; } .signature-pad { flex: 1; touch-action: none; } .signature-controls { display: flex; align-items: center; justify-content: flex-end; gap: 16px; padding: 8px; } </style>