add qr-code install instructions
Main daploy / deploy (push) Successful in 47s

This commit is contained in:
2024-11-19 18:49:12 +02:00
parent 4dbf9825eb
commit 5d66a3c79a
16 changed files with 755 additions and 63 deletions
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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>
<!-- Предустановленные сообщения -->
+22
View File
@@ -0,0 +1,22 @@
### Инструкции по размещению на автомобиль:
**Где размещать:**
- На боковом или заднем стекле автомобиля.
**Как разместить:**
1. Найдите видимое место на стекле, которое будет хорошо видно прохожим, например, в углу заднего или бокового окна.
2. Очистите поверхность стекла от пыли и грязи с помощью сухой или слегка влажной ткани.
3. Если вы используете наклейку:
- Убедитесь, что материал устойчив к влаге и выцветанию.
- Аккуратно приклейте наклейку на стекло, разглаживая её от центра к краям, чтобы избежать появления пузырьков воздуха.
4. Если вы используете пластину или рамку:
- Проверьте, что крепление не повредит стекло.
- Убедитесь, что QR-код надёжно зафиксирован и не шатается.
5. Если QR-код нанесён с помощью трафарета или гравировки:
- Проверьте, что код выполнен без дефектов и легко сканируется.
- Очистите стекло от остатков краски или мусора.
**Важно:**
- Убедитесь, что QR-код не блокирует обзор водителю.
- Проверьте, что код можно легко сканировать снаружи автомобиля в любое время суток.
- Если QR-код размещён внутри, убедитесь, что стекло чистое и не бликует.
+14
View File
@@ -0,0 +1,14 @@
### Инструкции по размещению на входную дверь дома или квартиры:
**Где размещать:**
- На входной двери или рядом с домофоном.
- На двери или рядом с дверным звонком.
**Как разместить:**
1. Найдите место рядом с дверным звонком или на самой двери, которое будет заметно для гостей.
2. Очистите поверхность от пыли и грязи, чтобы QR-код держался крепче.
3. Приклейте QR-код на дверь с помощью двустороннего скотча или водостойкого клея.
4. Если вы не хотите приклеивать код, используйте рамку или подставку и разместите её на полке или возле двери.
**Важно:**
- Проверьте, что QR-код не блокирует другие элементы двери.
- Убедитесь, что его легко заметить и сканировать при любом освещении.
@@ -0,0 +1,12 @@
### Инструкции по размещению:
**Где размещать:**
- В зависимости от ситуации, на любом удобном и видимом месте.
**Как разместить:**
1. Подберите место, где QR-код будет защищён от повреждений и виден окружающим.
2. Используйте подходящие материалы для крепления: двусторонний скотч, рамку, подставку или брелок.
3. Убедитесь, что QR-код хорошо заметен и его легко сканировать.
**Важно:**
- Адаптируйте место размещения под конкретную задачу.
- Проверьте, чтобы QR-код не был скрыт другими предметами.
+13
View File
@@ -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-код находится на улице, используйте стойкие к погодным условиям материалы.
- Разместите код так, чтобы он не мешал и не мог быть случайно повреждён.
+4
View File
@@ -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, {
+2 -2
View File
@@ -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" />
+59 -55
View File
@@ -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 -1
View File
@@ -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