/** * SignaturePad Component * * Full-screen signature capture for shipping notes. * Features: * - Canvas-based signature drawing * - Customer name input * - Clear/retry functionality * - Base64 PNG export */ import { shippingNoteApi } from '/mobile/modules/lager/shippingnote/ShippingNoteModule.js'; export default { name: 'SignaturePad', emits: ['close', 'signed', 'toast'], props: { shippingNoteId: [Number, String], shippingNote: Object }, setup(props, { emit }) { const { ref, onMounted, onUnmounted, nextTick } = Vue; // Refs const canvasRef = ref(null); const signatureName = ref(''); const loading = ref(false); const hasSignature = ref(false); // Canvas context let ctx = null; let isDrawing = false; let lastX = 0; let lastY = 0; // Initialize canvas onMounted(async () => { await nextTick(); initCanvas(); window.addEventListener('resize', handleResize); }); onUnmounted(() => { window.removeEventListener('resize', handleResize); }); const initCanvas = () => { const canvas = canvasRef.value; if (!canvas) return; // Set canvas size to container const container = canvas.parentElement; canvas.width = container.clientWidth; canvas.height = container.clientHeight; ctx = canvas.getContext('2d'); ctx.strokeStyle = '#000000'; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; // Fill with white background ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); }; const handleResize = () => { // Save current signature const canvas = canvasRef.value; if (!canvas) return; const imageData = canvas.toDataURL(); // Resize canvas initCanvas(); // Restore signature if (hasSignature.value) { const img = new Image(); img.onload = () => { ctx.drawImage(img, 0, 0); }; img.src = imageData; } }; // Get position from touch/mouse event const getPosition = (e) => { const canvas = canvasRef.value; const rect = canvas.getBoundingClientRect(); if (e.touches && e.touches.length > 0) { return { x: e.touches[0].clientX - rect.left, y: e.touches[0].clientY - rect.top }; } return { x: e.clientX - rect.left, y: e.clientY - rect.top }; }; // Start drawing const startDrawing = (e) => { e.preventDefault(); isDrawing = true; const pos = getPosition(e); lastX = pos.x; lastY = pos.y; }; // Draw const draw = (e) => { if (!isDrawing) return; e.preventDefault(); const pos = getPosition(e); ctx.beginPath(); ctx.moveTo(lastX, lastY); ctx.lineTo(pos.x, pos.y); ctx.stroke(); lastX = pos.x; lastY = pos.y; hasSignature.value = true; }; // Stop drawing const stopDrawing = () => { isDrawing = false; }; // Clear canvas const clearCanvas = () => { const canvas = canvasRef.value; if (!canvas || !ctx) return; ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, canvas.width, canvas.height); hasSignature.value = false; navigator.vibrate?.([30]); }; // Submit signature const submitSignature = async () => { if (!hasSignature.value) { emit('toast', 'Bitte unterschreiben', 'error'); return; } if (!signatureName.value.trim()) { emit('toast', 'Bitte Namen eingeben', 'error'); return; } loading.value = true; try { const canvas = canvasRef.value; const signatureData = canvas.toDataURL('image/png'); const data = await shippingNoteApi.post(`sign?id=${props.shippingNoteId}`, { signature: signatureData, signatureName: signatureName.value.trim() }); if (data.success) { emit('signed', data); } else { emit('toast', data.error || 'Fehler beim Speichern', 'error'); } } catch (e) { console.error('Signature submit failed:', e); emit('toast', 'Netzwerkfehler', 'error'); } finally { loading.value = false; } }; // Close handler const handleClose = () => { emit('close'); }; return { canvasRef, signatureName, loading, hasSignature, startDrawing, draw, stopDrawing, clearCanvas, submitSignature, handleClose }; }, template: `

Unterschrift

Lieferschein Nr.
{{ shippingNote.number || shippingNote.id }}
{{ shippingNote.customerName }}
Hier unterschreiben

Mit Finger unterschreiben

` };