This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@f3ve/vue-markdown-it": "^0.2.3",
|
||||
"@hereconnect/types": "^1.0.0",
|
||||
"@primevue/forms": "^4.2.1",
|
||||
"@primevue/themes": "^4.2.1",
|
||||
"@tanstack/vue-query": "^5.60.6",
|
||||
|
||||
@@ -4,8 +4,8 @@ import type {
|
||||
QRCodeDocument,
|
||||
MessageDocument,
|
||||
WebPushSubscriptionDocument,
|
||||
} from '@/types/DBDocumentTypes'
|
||||
import type { PublicSettingsDocument } from '@/types/DBDocumentTypes/PublicSettingsDocument'
|
||||
PublicSettingsDocument,
|
||||
} from '@hereconnect/types'
|
||||
|
||||
export const qrCodeDbRemote = new PouchDB<QRCodeDocument>(`${import.meta.env.VITE_API_URL}/qr`, {
|
||||
skip_setup: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import type { QRCodeDocument } from '@hereconnect/types'
|
||||
import { qrCodeDbLocal, qrCodeDbRemote } from '@/api/dbs'
|
||||
import { getUserUuid } from '@/utils/getUserUuid'
|
||||
import { getBrowserUuid } from '@/utils/getBrowserUuid'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { WebPushSubscriptionDocument } from '@/types/DBDocumentTypes'
|
||||
import type { WebPushSubscriptionDocument } from '@hereconnect/types'
|
||||
import { publicSettingsDbRemote, webPushSubscriptionsDbRemote } from '@/api/dbs'
|
||||
import { getUserUuid } from '@/utils/getUserUuid'
|
||||
import { getBrowserUuid } from '@/utils/getBrowserUuid'
|
||||
|
||||
@@ -40,7 +40,9 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, useTemplateRef, watch, nextTick } from 'vue'
|
||||
import { messagesDbLocal as db, messagesDbRemote as remoteDb } from '@/api/dbs'
|
||||
import { type MessageDocument } from '@/types/DBDocumentTypes'
|
||||
import { type MessageDocument } from '@hereconnect/types'
|
||||
import { getBrowserUuid } from '@/utils/getBrowserUuid'
|
||||
import { getUserUuid } from '@/utils/getUserUuid'
|
||||
|
||||
const formRef = useTemplateRef('form')
|
||||
const messagesContainerRef = useTemplateRef('messagesContainer')
|
||||
@@ -84,9 +86,11 @@ async function sendMessage() {
|
||||
type: 'message',
|
||||
qr_code_uri,
|
||||
chat,
|
||||
created_at,
|
||||
from: userRole,
|
||||
body: newMessage.value.trim(),
|
||||
browser_uuid: getBrowserUuid(),
|
||||
user_uuid: getUserUuid(),
|
||||
created_at,
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { type QRCodeDocument } from '@hereconnect/types'
|
||||
import CreateMessageDeliveryOptionTelegram from '@/components/create/CreateMessageDeliveryOptionTelegram.vue'
|
||||
import CreateMessageDeliveryOptionWebPush from '@/components/create/CreateMessageDeliveryOptionWebPush.vue'
|
||||
import { ref } from 'vue'
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import type { QRCodeDocument } from '@hereconnect/types'
|
||||
import type { DeliveryOption } from '@/constants/deliveryOptions'
|
||||
|
||||
defineProps<{
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { type QRCodeDocument } from '@hereconnect/types'
|
||||
import { type DeliveryOption, DELIVERY_METHOD_TELEGRAM } from '@/constants/deliveryOptions'
|
||||
import { computed } from 'vue'
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { type QRCodeDocument } from '@hereconnect/types'
|
||||
import { type DeliveryOption, DELIVERY_METHOD_WEB_PUSH } from '@/constants/deliveryOptions'
|
||||
import { computed } from 'vue'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { qrCodePlacements } from '@/constants/qrCodePlacements'
|
||||
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import type { QRCodeDocument } from '@hereconnect/types'
|
||||
const selectedPlacement = defineModel<null | QRCodeDocument['placement']>()
|
||||
|
||||
defineEmits(['nextStep'])
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, useTemplateRef, watch } from 'vue'
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { type QRCodeDocument } from '@hereconnect/types'
|
||||
import { defaultMessages } from '@/constants/defaultMessages'
|
||||
|
||||
// Пропсы
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import type { QRCodeDocument } from '@hereconnect/types'
|
||||
|
||||
// Пропсы, которые будут передаваться из родительского компонента
|
||||
const props = defineProps<{
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { type QRCodeDocument } from '@hereconnect/types'
|
||||
defineProps<{ buttonLabel: string }>()
|
||||
const name = defineModel<QRCodeDocument['name']>('name')
|
||||
const emit = defineEmits(['nextStep'])
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import { computed } from 'vue'
|
||||
import { VueMarkdownIt } from '@f3ve/vue-markdown-it'
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { type QRCodeDocument } from '@hereconnect/types'
|
||||
|
||||
// Импортируем все инструкции из директории
|
||||
const importInstructions = import.meta.glob('/src/content/placementInstructions/*.md', {
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import md5 from 'md5'
|
||||
import type { ContactInfo } from '@/types/DBDocumentTypes'
|
||||
import type { ContactInfo } from '@hereconnect/types'
|
||||
import { sanitizeFileName } from '@/utils/sanitizeFileName'
|
||||
|
||||
const { contactInfo } = defineProps<{ contactInfo: ContactInfo }>()
|
||||
|
||||
@@ -45,8 +45,10 @@
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { messagesDbLocal as db } from '@/api/dbs'
|
||||
import type { MessageDocument, QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import type { MessageDocument, QRCodeDocument } from '@hereconnect/types'
|
||||
import { ROUTE_NAMES } from '@/constants/routes'
|
||||
import { getUserUuid } from '@/utils/getUserUuid'
|
||||
import { getBrowserUuid } from '@/utils/getBrowserUuid'
|
||||
|
||||
const { doc } = defineProps<{
|
||||
doc: QRCodeDocument
|
||||
@@ -76,6 +78,8 @@ async function sendMessage(content: string) {
|
||||
chat,
|
||||
created_at: new Date().toISOString(),
|
||||
from: 'guest',
|
||||
user_uuid: getUserUuid(),
|
||||
browser_uuid: getBrowserUuid(),
|
||||
body: content.trim(),
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* Базовый интерфейс для всех документов в базе данных.
|
||||
* Содержит общие поля, такие как идентификатор, тип и дату создания.
|
||||
*/
|
||||
export interface BaseDocument {
|
||||
/** Уникальный идентификатор документа */
|
||||
_id: string
|
||||
|
||||
_rev?: string
|
||||
|
||||
/** Тип документа */
|
||||
type: string
|
||||
|
||||
/** Дата создания документа в формате ISO */
|
||||
created_at: string
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { type BaseDocument } from './BaseDocument'
|
||||
|
||||
/**
|
||||
* Интерфейс для документа сообщения.
|
||||
* Используется для хранения сообщений, отправленных через QR‑код, с поддержкой вложений.
|
||||
*/
|
||||
export interface MessageDocument extends BaseDocument {
|
||||
/** Тип документа, всегда 'message' */
|
||||
type: 'message'
|
||||
|
||||
/** Идентификатор QR‑кода, к которому относится сообщение */
|
||||
qr_code_uri: string
|
||||
|
||||
/** Идентификатор чата для связывания сообщений */
|
||||
chat: string
|
||||
|
||||
/** Отправитель сообщения ('guest' или 'owner') */
|
||||
from: 'guest' | 'owner'
|
||||
|
||||
/** Содержимое сообщения */
|
||||
body: string
|
||||
|
||||
/** Опциональная геопозиция (широта и долгота) */
|
||||
location?: { lat: number; lng: number }
|
||||
|
||||
/** Вложения, такие как фото */
|
||||
// _attachments?: {
|
||||
// [key: string]: Attachment
|
||||
// }
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import type { BaseDocument } from '@/types/DBDocumentTypes/BaseDocument'
|
||||
|
||||
export interface PublicSettingsDocument extends BaseDocument {
|
||||
_id: string // Формат: "public_settings:<name>"
|
||||
type: 'public_settings'
|
||||
|
||||
/**
|
||||
* Название настройки
|
||||
*/
|
||||
name: string
|
||||
|
||||
/**
|
||||
* Значение настройки
|
||||
*/
|
||||
value: string
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import type { BaseDocument } from './BaseDocument'
|
||||
|
||||
export type ContactInfo = {
|
||||
name?: string
|
||||
phone?: string
|
||||
email?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Интерфейс для документа QR‑кода.
|
||||
* Представляет QR‑код, созданный пользователем, и информацию о привязанном объекте.
|
||||
*/
|
||||
export interface QRCodeDocument extends BaseDocument {
|
||||
/** Тип документа, всегда 'qr_code' */
|
||||
type: 'qr_code'
|
||||
|
||||
/** Уникальный URL для настройки и взаимодействия */
|
||||
uri: string
|
||||
|
||||
/** Ссылка на QR‑код */
|
||||
url: string
|
||||
|
||||
/** Идентификатор пользователя, который создал QR‑код */
|
||||
user_uuid: string
|
||||
|
||||
/** Идентификатор браузера, через который был создан QR-код */
|
||||
browser_uuid: string
|
||||
|
||||
/** Название объекта, для которого создан QR‑код, например "Мой автомобиль" */
|
||||
name: string
|
||||
|
||||
messageDeliveryMethod: 'telegram' | 'webPush'
|
||||
|
||||
/** Тип объекта, для которого создан QR‑код, например "car" */
|
||||
placement: 'car' | 'pet' | 'door' | 'store' | 'restaurant_table' | 'other'
|
||||
|
||||
/** Доступные действия для гостей (например, ["send_message"]) */
|
||||
actions: string[]
|
||||
|
||||
/** Предустановленное сообщение для гостей */
|
||||
predefinedMessages?: string[]
|
||||
|
||||
contactInfo?: ContactInfo
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import type { BaseDocument } from './BaseDocument'
|
||||
|
||||
/**
|
||||
* Интерфейс для документа пользователя.
|
||||
* Содержит данные, такие как имя пользователя, уникальный UUID, email и роли.
|
||||
*/
|
||||
export interface UserDocument extends BaseDocument {
|
||||
/** Тип документа, всегда 'user' */
|
||||
type: 'user'
|
||||
|
||||
/** Имя пользователя */
|
||||
name: string
|
||||
|
||||
/** Уникальный UUID пользователя, не связанный с _id */
|
||||
user_uuid: string
|
||||
|
||||
/** Email пользователя */
|
||||
email: string
|
||||
|
||||
/** Роли пользователя (например, ['admin', 'user']) */
|
||||
roles: string[]
|
||||
|
||||
/** Пароль пользователя (должен храниться в зашифрованном виде) */
|
||||
password: string
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
import type { BaseDocument } from '@/types/DBDocumentTypes/BaseDocument'
|
||||
|
||||
export interface WebPushSubscriptionDocument extends BaseDocument {
|
||||
_id: string // Формат: "web_push_subscription:<user_uuid>:<browser_uuid>"
|
||||
type: 'web_push_subscription'
|
||||
user_uuid: string // UUID пользователя, связанного с подпиской
|
||||
browser_uuid: string // UUID браузера, связанного с подпиской
|
||||
subscription: PushSubscriptionJSON
|
||||
created_at: string // Дата создания подписки в ISO формате
|
||||
updated_at?: string // Дата последнего обновления подписки
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export * from './BaseDocument'
|
||||
export * from './QRCodeDocument'
|
||||
export * from './MessageDocument'
|
||||
export * from './UserDocument'
|
||||
export * from './WebPushSubscriptionDocument'
|
||||
@@ -44,7 +44,7 @@
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { createQrCodeDocument } from '@/api/qrCode'
|
||||
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import type { QRCodeDocument } from '@hereconnect/types'
|
||||
import { ROUTE_NAMES } from '@/constants/routes'
|
||||
|
||||
const placement = ref<null | QRCodeDocument['placement']>(null)
|
||||
|
||||
@@ -1,52 +1,62 @@
|
||||
<template>
|
||||
<div class="flex flex-col min-h-full">
|
||||
<main class="flex-grow flex items-center justify-center py-6 px-4 md:px-6">
|
||||
<main
|
||||
class="flex-grow flex justify-center py-6 px-4 md:px-6"
|
||||
:class="{
|
||||
'items-center':
|
||||
svgQueryIsLoading || qrCodeQueryIsLoading || svgQueryIsError || qrCodeQueryIsError,
|
||||
}"
|
||||
>
|
||||
<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="!svgQueryIsLoading && svgQueryData" class="border p-4 bg-white rounded-lg mb-6">
|
||||
<img :src="svgQueryData" alt="QR‑код" />
|
||||
</div>
|
||||
<p v-if="svgQueryIsLoading || qrCodeQueryIsLoading" class="text-gray-500 mt-4">
|
||||
Загрузка...
|
||||
</p>
|
||||
<p v-else-if="svgQueryIsError" class="text-red-500 mt-4">
|
||||
Ошибка генерации QR‑кода: {{ svgQueryError?.message }}
|
||||
</p>
|
||||
|
||||
<!-- Сообщение об ошибке -->
|
||||
<p v-if="qrCodeQueryIsError" class="text-red-500 mt-4">
|
||||
<p v-else-if="qrCodeQueryIsError" class="text-red-500 mt-4">
|
||||
Ошибка загрузки данных QR‑кода: {{ qrCodeQueryError!.message }}
|
||||
</p>
|
||||
<p v-if="svgQueryIsLoading" class="text-gray-500 mt-4">Загрузка...</p>
|
||||
<template v-else>
|
||||
<h2 class="text-xl font-semibold mb-4">Ваш QR‑код готов</h2>
|
||||
<p class="text-gray-600 mb-4">
|
||||
Сохраните его и ознакомьтесь с инструкциями по размещению.
|
||||
</p>
|
||||
|
||||
<!-- Кнопки для скачивания -->
|
||||
<div
|
||||
v-if="!svgQueryIsLoading && !qrCodeQueryIsError && svgQueryData"
|
||||
class="flex flex-row gap-4 justify-center"
|
||||
>
|
||||
<Button
|
||||
label="Скачать SVG"
|
||||
icon="pi pi-download"
|
||||
class="p-button-sm"
|
||||
:href="svgQueryData"
|
||||
:download="`${sanitizedFileName}.svg`"
|
||||
as="a"
|
||||
<!-- Рендер QR‑кода -->
|
||||
<div class="border p-4 bg-white rounded-lg mb-6">
|
||||
<img v-if="!svgQueryIsLoading && svgQueryData" :src="svgQueryData" alt="QR‑код" />
|
||||
</div>
|
||||
|
||||
<!-- Кнопки для скачивания -->
|
||||
<div
|
||||
v-if="!svgQueryIsLoading && !qrCodeQueryIsError && svgQueryData"
|
||||
class="flex flex-row gap-4 justify-center"
|
||||
>
|
||||
<Button
|
||||
label="Скачать SVG"
|
||||
icon="pi pi-download"
|
||||
class="p-button-sm"
|
||||
:href="svgQueryData"
|
||||
:download="`${sanitizedFileName}.svg`"
|
||||
as="a"
|
||||
/>
|
||||
|
||||
<DownloadImageButton
|
||||
:svgUrl="svgQueryData"
|
||||
:filename="`${sanitizedFileName}.png`"
|
||||
label="Скачать PNG"
|
||||
type="image/png"
|
||||
:width="width"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Инструкции по размещению -->
|
||||
<ManagePlacementInstructions
|
||||
:placement="qrCodeDoc?.placement"
|
||||
v-if="qrCodeDoc?.placement"
|
||||
/>
|
||||
|
||||
<DownloadImageButton
|
||||
:svgUrl="svgQueryData"
|
||||
:filename="`${sanitizedFileName}.png`"
|
||||
label="Скачать PNG"
|
||||
type="image/png"
|
||||
:width="width"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Инструкции по размещению -->
|
||||
<ManagePlacementInstructions
|
||||
:placement="qrCodeDoc?.placement"
|
||||
v-if="qrCodeDoc?.placement"
|
||||
/>
|
||||
</template>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
@@ -71,7 +81,7 @@ import QRCodeStyling from 'qr-code-styling'
|
||||
import { optimizeSvg } from '@/utils/images/svg/optimizeSvg'
|
||||
import { replaceFillInSvg } from '@/utils/images/svg/replaceFillInSvg'
|
||||
import { getQrCodeDocument } from '@/api/qrCode'
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { type QRCodeDocument } from '@hereconnect/types'
|
||||
import { addLabelToSvg } from '@/utils/images/svg/addLabelToSvg'
|
||||
import logo from '@/assets/logo.svg?raw'
|
||||
import { sanitizeFileName } from '@/utils/sanitizeFileName'
|
||||
@@ -86,11 +96,15 @@ const height = ref(width.value)
|
||||
// Хук для загрузки данных QR‑кода
|
||||
const {
|
||||
data: qrCodeDoc,
|
||||
isLoading: qrCodeQueryIsLoading,
|
||||
isError: qrCodeQueryIsError,
|
||||
error: qrCodeQueryError,
|
||||
} = useQuery({
|
||||
queryKey: ['qrCode', computed(() => qr_code_uri)],
|
||||
queryFn: () => getQrCodeDocument(qr_code_uri),
|
||||
queryFn: async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
return getQrCodeDocument(qr_code_uri)
|
||||
},
|
||||
})
|
||||
|
||||
const sanitizedFileName = computed(() => {
|
||||
@@ -110,6 +124,8 @@ const {
|
||||
enabled: computed(() => !!qrCodeDoc.value?.url),
|
||||
retry: false,
|
||||
queryFn: async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
// Заменяем цвет `fill` в логотипе
|
||||
const updatedLogo = replaceFillInSvg(logo, logoColor.value)
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { MessageDocument } from '@/types/DBDocumentTypes'
|
||||
import type { MessageDocument } from '@hereconnect/types'
|
||||
import ChatComponent from '@/components/ChatComponent.vue'
|
||||
defineProps<{ qr_code_uri: MessageDocument['qr_code_uri']; chat: MessageDocument['chat'] }>()
|
||||
</script>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
import { getQrCodeDocument } from '@/api/qrCode'
|
||||
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import type { QRCodeDocument } from '@hereconnect/types'
|
||||
import QueryRender from '@/components/QueryRender.vue'
|
||||
|
||||
const { qr_code_uri } = defineProps<{ qr_code_uri: string }>()
|
||||
|
||||
Reference in New Issue
Block a user