- aggiornamento di tante cose...
- generazione Volantini - pagina RIS
This commit is contained in:
@@ -1,25 +1,38 @@
|
||||
import {
|
||||
computed,
|
||||
provide, defineComponent, onBeforeMount, onBeforeUnmount, onMounted, ref, toRef, toRefs, watch, reactive
|
||||
} from 'vue'
|
||||
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 { 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 { shared_consts } from '@/common/shared_vuejs'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { toolsext } from '@store/Modules/toolsext'
|
||||
import { useQuasar } from 'quasar'
|
||||
|
||||
import { QrStream, QrCapture, QrDropzone } from 'vue3-qr-reader'
|
||||
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',
|
||||
emits: [''],
|
||||
|
||||
components: {
|
||||
QrStream,
|
||||
QrCapture,
|
||||
QrDropzone,
|
||||
QRCodeVue3,
|
||||
},
|
||||
|
||||
props: {
|
||||
link: {
|
||||
type: String,
|
||||
@@ -41,51 +54,361 @@ export default defineComponent({
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
required: false,
|
||||
default: 250,
|
||||
},
|
||||
primaryColor: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '#26249a',
|
||||
},
|
||||
},
|
||||
components: {
|
||||
QrStream,
|
||||
QrCapture,
|
||||
QrDropzone
|
||||
},
|
||||
setup(props, { attrs, slots, emit }) {
|
||||
const { t } = useI18n()
|
||||
const $q = useQuasar()
|
||||
const globalStore = useGlobalStore()
|
||||
const userStore = useUserStore()
|
||||
const $router = useRouter()
|
||||
|
||||
emits: ['decoded', 'error'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
const $q = useQuasar();
|
||||
const globalStore = useGlobalStore();
|
||||
const userStore = useUserStore();
|
||||
const $router = useRouter();
|
||||
|
||||
const qrDisplayRef = ref<HTMLElement | null>(null);
|
||||
|
||||
// State
|
||||
const state = reactive({
|
||||
data: null
|
||||
})
|
||||
data: null as string | null,
|
||||
isDownloading: false,
|
||||
uploadedFile: null as File | null,
|
||||
});
|
||||
|
||||
function onDecode(data: any) {
|
||||
if (data)
|
||||
state.data = data
|
||||
// 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const text = ref('');
|
||||
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<HTMLCanvasElement | null> {
|
||||
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 <img> 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)
|
||||
$router.push(path);
|
||||
}
|
||||
|
||||
onMounted(mounted)
|
||||
function debugCanvas() {
|
||||
console.log('=== DEBUG QR ===');
|
||||
console.log('1. qrDisplayRef.value:', qrDisplayRef.value);
|
||||
|
||||
function mounted() {
|
||||
// ...
|
||||
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,
|
||||
text,
|
||||
userStore,
|
||||
|
||||
// State
|
||||
...toRefs(state),
|
||||
|
||||
// Computed
|
||||
qrSize,
|
||||
logoImage,
|
||||
dotsOptions,
|
||||
canShare,
|
||||
downloadFilename,
|
||||
primaryColor: props.primaryColor,
|
||||
|
||||
// Methods
|
||||
onDecode,
|
||||
handleFileUpload,
|
||||
qrDisplayRef,
|
||||
downloadQR,
|
||||
shareQR,
|
||||
copyToClipboard,
|
||||
isValidUrl,
|
||||
truncateUrl,
|
||||
naviga,
|
||||
globalStore,
|
||||
}
|
||||
};
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user