@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold mb-4">Выберите доступные действия</h2>
|
||||
<p class="text-gray-600 mb-4">Укажите, какие действия будут доступны для сканирующих QR‑код.</p>
|
||||
<p class="text-sm text-gray-500 mb-6">Вы сможете изменить эти настройки позже.</p>
|
||||
<div class="flex flex-col gap-3 mb-4">
|
||||
<div v-for="(label, action) in actionsOptions" :key="action" class="flex items-center gap-2">
|
||||
<Checkbox v-model="model" :inputId="action" :value="action" @change="next" />
|
||||
<label :for="action">{{ label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const emit = defineEmits(['nextStep'])
|
||||
const model = defineModel()
|
||||
|
||||
const actionsOptions = {
|
||||
sendMessage: 'Отправить сообщение',
|
||||
viewContactInfo: 'Посмотреть ваши контактные данные',
|
||||
}
|
||||
|
||||
function next() {
|
||||
emit('nextStep')
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold mb-4">Куда отправлять сообщения?</h2>
|
||||
<p class="text-gray-600 mb-4">Выберите, как вы хотите получать сообщения.</p>
|
||||
<div class="flex flex-col gap-3 mb-4">
|
||||
<div class="flex items-center gap-2" v-for="option in deliveryOptions" :key="option.method">
|
||||
<RadioButton
|
||||
v-model="model"
|
||||
:inputId="option.method"
|
||||
name="delivery"
|
||||
:value="option.method"
|
||||
@change="handleSelection(option.method)"
|
||||
/>
|
||||
<label :for="option.method">{{ option.label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Сообщение о недоступности -->
|
||||
<p v-if="telegramNotice" class="text-red-500 mb-2">
|
||||
Отправка в Telegram пока в разработке.<br />
|
||||
Этот способ отправки сообщений можно будет выбрать позже.
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { deliveryOptions } from '@/constants/deliveryOptions'
|
||||
|
||||
const emit = defineEmits(['nextStep'])
|
||||
const model = defineModel<QRCodeDocument['messageDeliveryMethod']>()
|
||||
|
||||
// Локальное состояние для уведомления
|
||||
const telegramNotice = ref(false)
|
||||
|
||||
// Обработчик выбора
|
||||
function handleSelection(method: QRCodeDocument['messageDeliveryMethod']) {
|
||||
if (method === 'telegram') {
|
||||
telegramNotice.value = true
|
||||
} else {
|
||||
telegramNotice.value = false
|
||||
emit('nextStep')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold mb-4">Где вы хотите разместить QR‑код?</h2>
|
||||
<p class="text-gray-600 mb-4">Этот выбор поможет настроить QR‑код с подходящими действиями.</p>
|
||||
<div class="flex flex-col gap-3 mb-4">
|
||||
<div v-for="{ label, type } in qrCodePlacements" :key="type" class="flex items-center gap-2">
|
||||
<RadioButton
|
||||
v-model="selectedPlacement"
|
||||
:inputId="type"
|
||||
name="placement"
|
||||
:value="type"
|
||||
@change="$emit('nextStep')"
|
||||
/>
|
||||
<label :for="type">{{ label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { qrCodePlacements } from '@/constants/qrCodePlacements'
|
||||
const selectedPlacement = defineModel<string>()
|
||||
|
||||
defineEmits(['nextStep'])
|
||||
</script>
|
||||
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold mb-4">Сообщения для отправки</h2>
|
||||
<p class="text-gray-600 mb-4">
|
||||
После считывания QR‑кода можно будет выбрать одно из этих сообщений или написать своё.
|
||||
</p>
|
||||
<p class="text-sm text-gray-500 mb-6">Вы сможете изменить эти сообщения позже.</p>
|
||||
|
||||
<!-- Список сообщений -->
|
||||
<div class="flex flex-col gap-2 mb-4">
|
||||
<div v-for="(message, index) in messages" :key="index" class="flex gap-2" ref="messagesRef">
|
||||
<InputText v-model="messages[index]" class="flex-1" placeholder="Введите сообщение" />
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
class="p-button-danger p-button-text"
|
||||
@click="removeMessage(index)"
|
||||
title="Удалить сообщение"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Кнопка добавления сообщения -->
|
||||
<Button
|
||||
label="Добавить сообщение"
|
||||
icon="pi pi-plus"
|
||||
class="p-button-sm p-button-outlined mb-4"
|
||||
@click="addMessage"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { nextTick, useTemplateRef, watch } from 'vue'
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
import { defaultMessages } from '@/constants/defaultMessages'
|
||||
|
||||
// Пропсы
|
||||
const props = defineProps<{ placement: QRCodeDocument['placement'] }>()
|
||||
const messagesRef = useTemplateRef<HTMLDivElement[]>('messagesRef')
|
||||
|
||||
// Локальное хранилище сообщений
|
||||
const messages = defineModel<Exclude<QRCodeDocument['predefinedMessages'], undefined>>({
|
||||
required: true,
|
||||
})
|
||||
|
||||
// Следим за типом размещения и устанавливаем предустановленные сообщения при его смене
|
||||
watch(
|
||||
() => props.placement,
|
||||
(newPlacement, previousPlacement) => {
|
||||
let prevDefaultMessages: (typeof defaultMessages)[QRCodeDocument['placement']] = []
|
||||
if (previousPlacement && previousPlacement in defaultMessages) {
|
||||
prevDefaultMessages = defaultMessages[previousPlacement]
|
||||
}
|
||||
const isMessagesDefault =
|
||||
messages.value.length === 0 ||
|
||||
JSON.stringify(messages.value) === JSON.stringify(prevDefaultMessages)
|
||||
if (isMessagesDefault) {
|
||||
messages.value = [...(defaultMessages[newPlacement] || [])]
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// Функции для управления сообщениями
|
||||
function addMessage() {
|
||||
messages.value.push('')
|
||||
nextTick(() => {
|
||||
const lastMessageRef = messagesRef.value![messagesRef.value!.length - 1]
|
||||
lastMessageRef?.querySelector('input')?.focus() // Фокусируемся на последнем поле
|
||||
})
|
||||
}
|
||||
|
||||
function removeMessage(index: number) {
|
||||
messages.value.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<section :class="{ 'active-section': isActive }" ref="section">
|
||||
<slot />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { watch, useTemplateRef } from 'vue'
|
||||
|
||||
const props = defineProps<{ isActive: boolean; isNextActiveSection: boolean }>()
|
||||
|
||||
const sectionRef = useTemplateRef('section')
|
||||
|
||||
// плавная прокрутка при активации секции
|
||||
watch(
|
||||
() => sectionRef.value && props.isNextActiveSection,
|
||||
(isNextActiveSection) => {
|
||||
if (isNextActiveSection) {
|
||||
sectionRef.value?.querySelector('input[type="text"]')?.focus()
|
||||
sectionRef.value?.scrollIntoView({ behavior: 'smooth' })
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
section {
|
||||
opacity: 0.1;
|
||||
transition: opacity 0.5s;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
section {
|
||||
border-top: 1px solid silver;
|
||||
padding-top: 1em;
|
||||
}
|
||||
section:first-of-type {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
section.active-section {
|
||||
opacity: 1;
|
||||
pointer-events: all;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,107 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Вывод секций через цикл -->
|
||||
<CreateSection
|
||||
v-for="{ name, isActive, isNextActiveSection } in visibleSections"
|
||||
:key="name"
|
||||
:isActive
|
||||
:isNextActiveSection
|
||||
>
|
||||
<slot :name="name" />
|
||||
</CreateSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import type { QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
|
||||
// Пропсы, которые будут передаваться из родительского компонента
|
||||
const props = defineProps<{
|
||||
placement: null | QRCodeDocument['placement']
|
||||
selectedActions: QRCodeDocument['actions']
|
||||
messageDeliveryMethod: null | QRCodeDocument['messageDeliveryMethod']
|
||||
}>()
|
||||
|
||||
// Доступные шаги и их условия
|
||||
const steps = [
|
||||
{
|
||||
name: 'placement',
|
||||
isActive: true,
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
get isActive() {
|
||||
return !!props.placement
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delivery',
|
||||
get isHidden() {
|
||||
return !props.placement || !props.selectedActions.includes('sendMessage')
|
||||
},
|
||||
get isActive() {
|
||||
return props.selectedActions.length > 0 && props.selectedActions.includes('sendMessage')
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'messages',
|
||||
get isHidden() {
|
||||
return (
|
||||
!props.placement ||
|
||||
!props.selectedActions.includes('sendMessage') ||
|
||||
props.messageDeliveryMethod === 'telegram'
|
||||
)
|
||||
},
|
||||
get isActive() {
|
||||
return props.messageDeliveryMethod && props.selectedActions.includes('sendMessage')
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'summary',
|
||||
get isActive() {
|
||||
return props.selectedActions.includes('sendMessage')
|
||||
? !!props.messageDeliveryMethod && props.messageDeliveryMethod !== 'telegram'
|
||||
: props.selectedActions.length > 0
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const activeSections = computed(() => {
|
||||
const result = []
|
||||
for (const step of steps) {
|
||||
const isActive = !step.isHidden && step.isActive
|
||||
if (isActive) {
|
||||
result.push(step.name)
|
||||
}
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const nextActiveSectionName = ref<string>('')
|
||||
|
||||
watch(
|
||||
activeSections,
|
||||
(newActiveSections, oldActiveSteps) => {
|
||||
nextActiveSectionName.value =
|
||||
(oldActiveSteps && newActiveSections[oldActiveSteps.length]) ?? newActiveSections.at(-1)!
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
},
|
||||
)
|
||||
|
||||
// Выводимые секции
|
||||
const visibleSections = computed(() => {
|
||||
const result = []
|
||||
for (const step of steps) {
|
||||
if (step.isHidden) continue
|
||||
result.push({
|
||||
name: step.name,
|
||||
isActive: activeSections.value.includes(step.name),
|
||||
isNextActiveSection: nextActiveSectionName.value === step.name,
|
||||
})
|
||||
}
|
||||
return result
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<section>
|
||||
<h2 class="text-xl font-semibold mb-4">Введите название для вашего QR‑кода</h2>
|
||||
<p class="text-gray-600 mb-4">Название поможет вам организовать ваши QR‑коды.</p>
|
||||
|
||||
<form @submit.prevent="emit('nextStep')">
|
||||
<label for="name" class="block text-gray-700 font-semibold mb-2">Название</label>
|
||||
<InputText id="name" v-model="name" class="w-full" placeholder="Введите название" />
|
||||
<p v-if="!name" class="text-red-500 text-sm mt-1">Название обязательно.</p>
|
||||
|
||||
<Button type="submit" :label="buttonLabel" class="p-button-md mt-4 mb-4" :disabled="!name" />
|
||||
</form>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type QRCodeDocument } from '@/types/DBDocumentTypes'
|
||||
defineProps<{ buttonLabel: string }>()
|
||||
const name = defineModel<QRCodeDocument['name']>('name')
|
||||
const emit = defineEmits(['nextStep'])
|
||||
</script>
|
||||
Reference in New Issue
Block a user