import { computed, defineComponent, onMounted, ref, toRefs, reactive, nextTick, } from 'vue'; import { tools } from '@tools'; import { costanti } from '@costanti'; import { useGlobalStore } from '@store/globalStore'; import { useUserStore } from '@store/UserStore'; import { useI18n } from 'vue-i18n'; import { toolsext } from '@store/Modules/toolsext'; import { useQuasar } from 'quasar'; import { useRouter } from 'vue-router'; // Import per lettura QR import { QrStream, QrCapture, QrDropzone } from 'vue3-qr-reader'; // Import per generazione QR import QRCodeVue3 from 'qrcode-vue3'; export default defineComponent({ name: 'CQRCode', components: { QrStream, QrCapture, QrDropzone, QRCodeVue3, }, props: { link: { type: String, required: false, default: '', }, textlink: { type: String, required: false, default: '', }, imglogo: { type: String, required: false, default: '', }, read: { type: Boolean, required: false, default: false, }, size: { type: Number, required: false, default: 250, }, primaryColor: { type: String, required: false, default: '#26249a', }, }, emits: ['decoded', 'error'], setup(props, { emit }) { const { t } = useI18n(); const $q = useQuasar(); const globalStore = useGlobalStore(); const userStore = useUserStore(); const $router = useRouter(); const qrDisplayRef = ref(null); // State const state = reactive({ data: null as string | null, isDownloading: false, uploadedFile: null as File | null, }); // Computed const qrSize = computed(() => { // Responsive size if ($q.screen.lt.sm) { return Math.min(props.size, window.innerWidth - 80); } return props.size; }); const logoImage = computed(() => { return props.imglogo || tools.getimglogo(); }); const dotsOptions = computed(() => ({ type: 'rounded' as const, color: props.primaryColor, gradient: { type: 'linear' as const, rotation: 0, colorStops: [ { offset: 0, color: props.primaryColor }, { offset: 1, color: lightenColor(props.primaryColor, 20) }, ], }, })); const canShare = computed(() => { return !!navigator.share; }); const downloadFilename = computed(() => { const username = userStore.my?.username || 'user'; const timestamp = Date.now(); return `qrcode-${username}-${timestamp}`; }); // Methods function onDecode(data: string) { if (data) { state.data = data; emit('decoded', data); // Vibrazione feedback su mobile if (navigator.vibrate) { navigator.vibrate(100); } $q.notify({ type: 'positive', message: 'QR Code rilevato!', position: 'top', timeout: 2000, }); } } function handleFileUpload(file: File | null) { if (!file) return; // Qui potresti usare una libreria per decodificare QR da immagine // Per esempio: jsQR $q.notify({ type: 'info', message: 'Analisi immagine in corso...', position: 'top', }); } async function findCanvas(maxAttempts = 10): Promise { for (let i = 0; i < maxAttempts; i++) { // Cerca in vari modi let canvas: HTMLCanvasElement | null = null; // Metodo 1: cerca nel ref if (qrDisplayRef.value) { canvas = qrDisplayRef.value.querySelector('canvas'); } // Metodo 2: cerca con classe specifica if (!canvas) { const qrCode = document.querySelector('.qr-code'); canvas = qrCode?.querySelector('canvas') || null; } // Metodo 3: cerca nel container .qr-display if (!canvas) { const container = document.querySelector('.qr-display'); canvas = container?.querySelector('canvas') || null; } if (canvas && canvas.width > 0 && canvas.height > 0) { return canvas; } // Aspetta prima del prossimo tentativo await new Promise((resolve) => setTimeout(resolve, 100)); } return null; } // Nuova funzione downloadQR async function downloadQR() { state.isDownloading = true; try { await nextTick(); await new Promise((resolve) => setTimeout(resolve, 200)); let dataUrl: string | null = null; // Metodo 1: Cerca l'immagine (QRCodeVue3 genera un con base64) const qrDisplay = qrDisplayRef.value || document.querySelector('.qr-display'); if (qrDisplay) { const img = qrDisplay.querySelector('img') as HTMLImageElement; if (img && img.src && img.src.startsWith('data:image')) { console.log('✅ Immagine base64 trovata!'); dataUrl = img.src; } } // Metodo 2: Se non trova img, cerca canvas (fallback) if (!dataUrl) { const canvas = document.querySelector( '.qr-display canvas' ) as HTMLCanvasElement; if (canvas && canvas.width > 0) { console.log('✅ Canvas trovato come fallback'); dataUrl = canvas.toDataURL('image/png'); } } if (!dataUrl) { debugCanvas(); throw new Error('QR Code non trovato. Riprova.'); } // Download const link = document.createElement('a'); link.href = dataUrl; link.download = `${downloadFilename.value}.png`; document.body.appendChild(link); link.click(); document.body.removeChild(link); $q.notify({ type: 'positive', message: 'QR Code scaricato!', icon: 'download_done', position: 'top', }); } catch (error: any) { console.error('Errore download QR:', error); $q.notify({ type: 'negative', message: error.message || 'Errore durante il download', position: 'top', }); emit('error', error); } finally { state.isDownloading = false; } } async function shareQR() { try { // Cerca l'immagine base64 const qrDisplay = qrDisplayRef.value || document.querySelector('.qr-display'); const img = qrDisplay?.querySelector('img') as HTMLImageElement; if (img && img.src && img.src.startsWith('data:image')) { // Converti base64 in blob const response = await fetch(img.src); const blob = await response.blob(); const file = new File([blob], `${downloadFilename.value}.png`, { type: 'image/png', }); if (navigator.canShare && navigator.canShare({ files: [file] })) { await navigator.share({ title: props.textlink || 'QR Code', files: [file], }); $q.notify({ type: 'positive', message: 'Condivisione avviata!', position: 'top', }); return; } } // Fallback: condividi solo il link await navigator.share({ title: props.textlink || 'QR Code', url: props.link, }); } catch (error: any) { if (error.name !== 'AbortError') { console.error('Errore condivisione:', error); } } } function copyToClipboard(text: string) { navigator.clipboard .writeText(text) .then(() => { $q.notify({ type: 'positive', message: 'Copiato negli appunti!', icon: 'content_copy', position: 'top', timeout: 1500, }); }) .catch((err) => { console.error('Errore copia:', err); }); } function isValidUrl(text: string): boolean { try { new URL(text); return true; } catch { return false; } } function truncateUrl(url: string, maxLength: number = 400): string { if (!url || url.length <= maxLength) return url; return url.substring(0, maxLength) + '...'; } function lightenColor(color: string, percent: number): string { const num = parseInt(color.replace('#', ''), 16); const amt = Math.round(2.55 * percent); const R = (num >> 16) + amt; const G = ((num >> 8) & 0x00ff) + amt; const B = (num & 0x0000ff) + amt; return ( '#' + ( 0x1000000 + (R < 255 ? (R < 1 ? 0 : R) : 255) * 0x10000 + (G < 255 ? (G < 1 ? 0 : G) : 255) * 0x100 + (B < 255 ? (B < 1 ? 0 : B) : 255) ) .toString(16) .slice(1) ); } function naviga(path: string) { $router.push(path); } function debugCanvas() { console.log('=== DEBUG QR ==='); console.log('1. qrDisplayRef.value:', qrDisplayRef.value); const qrDisplay = qrDisplayRef.value || document.querySelector('.qr-display'); if (qrDisplay) { // Cerca immagini const imgs = qrDisplay.querySelectorAll('img'); console.log('2. Immagini trovate:', imgs.length); imgs.forEach((img, i) => { const imgEl = img as HTMLImageElement; console.log( ` Img ${i}: src starts with data:image = ${imgEl.src?.startsWith('data:image')}` ); }); // Cerca canvas const canvases = qrDisplay.querySelectorAll('canvas'); console.log('3. Canvas trovati:', canvases.length); console.log( '4. innerHTML (primi 200 char):', qrDisplay.innerHTML.substring(0, 200) ); } } // Lifecycle onMounted(() => { // Inizializzazione se necessaria }); return { // Stores globalStore, userStore, // Utils t, tools, costanti, toolsext, // State ...toRefs(state), // Computed qrSize, logoImage, dotsOptions, canShare, downloadFilename, primaryColor: props.primaryColor, // Methods onDecode, handleFileUpload, qrDisplayRef, downloadQR, shareQR, copyToClipboard, isValidUrl, truncateUrl, naviga, }; }, });