feat: add FormKit framework support and update dependencies
- Add `telecart_forms` table migration and default checkout form seeder - Implement `FormsHandler` to fetch form schemas - Update `OrderCreateService` to handle custom fields in order comments - Add `update` method to QueryBuilder and Grammar - Add `Arr::except` helper - Update composer dependencies (Carbon, Symfony, PHPUnit, etc.) - Improve `MigratorService` error handling - Add unit tests for new functionality
This commit is contained in:
139
frontend/admin/src/components/FormBuilder/FormCanvas.vue
Normal file
139
frontend/admin/src/components/FormBuilder/FormCanvas.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<div
|
||||
class="tw:min-h-full tw:w-full tw:flex tw:flex-col tw:items-center tw:p-8 blueprint-bg"
|
||||
@click.self="handleBackgroundClick"
|
||||
>
|
||||
<div v-if="formFields.length === 0" class="tw:absolute tw:top-1/2 tw:left-1/2 tw:-translate-x-1/2 tw:-translate-y-1/2 tw:z-0 tw:text-center tw:text-gray-500 tw:py-12 tw:bg-white/80 tw:backdrop-blur-sm tw:rounded-xl tw:p-8 tw:shadow-sm tw:max-w-md tw:pointer-events-none">
|
||||
<i class="fa fa-mouse-pointer tw:text-4xl tw:mb-4 tw:text-blue-500/50"></i>
|
||||
<p class="tw:font-medium">Перетащите поля сюда, чтобы начать создавать форму</p>
|
||||
</div>
|
||||
|
||||
<draggable
|
||||
v-model="formFields"
|
||||
group="fields"
|
||||
item-key="id"
|
||||
class="tw:w-full tw:max-w-2xl tw:space-y-4 tw:flex-1 tw:min-h-[300px] tw:relative tw:z-10 tw:pb-24"
|
||||
ghost-class="ghost-field"
|
||||
drag-class="drag-field"
|
||||
@change="handleDragChange"
|
||||
@click.self="handleBackgroundClick"
|
||||
>
|
||||
<template #item="{ element: field, index }">
|
||||
<div
|
||||
class="tw:relative tw:group tw:border-2 tw:rounded-lg tw:p-4 tw:bg-white tw:shadow-sm tw:cursor-pointer tw:transition-all"
|
||||
:class="[
|
||||
fieldErrors[field.id] ? 'tw:border-red-500 tw:ring-2 tw:ring-red-200' :
|
||||
selectedFieldId === field.id ? 'tw:border-blue-500 tw:ring-2 tw:ring-blue-200' : 'tw:border-transparent hover:tw:border-blue-400'
|
||||
]"
|
||||
@click.stop="selectField(field.id)"
|
||||
>
|
||||
<!-- Иконка ошибки -->
|
||||
<div
|
||||
v-if="fieldErrors[field.id]"
|
||||
class="tw:absolute tw:-left-3 tw:-top-3 tw:z-20 tw:bg-red-500 tw:text-white tw:rounded-full tw:w-6 tw:h-6 tw:flex tw:items-center tw:justify-center tw:shadow-sm"
|
||||
v-tooltip.top="fieldErrors[field.id]"
|
||||
>
|
||||
<i class="fa fa-exclamation tw:text-xs"></i>
|
||||
</div>
|
||||
|
||||
<!-- Кнопка удаления (справа за пределами блока, видна при выборе) -->
|
||||
<div
|
||||
class="tw:absolute tw:-right-12 tw:top-1/2 tw:-translate-y-1/2 tw:z-10 tw:transition-opacity tw:duration-200"
|
||||
:class="selectedFieldId === field.id ? 'tw:opacity-100' : 'tw:opacity-0 tw:pointer-events-none'"
|
||||
>
|
||||
<Button
|
||||
@click.stop="removeField(field.id)"
|
||||
icon="fa fa-trash"
|
||||
severity="danger"
|
||||
rounded
|
||||
size="small"
|
||||
v-tooltip.right="'Удалить поле'"
|
||||
class="!tw:shadow-md !tw:w-9 !tw:h-9 !tw:p-0 tw:flex tw:items-center tw:justify-center hover:!tw:bg-red-600"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Оверлей для перехвата кликов поверх disabled инпутов -->
|
||||
<div class="tw:absolute tw:inset-0 tw:z-[1] tw:bg-transparent"></div>
|
||||
|
||||
<!-- Предпросмотр поля -->
|
||||
<div class="tw:relative tw:z-0">
|
||||
<FormKit
|
||||
:key="`${field.id}-${field.prefixIcon}-${field.suffixIcon}`"
|
||||
:type="field.$formkit"
|
||||
v-bind="getFieldProps(field)"
|
||||
:name="field.name || `field_${field.id}`"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { FormKit } from '@formkit/vue';
|
||||
import { Button } from 'primevue';
|
||||
import draggable from 'vuedraggable';
|
||||
import { useFormFields } from './composables/useFormFields.js';
|
||||
import { getFieldProps } from './utils/fieldHelpers.js';
|
||||
import { toastBus } from '@/utils/toastHelper';
|
||||
|
||||
// Используем composable для работы с полями
|
||||
const {
|
||||
formFields,
|
||||
selectedFieldId,
|
||||
fieldErrors,
|
||||
selectField,
|
||||
removeField,
|
||||
isFieldNameUnique
|
||||
} = useFormFields();
|
||||
|
||||
function handleDragChange(evt) {
|
||||
if (evt.added) {
|
||||
const addedField = evt.added.element;
|
||||
|
||||
// Проверяем уникальность имени
|
||||
if (!isFieldNameUnique(addedField.name, addedField.id)) {
|
||||
// Удаляем дубликат
|
||||
removeField(addedField.id);
|
||||
|
||||
toastBus.emit('show', {
|
||||
severity: 'error',
|
||||
summary: 'Ошибка добавления',
|
||||
detail: `Поле с именем "${addedField.name}" уже добавлено в форму.`
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
selectField(addedField.id);
|
||||
}
|
||||
}
|
||||
|
||||
function handleBackgroundClick() {
|
||||
// Сбрасываем выбор, если есть выбранный элемент
|
||||
if (selectedFieldId.value) {
|
||||
selectedFieldId.value = null;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.blueprint-bg {
|
||||
background-color: #e2e8f0;
|
||||
background-image: radial-gradient(#cbd5e1 1px, transparent 1px);
|
||||
background-size: 10px 10px;
|
||||
}
|
||||
|
||||
.ghost-field {
|
||||
opacity: 0.5;
|
||||
background-color: #eff6ff;
|
||||
border-color: #93c5fd;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.drag-field {
|
||||
opacity: 1;
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user