This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<div class="max-w-3xl mx-auto py-6 px-4 md:px-6">
|
||||
<div v-if="isLoading" class="text-gray-500">Загрузка инструкции...</div>
|
||||
<div v-else-if="data" class="markdown">
|
||||
<VueMarkdownIt :source="data" />
|
||||
</div>
|
||||
<p v-else-if="isError" class="text-red-500">Ошибка загрузки инструкции.</p>
|
||||
<p v-else class="text-gray-500">Инструкция для этого типа размещения отсутствует.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { VueMarkdownIt } from '@f3ve/vue-markdown-it'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
|
||||
// Импортируем все инструкции из директории
|
||||
const importInstructions = import.meta.glob('/src/content/placementInstructions/*.md', {
|
||||
as: 'raw',
|
||||
}) as unknown as Record<string, () => Promise<string>>
|
||||
|
||||
const { placement } = defineProps<{ placement: QRCodeDocument['placement'] }>()
|
||||
|
||||
// Хук для загрузки инструкции
|
||||
async function fetchInstructions(): Promise<string | undefined> {
|
||||
return importInstructions[`/src/content/placementInstructions/${placement}.md`]?.()
|
||||
}
|
||||
|
||||
// Используем vue-query для управления запросом
|
||||
const { isLoading, isError, data } = useQuery({
|
||||
queryKey: ['placementInstructions', computed(() => placement)],
|
||||
queryFn: fetchInstructions,
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.markdown {
|
||||
font-family: 'Inter', sans-serif;
|
||||
line-height: 1.7;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(h1) {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #111827;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(h2) {
|
||||
font-size: 1.75rem;
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #1f2937;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(h3) {
|
||||
font-size: 1.5rem;
|
||||
margin-top: 1.25rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #374151;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(p) {
|
||||
margin-bottom: 1rem;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(ul) {
|
||||
list-style: disc;
|
||||
margin-left: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(ol) {
|
||||
list-style: decimal;
|
||||
margin-left: 1.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(li) {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(a) {
|
||||
color: #2563eb;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(blockquote) {
|
||||
border-left: 4px solid #d1d5db;
|
||||
padding-left: 1rem;
|
||||
color: #6b7280;
|
||||
margin: 1rem 0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(code) {
|
||||
background-color: #f3f4f6;
|
||||
border-radius: 4px;
|
||||
padding: 0.2rem 0.4rem;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(pre) {
|
||||
background-color: #f9fafb;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
overflow-x: auto;
|
||||
color: #111827;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(table) {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(th),
|
||||
.markdown ::v-deep(td) {
|
||||
border: 1px solid #d1d5db;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.markdown ::v-deep(th) {
|
||||
background-color: #f9fafb;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<section class="max-w-lg w-full text-center">
|
||||
<section class="max-w-prose w-full text-center">
|
||||
<!-- Контактная информация -->
|
||||
<div class="flex items-start bg-gray-100 border rounded-lg p-4">
|
||||
<!-- Аватарка Gravatar -->
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<section class="max-w-lg w-full">
|
||||
<section class="max-w-prose w-full">
|
||||
<p class="text-gray-600 mb-4 text-center">Выберите сообщение из списка или напишите своё:</p>
|
||||
|
||||
<!-- Предустановленные сообщения -->
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
### Инструкции по размещению на автомобиль:
|
||||
|
||||
**Где размещать:**
|
||||
- На боковом или заднем стекле автомобиля.
|
||||
|
||||
**Как разместить:**
|
||||
1. Найдите видимое место на стекле, которое будет хорошо видно прохожим, например, в углу заднего или бокового окна.
|
||||
2. Очистите поверхность стекла от пыли и грязи с помощью сухой или слегка влажной ткани.
|
||||
3. Если вы используете наклейку:
|
||||
- Убедитесь, что материал устойчив к влаге и выцветанию.
|
||||
- Аккуратно приклейте наклейку на стекло, разглаживая её от центра к краям, чтобы избежать появления пузырьков воздуха.
|
||||
4. Если вы используете пластину или рамку:
|
||||
- Проверьте, что крепление не повредит стекло.
|
||||
- Убедитесь, что QR-код надёжно зафиксирован и не шатается.
|
||||
5. Если QR-код нанесён с помощью трафарета или гравировки:
|
||||
- Проверьте, что код выполнен без дефектов и легко сканируется.
|
||||
- Очистите стекло от остатков краски или мусора.
|
||||
|
||||
**Важно:**
|
||||
- Убедитесь, что QR-код не блокирует обзор водителю.
|
||||
- Проверьте, что код можно легко сканировать снаружи автомобиля в любое время суток.
|
||||
- Если QR-код размещён внутри, убедитесь, что стекло чистое и не бликует.
|
||||
@@ -0,0 +1,14 @@
|
||||
### Инструкции по размещению на входную дверь дома или квартиры:
|
||||
**Где размещать:**
|
||||
- На входной двери или рядом с домофоном.
|
||||
- На двери или рядом с дверным звонком.
|
||||
|
||||
**Как разместить:**
|
||||
1. Найдите место рядом с дверным звонком или на самой двери, которое будет заметно для гостей.
|
||||
2. Очистите поверхность от пыли и грязи, чтобы QR-код держался крепче.
|
||||
3. Приклейте QR-код на дверь с помощью двустороннего скотча или водостойкого клея.
|
||||
4. Если вы не хотите приклеивать код, используйте рамку или подставку и разместите её на полке или возле двери.
|
||||
|
||||
**Важно:**
|
||||
- Проверьте, что QR-код не блокирует другие элементы двери.
|
||||
- Убедитесь, что его легко заметить и сканировать при любом освещении.
|
||||
@@ -0,0 +1,12 @@
|
||||
### Инструкции по размещению:
|
||||
**Где размещать:**
|
||||
- В зависимости от ситуации, на любом удобном и видимом месте.
|
||||
|
||||
**Как разместить:**
|
||||
1. Подберите место, где QR-код будет защищён от повреждений и виден окружающим.
|
||||
2. Используйте подходящие материалы для крепления: двусторонний скотч, рамку, подставку или брелок.
|
||||
3. Убедитесь, что QR-код хорошо заметен и его легко сканировать.
|
||||
|
||||
**Важно:**
|
||||
- Адаптируйте место размещения под конкретную задачу.
|
||||
- Проверьте, чтобы QR-код не был скрыт другими предметами.
|
||||
@@ -0,0 +1,13 @@
|
||||
### Инструкции по размещению на питомца:
|
||||
**Где размещать:**
|
||||
- На ошейнике питомца.
|
||||
|
||||
**Как разместить:**
|
||||
1. Выберите удобный ошейник с креплением для QR-кода. Лучше всего подойдут ошейники с кольцом или специальным держателем.
|
||||
2. Используйте прочный и водостойкий брелок или пластиковую подложку для QR-кода.
|
||||
3. Закрепите QR-код на ошейнике так, чтобы он не мешал вашему питомцу, но был легко доступен для сканирования.
|
||||
4. Проверьте, чтобы QR-код был хорошо зафиксирован и не потерялся во время прогулки.
|
||||
|
||||
**Важно:**
|
||||
- Используйте материалы, которые не вызовут аллергии или раздражения у питомца.
|
||||
- Проверьте, что QR-код можно легко сканировать в любом положении.
|
||||
@@ -0,0 +1,18 @@
|
||||
### Инструкции по размещению на стол в кафе или ресторане:
|
||||
**Где размещать:**
|
||||
- На столе, в меню или на подставке.
|
||||
|
||||
**Как разместить:**
|
||||
1. Если QR-код размещается на столе:
|
||||
- Используйте небольшой деревянный держатель или подставку.
|
||||
- Поставьте подставку в центр стола или сбоку, чтобы гости могли легко заметить код.
|
||||
2. Если QR-код размещается в меню:
|
||||
- Вставьте QR-код на видное место, например, на первую или последнюю страницу меню.
|
||||
- Используйте плотный ламинированный материал, чтобы код не повредился при использовании меню.
|
||||
3. Если QR-код размещается на подставке:
|
||||
- Поставьте подставку на уровне глаз сидящих гостей.
|
||||
- Проверьте, чтобы подставка была устойчива и не падала.
|
||||
|
||||
**Важно:**
|
||||
- Защищайте QR-код от влаги, например, от пролитых напитков.
|
||||
- Проверьте, что его можно легко сканировать, сидя за столом.
|
||||
@@ -0,0 +1,21 @@
|
||||
### Инструкции по размещению в магазине
|
||||
|
||||
**Где размещать:**
|
||||
- На витрине, входной двери, кассе или информационной стойке.
|
||||
|
||||
**Как разместить:**
|
||||
1. **Если QR-код размещается на витрине или входной двери:**
|
||||
- Найдите видное место на стекле, чтобы QR-код был хорошо заметен прохожим.
|
||||
- Очистите стекло от пыли и загрязнений.
|
||||
- Приклейте QR-код на внутреннюю сторону стекла, чтобы защитить его от погодных условий.
|
||||
2. **Если QR-код размещается на кассе:**
|
||||
- Используйте подставку или рамку, чтобы QR-код был на уровне глаз клиента.
|
||||
- Убедитесь, что он не мешает работе кассира.
|
||||
3. **Если QR-код размещается на информационной стойке:**
|
||||
- Прикрепите QR-код к стойке с помощью двустороннего скотча.
|
||||
- Либо используйте рамку, чтобы код выглядел аккуратно и профессионально.
|
||||
|
||||
**Важно:**
|
||||
- Убедитесь, что QR-код можно легко сканировать, даже при ярком свете или бликах.
|
||||
- Если QR-код находится на улице, используйте стойкие к погодным условиям материалы.
|
||||
- Разместите код так, чтобы он не мешал и не мог быть случайно повреждён.
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createApp } from 'vue'
|
||||
import { VueQueryPlugin } from '@tanstack/vue-query'
|
||||
import { createPinia } from 'pinia'
|
||||
import PouchDB from 'pouchdb-browser'
|
||||
|
||||
@@ -31,6 +32,9 @@ import router from './router'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(VueQueryPlugin, {
|
||||
enableDevtoolsV6Plugin: import.meta.env.DEV,
|
||||
})
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(PrimeVue, {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="max-w-lg mx-auto py-6 px-4 md:px-6">
|
||||
<div class="max-w-prose mx-auto py-6 px-4 md:px-6">
|
||||
<section class="content">
|
||||
<div class="max-w-lg mx-auto py-8">
|
||||
<div class="max-w-prose mx-auto py-8">
|
||||
<!-- Секция выбора размещения -->
|
||||
<CreateSection :isActive="activeSteps.includes('placement')">
|
||||
<CreatePlacementSection v-model="placement" />
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
<template>
|
||||
<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">
|
||||
<section class="max-w-prose 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>
|
||||
|
||||
<!-- Рендер QR‑кода -->
|
||||
<div v-if="!loading && svgUrl" class="border p-4 bg-white rounded-lg mb-6">
|
||||
<img :src="svgUrl" alt="QR‑код" />
|
||||
<div v-if="!svgQueryIsLoading && svgQueryData" class="border p-4 bg-white rounded-lg mb-6">
|
||||
<img :src="svgQueryData" alt="QR‑код" />
|
||||
</div>
|
||||
|
||||
<!-- Сообщение об ошибке -->
|
||||
<p v-if="error" class="text-red-500 mt-4">{{ error }}</p>
|
||||
<p v-if="loading" class="text-gray-500 mt-4">Загрузка...</p>
|
||||
<p v-if="qrCodeQueryIsError" class="text-red-500 mt-4">
|
||||
Ошибка загрузки данных QR‑кода: {{ qrCodeQueryError!.message }}
|
||||
</p>
|
||||
<p v-if="svgQueryIsLoading" class="text-gray-500 mt-4">Загрузка...</p>
|
||||
|
||||
<!-- Кнопки для скачивания -->
|
||||
<div v-if="!loading && !error" class="flex flex-row gap-4 justify-center">
|
||||
<div
|
||||
v-if="!svgQueryIsLoading && !qrCodeQueryIsError && svgQueryData"
|
||||
class="flex flex-row gap-4 justify-center"
|
||||
>
|
||||
<Button
|
||||
v-if="svgUrl"
|
||||
label="Скачать SVG"
|
||||
icon="pi pi-download"
|
||||
class="p-button-sm"
|
||||
:href="svgUrl"
|
||||
:href="svgQueryData"
|
||||
:download="`${sanitizedFileName}.svg`"
|
||||
as="a"
|
||||
/>
|
||||
@@ -32,6 +36,12 @@
|
||||
@click="downloadPng"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Инструкции по размещению -->
|
||||
<ManagePlacementInstructions
|
||||
:placement="qrCodeDoc?.placement"
|
||||
v-if="qrCodeDoc?.placement"
|
||||
/>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -50,27 +60,23 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { computed } from 'vue'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import QRCodeStyling from 'qr-code-styling'
|
||||
import { getQrCodeDocument } from '@/api/qrCode'
|
||||
import { optimize } from 'svgo'
|
||||
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { getQrCodeDocument } from '@/api/qrCode'
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
|
||||
const { qr_code_uri } = defineProps<{ qr_code_uri: QRCodeDocument['uri'] }>()
|
||||
|
||||
// Локальные состояния
|
||||
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(() => {
|
||||
const sanitized = qrCodeData.value?.name
|
||||
.replace(/[<>:"/\\|?*\x00-\x1F]/g, '') // Убираем запрещённые символы
|
||||
.replace(/\s+/g, '_') // Заменяем пробелы на подчёркивания
|
||||
.trim()
|
||||
return sanitized ? `${sanitized}_QR-Code_HereConnect` : 'QR-Code_HereConnect'
|
||||
// Хук для загрузки данных QR‑кода
|
||||
const {
|
||||
data: qrCodeDoc,
|
||||
isError: qrCodeQueryIsError,
|
||||
error: qrCodeQueryError,
|
||||
} = useQuery({
|
||||
queryKey: ['qrCode', computed(() => qr_code_uri)],
|
||||
queryFn: () => getQrCodeDocument(qr_code_uri),
|
||||
})
|
||||
|
||||
// Инициализация QRCodeStyling
|
||||
@@ -84,10 +90,26 @@ const qrCode = new QRCodeStyling({
|
||||
},
|
||||
})
|
||||
|
||||
// Генерация и оптимизация SVG
|
||||
async function generateOptimizedSvg(url: string) {
|
||||
try {
|
||||
qrCode.update({ data: url })
|
||||
// Получение имени файла
|
||||
const sanitizedFileName = computed(() => {
|
||||
const sanitized = qrCodeDoc.value?.name
|
||||
?.replace(/[<>:"/\\|?*\x00-\x1F]/g, '') // Убираем запрещённые символы
|
||||
.replace(/\s+/g, '_') // Заменяем пробелы на подчёркивания
|
||||
.trim()
|
||||
return sanitized ? `${sanitized}_QR-Code_HereConnect` : 'QR-Code_HereConnect'
|
||||
})
|
||||
|
||||
// Хук для генерации и оптимизации SVG
|
||||
const {
|
||||
isLoading: svgQueryIsLoading,
|
||||
data: svgQueryData,
|
||||
error: svgQueryError,
|
||||
} = useQuery({
|
||||
queryKey: ['svg', computed(() => qr_code_uri)],
|
||||
enabled: computed(() => !!qrCodeDoc.value?.url),
|
||||
queryFn: async () => {
|
||||
qrCode.update({ data: qrCodeDoc.value!.url })
|
||||
|
||||
const blob = await qrCode.getRawData('svg')
|
||||
if (blob && blob instanceof Blob) {
|
||||
const text = await blob.text()
|
||||
@@ -104,17 +126,20 @@ async function generateOptimizedSvg(url: string) {
|
||||
})
|
||||
|
||||
if (optimizedSvg.data) {
|
||||
svgUrl.value = URL.createObjectURL(new Blob([optimizedSvg.data], { type: 'image/svg+xml' }))
|
||||
return URL.createObjectURL(new Blob([optimizedSvg.data], { type: 'image/svg+xml' }))
|
||||
} else {
|
||||
throw new Error('Ошибка оптимизации SVG')
|
||||
}
|
||||
} else {
|
||||
throw new Error('Ошибка генерации QR‑кода')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = (err as Error).message || 'Неизвестная ошибка'
|
||||
}
|
||||
}
|
||||
},
|
||||
onSettled(data) {
|
||||
if (!data && svgQueryError) {
|
||||
console.error('Ошибка загрузки SVG:', svgQueryError.message)
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// Скачивание PNG
|
||||
async function downloadPng() {
|
||||
@@ -124,28 +149,7 @@ async function downloadPng() {
|
||||
extension: 'png',
|
||||
})
|
||||
} catch (err) {
|
||||
error.value = (err as Error).message || 'Ошибка при скачивании QR‑кода'
|
||||
console.error('Ошибка скачивания PNG:', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Монтирование
|
||||
onMounted(async () => {
|
||||
try {
|
||||
// Загружаем данные QR‑кода
|
||||
const doc = await getQrCodeDocument(qr_code_uri)
|
||||
qrCodeData.value = { name: doc.name, url: doc.url }
|
||||
await generateOptimizedSvg(doc.url)
|
||||
} catch (err) {
|
||||
error.value = (err as Error).message || 'Ошибка загрузки данных QR‑кода'
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// Очистка ресурсов при размонтировании
|
||||
onUnmounted(() => {
|
||||
if (svgUrl.value) {
|
||||
URL.revokeObjectURL(svgUrl.value)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<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-10">
|
||||
<section class="max-w-prose w-full flex flex-col gap-10">
|
||||
<template v-for="action in actions">
|
||||
<ReadContactInfo v-if="action === 'viewContactInfo'" :contactInfo="contactInfo" />
|
||||
<ReadMessageSender
|
||||
|
||||
Reference in New Issue
Block a user