2024-11-18 23:28:33 +02:00
|
|
|
<template>
|
2024-11-19 15:50:15 +02:00
|
|
|
<div class="flex flex-col min-h-screen">
|
|
|
|
|
<main class="flex-grow flex items-center justify-center py-6 px-4 md:px-6">
|
|
|
|
|
<section class="max-w-lg w-full flex flex-col gap-1">
|
|
|
|
|
<h2 class="text-xl font-semibold mb-4">Ваш QR‑код готов</h2>
|
|
|
|
|
<p class="text-gray-600 mb-4">Сохраните его и ознакомьтесь с инструкциями по размещению.</p>
|
2024-11-18 23:28:33 +02:00
|
|
|
|
2024-11-19 15:50:15 +02:00
|
|
|
<!-- Рендер QR‑кода -->
|
|
|
|
|
<div v-if="!loading && svgUrl" class="border p-4 bg-white rounded-lg mb-6">
|
|
|
|
|
<img :src="svgUrl" alt="QR‑код" />
|
|
|
|
|
</div>
|
2024-11-18 23:28:33 +02:00
|
|
|
|
2024-11-19 15:50:15 +02:00
|
|
|
<!-- Сообщение об ошибке -->
|
|
|
|
|
<p v-if="error" class="text-red-500 mt-4">{{ error }}</p>
|
|
|
|
|
<p v-if="loading" class="text-gray-500 mt-4">Загрузка...</p>
|
2024-11-18 23:28:33 +02:00
|
|
|
|
2024-11-19 15:50:15 +02:00
|
|
|
<!-- Кнопки для скачивания -->
|
|
|
|
|
<div v-if="!loading && !error" class="flex flex-row gap-4 justify-center">
|
|
|
|
|
<Button
|
|
|
|
|
v-if="svgUrl"
|
|
|
|
|
label="Скачать SVG"
|
|
|
|
|
icon="pi pi-download"
|
|
|
|
|
class="p-button-sm"
|
|
|
|
|
:href="svgUrl"
|
|
|
|
|
:download="`${sanitizedFileName}.svg`"
|
|
|
|
|
as="a"
|
|
|
|
|
/>
|
|
|
|
|
<Button
|
|
|
|
|
label="Скачать PNG"
|
|
|
|
|
icon="pi pi-download"
|
|
|
|
|
class="p-button-sm p-button-outlined"
|
|
|
|
|
@click="downloadPng"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<footer class="bg-gray-100 text-center py-4 border-t text-sm text-gray-700">
|
|
|
|
|
<p>
|
|
|
|
|
Нужен еще один?
|
|
|
|
|
<a href="/create" class="underline">Создайте QR‑код</a>.
|
|
|
|
|
</p>
|
|
|
|
|
<p class="mt-1">
|
|
|
|
|
Сделайте НаСвязи удобнее. Будем рады вашей <a href="/donate" class="underline"
|
|
|
|
|
>поддержке</a
|
|
|
|
|
>.
|
|
|
|
|
</p>
|
|
|
|
|
</footer>
|
2024-11-18 23:28:33 +02:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
|
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
|
|
|
|
import QRCodeStyling from 'qr-code-styling'
|
|
|
|
|
import { getQrCodeDocument } from '@/api/qrCode'
|
|
|
|
|
import { optimize } from 'svgo'
|
2024-11-19 00:13:49 +02:00
|
|
|
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
2024-11-18 23:28:33 +02:00
|
|
|
|
2024-11-19 00:13:49 +02:00
|
|
|
const { qr_code_uri } = defineProps<{ qr_code_uri: QRCodeDocument['uri'] }>()
|
2024-11-18 23:28:33 +02:00
|
|
|
|
|
|
|
|
// Локальные состояния
|
|
|
|
|
const loading = ref(true)
|
|
|
|
|
const error = ref<string | null>(null)
|
|
|
|
|
const qrCodeData = ref<null | { name: string; url: string }>(null)
|
|
|
|
|
const svgUrl = ref<string | null>(null)
|
|
|
|
|
|
|
|
|
|
// Получение имени файла
|
|
|
|
|
const sanitizedFileName = computed(() => {
|
2024-11-19 00:51:11 +02:00
|
|
|
const sanitized = qrCodeData.value?.name
|
|
|
|
|
.replace(/[<>:"/\\|?*\x00-\x1F]/g, '') // Убираем запрещённые символы
|
|
|
|
|
.replace(/\s+/g, '_') // Заменяем пробелы на подчёркивания
|
|
|
|
|
.trim()
|
2024-11-18 23:28:33 +02:00
|
|
|
return sanitized ? `${sanitized}_QR-Code_HereConnect` : 'QR-Code_HereConnect'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Инициализация QRCodeStyling
|
|
|
|
|
const qrCode = new QRCodeStyling({
|
|
|
|
|
width: 256 * 4,
|
|
|
|
|
height: 256 * 4,
|
|
|
|
|
data: '',
|
|
|
|
|
type: 'svg',
|
|
|
|
|
backgroundOptions: {
|
|
|
|
|
color: '#ffffff',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Генерация и оптимизация SVG
|
|
|
|
|
async function generateOptimizedSvg(url: string) {
|
|
|
|
|
try {
|
|
|
|
|
qrCode.update({ data: url })
|
|
|
|
|
const blob = await qrCode.getRawData('svg')
|
|
|
|
|
if (blob && blob instanceof Blob) {
|
|
|
|
|
const text = await blob.text()
|
|
|
|
|
|
|
|
|
|
const optimizedSvg = optimize(text, {
|
|
|
|
|
plugins: [
|
|
|
|
|
{ name: 'removeDoctype' },
|
|
|
|
|
{ name: 'removeComments' },
|
|
|
|
|
{ name: 'removeDimensions' },
|
|
|
|
|
{ name: 'convertShapeToPath' },
|
|
|
|
|
{ name: 'convertPathData' },
|
|
|
|
|
{ name: 'mergePaths' },
|
|
|
|
|
],
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (optimizedSvg.data) {
|
|
|
|
|
svgUrl.value = URL.createObjectURL(new Blob([optimizedSvg.data], { type: 'image/svg+xml' }))
|
|
|
|
|
} else {
|
|
|
|
|
throw new Error('Ошибка оптимизации SVG')
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2024-11-19 00:13:49 +02:00
|
|
|
throw new Error('Ошибка генерации QR‑кода')
|
2024-11-18 23:28:33 +02:00
|
|
|
}
|
|
|
|
|
} catch (err) {
|
|
|
|
|
error.value = (err as Error).message || 'Неизвестная ошибка'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Скачивание PNG
|
|
|
|
|
async function downloadPng() {
|
|
|
|
|
try {
|
|
|
|
|
await qrCode.download({
|
|
|
|
|
name: sanitizedFileName.value,
|
|
|
|
|
extension: 'png',
|
|
|
|
|
})
|
|
|
|
|
} catch (err) {
|
2024-11-19 00:13:49 +02:00
|
|
|
error.value = (err as Error).message || 'Ошибка при скачивании QR‑кода'
|
2024-11-18 23:28:33 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Монтирование
|
|
|
|
|
onMounted(async () => {
|
|
|
|
|
try {
|
2024-11-19 00:13:49 +02:00
|
|
|
// Загружаем данные QR‑кода
|
2024-11-18 23:28:33 +02:00
|
|
|
const doc = await getQrCodeDocument(qr_code_uri)
|
|
|
|
|
qrCodeData.value = { name: doc.name, url: doc.url }
|
|
|
|
|
await generateOptimizedSvg(doc.url)
|
|
|
|
|
} catch (err) {
|
2024-11-19 00:13:49 +02:00
|
|
|
error.value = (err as Error).message || 'Ошибка загрузки данных QR‑кода'
|
2024-11-18 23:28:33 +02:00
|
|
|
} finally {
|
|
|
|
|
loading.value = false
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Очистка ресурсов при размонтировании
|
|
|
|
|
onUnmounted(() => {
|
|
|
|
|
if (svgUrl.value) {
|
|
|
|
|
URL.revokeObjectURL(svgUrl.value)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
</script>
|