Untitled
<script setup lang="ts"> import { ref, onMounted, onBeforeUnmount, nextTick, defineEmits } from 'vue'; import { BrowserMultiFormatReader, IScannerControls } from '@zxing/browser'; import { BarcodeFormat } from '@zxing/library'; import { onBeforeRouteLeave } from 'vue-router'; const emit = defineEmits(['code-scanned']); const videoElement = ref<HTMLVideoElement | null>(null); let scanner: BrowserMultiFormatReader | null = null; let currentStream: MediaStream | null = null; let scannerControls: IScannerControls | null = null; const scanning = ref(false); function createScanner() { const hints = new Map(); hints.set('possibleFormats', [ BarcodeFormat.QR_CODE, BarcodeFormat.EAN_13, BarcodeFormat.EAN_8, BarcodeFormat.CODE_128, BarcodeFormat.CODE_39, BarcodeFormat.UPC_A, BarcodeFormat.UPC_E, BarcodeFormat.ISBN ]); return new BrowserMultiFormatReader(hints); } async function startScan() { console.log('startScan iniziato'); if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { alert("Il tuo browser non supporta l'accesso alla fotocamera."); return; } try { // Prima fermiamo eventuali scansioni precedenti await stopScan(); // Settiamo scanning a true per far renderizzare il video scanning.value = true; // Aspettiamo il prossimo ciclo di rendering await nextTick(); // Aspettiamo un attimo per essere sicuri che il DOM sia aggiornato await new Promise(resolve => setTimeout(resolve, 100)); if (!videoElement.value) { throw new Error('Elemento video non trovato dopo il rendering'); } console.log('Video elemento trovato nel DOM'); // Crea un nuovo scanner scanner = createScanner(); console.log('Scanner creato'); // Richiedi accesso alla fotocamera const stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment', width: { ideal: 1280 }, height: { ideal: 720 } } }); console.log('Stream ottenuto'); // Verifica nuovamente che il video esista if (!videoElement.value) { throw new Error('Elemento video perso durante l\'inizializzazione'); } const video = videoElement.value; // Configura gli event listeners const onMetadataLoaded = () => { console.log('Video metadata caricati'); }; const onPlay = () => { console.log('Video in riproduzione'); }; const onError = () => { console.error('Errore video:', video.error); }; video.addEventListener('loadedmetadata', onMetadataLoaded); video.addEventListener('play', onPlay); video.addEventListener('error', onError); // Cleanup function per gli event listeners const cleanup = () => { video.removeEventListener('loadedmetadata', onMetadataLoaded); video.removeEventListener('play', onPlay); video.removeEventListener('error', onError); }; // Imposta lo stream nel video video.srcObject = stream; currentStream = stream; try { await video.play(); console.log('Video play avviato'); if (scanner) { const deviceId = stream.getVideoTracks()[0].getSettings().deviceId; console.log('Avvio scansione con deviceId:', deviceId); try { scannerControls = await scanner.decodeFromVideoDevice( deviceId, video, (result, error) => { if (result) { const text = result.getText(); const format = result.getBarcodeFormat(); console.log("Codice trovato:", text, "Formato:", format); // Emetti l'evento con il codice scansionato emit('code-scanned', text); cleanup(); stopScan(); } if (error && error.message !== "No MultiFormat Readers were able to detect the code.") { console.warn("Errore durante la scansione:", error); } } ); console.log('Scanner avviato correttamente'); } catch (scanError) { console.error('Errore durante l\'avvio dello scanner:', scanError); throw scanError; } } } catch (playError) { cleanup(); console.error('Errore durante il play del video:', playError); throw playError; } } catch (error) { console.error("Errore completo:", error); alert("Non è stato possibile accendere la fotocamera. " + error.message); scanning.value = false; } } async function stopScan() { console.log('Arresto scansione...'); try { scanning.value = false; await nextTick(); if (scannerControls) { console.log('Arresto controlli scanner'); scannerControls.stop(); scannerControls = null; } if (currentStream) { currentStream.getTracks().forEach(track => { console.log('Arresto track:', track.label); track.stop(); }); currentStream = null; } if (videoElement.value) { videoElement.value.srcObject = null; console.log('Video source rimosso'); } if (scanner) { scanner = null; console.log('Scanner resettato'); } console.log('Scansione arrestata completamente'); } catch (error) { console.error("Errore durante lo stop dello scanner:", error); } } // Cleanup quando il componente viene distrutto onBeforeUnmount(() => { stopScan(); }); // Cleanup quando si cambia route onBeforeRouteLeave((to, from, next) => { stopScan(); next(); }); </script> <template> <div class="scanner-container"> <div v-if="scanning" class="video-container"> <video ref="videoElement" playsinline autoplay muted class="scanner-video" ></video> <div class="scanner-overlay"> <div class="scanner-frame"></div> </div> <button @click="stopScan" class="btn btn-danger mt-3">Ferma Scansione</button> </div> <div v-else class="start-scan"> <button @click="startScan" class="btn barcode-btn"> <i class="bi bi-upc-scan fs-1"></i> </button> </div> </div> </template> <style scoped> .scanner-container { position: relative; width: 100%; max-width: 600px; margin: 0 auto; padding: 20px; } .video-container { position: relative; width: 100%; background: #000; border-radius: 10px; overflow: hidden; } .scanner-video { display: block; width: 100%; height: auto; aspect-ratio: 4/3; object-fit: cover; } .scanner-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; } .scanner-frame { width: 70%; height: 70%; border: 2px solid #fff; border-radius: 10px; box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.3); } .start-scan { display: flex; justify-content: center; padding: 20px; } .barcode-btn { padding: 15px 30px; background: #007bff; color: white; border: none; border-radius: 8px; transition: all 0.3s ease; } .barcode-btn:hover { background: #0056b3; transform: scale(1.05); } button { z-index: 10; position: relative; } </style>
Leave a Comment