liveh5-nuxt/app/components/SignaturePad.vue

181 lines
3.9 KiB
Vue
Raw Normal View History

2025-01-21 08:27:26 +00:00
<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>