Розробка покрокової форми заявки на 1С-Бітрікс

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Пропоновані послуги
Показано 1 з 1 послугУсі 1626 послуг
Розробка покрокової форми заявки на 1С-Бітрікс
Середня
~1-2 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Розробка на базі Бітрікс, Бітрікс24, 1С для компанії Development of an Online
    585
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Розробка на базі 1С Підприємство для компанії МИРСАНБЕЛ
    751
  • image_crm_dolbimby_434_0.webp
    Розробка сайту на CRM Бітрікс24 для компанії DOLBIMBY
    657
  • image_crm_technotorgcomplex_453_0.webp
    Розробка на базі Бітрікс24 для компанії ТЕХНОТОРГКОМПЛЕКС
    989

Розробка покрокової форми заявки на 1С-Бітрікс

Довга форма на одній сторінці вбиває конверсію. Коли користувач бачить одразу 10+ полів — закриває вкладку. Розбивка на кроки вирішує це: на кожному екрані 2–4 поля, здається простіше, прогрес видно. Покрокова форма на 1С-Бітрікс — це кастомний компонент з клієнтською навігацією між кроками, валідацією на кожному кроці та фінальним надсиланням на сервер.

Архітектура компонента

Форма зберігає стан повністю на клієнті (JS-об'єкт), надсилає дані єдиним запитом після останнього кроку. Сервер отримує повний набір даних.

Конфігурація кроків — у параметрах компонента (PHP-масив) або в адміністративному інтерфейсі через інфоблок.

// /local/components/local/multistep.form/class.php
namespace Local\Form;

class MultistepFormComponent extends \CBitrixComponent
{
    public function executeComponent(): void
    {
        // Конфігурація кроків
        $this->arResult['STEPS'] = $this->arParams['STEPS'] ?? $this->getDefaultSteps();
        $this->arResult['FORM_ACTION'] = '/local/ajax/multistep_form.php';
        $this->arResult['SESSID'] = bitrix_sessid();

        $this->includeComponentTemplate();
    }

    private function getDefaultSteps(): array
    {
        return [
            [
                'id'     => 'step_personal',
                'title'  => 'Ваші дані',
                'fields' => [
                    ['name' => 'name',  'label' => 'Ім\'я',    'type' => 'text',  'required' => true],
                    ['name' => 'phone', 'label' => 'Телефон',  'type' => 'tel',   'required' => true],
                    ['name' => 'email', 'label' => 'Email',    'type' => 'email', 'required' => false],
                ],
            ],
            [
                'id'     => 'step_details',
                'title'  => 'Деталі заявки',
                'fields' => [
                    ['name' => 'service', 'label' => 'Послуга', 'type' => 'select', 'options' => ['Консультація', 'Аудит', 'Розробка']],
                    ['name' => 'budget',  'label' => 'Бюджет',  'type' => 'radio',  'options' => ['до 100к', '100–500к', 'від 500к']],
                ],
            ],
            [
                'id'     => 'step_message',
                'title'  => 'Коментар',
                'fields' => [
                    ['name' => 'message', 'label' => 'Опишіть завдання', 'type' => 'textarea', 'required' => false],
                ],
            ],
        ];
    }
}

Клієнтська логіка

class MultistepForm {
    constructor(formEl) {
        this.form     = formEl;
        this.steps    = Array.from(formEl.querySelectorAll('.form-step'));
        this.current  = 0;
        this.formData = {};

        this.bindEvents();
        this.showStep(0);
    }

    bindEvents() {
        this.form.querySelectorAll('.btn-next').forEach(btn =>
            btn.addEventListener('click', () => this.tryNext())
        );
        this.form.querySelectorAll('.btn-prev').forEach(btn =>
            btn.addEventListener('click', () => this.prev())
        );
        this.form.addEventListener('submit', e => {
            e.preventDefault();
            this.submit();
        });
    }

    tryNext() {
        if (!this.validateCurrentStep()) return;
        this.collectCurrentStepData();

        if (this.current < this.steps.length - 1) {
            this.showStep(this.current + 1);
        }
    }

    prev() {
        if (this.current > 0) {
            this.showStep(this.current - 1);
        }
    }

    showStep(index) {
        this.steps.forEach((step, i) => {
            step.classList.toggle('active', i === index);
        });
        this.current = index;
        this.updateProgress();
    }

    validateCurrentStep(): boolean {
        const stepEl = this.steps[this.current];
        let valid    = true;

        stepEl.querySelectorAll('[required]').forEach(field => {
            const error = stepEl.querySelector(`[data-error-for="${field.name}"]`);
            if (!field.value.trim()) {
                valid = false;
                field.classList.add('error');
                if (error) error.textContent = 'Це поле обов\'язкове';
            } else {
                field.classList.remove('error');
                if (error) error.textContent = '';
            }
        });

        // Валідація телефону
        const phoneField = stepEl.querySelector('[name="phone"]');
        if (phoneField && phoneField.value) {
            const phoneRegex = /^(\+7|7|8)?[\s-]?\(?\d{3}\)?[\s-]?\d{3}[\s-]?\d{2}[\s-]?\d{2}$/;
            if (!phoneRegex.test(phoneField.value.replace(/\s/g, ''))) {
                valid = false;
                phoneField.classList.add('error');
            }
        }

        return valid;
    }

    collectCurrentStepData() {
        const stepEl = this.steps[this.current];
        stepEl.querySelectorAll('input, select, textarea').forEach(field => {
            if (field.type === 'radio' || field.type === 'checkbox') {
                if (field.checked) this.formData[field.name] = field.value;
            } else {
                this.formData[field.name] = field.value;
            }
        });
    }

    updateProgress() {
        const percent = ((this.current) / (this.steps.length - 1)) * 100;
        const bar     = this.form.querySelector('.progress-bar-fill');
        if (bar) bar.style.width = Math.min(100, percent) + '%';

        const label = this.form.querySelector('.progress-label');
        if (label) label.textContent = `Крок ${this.current + 1} з ${this.steps.length}`;
    }

    async submit() {
        this.collectCurrentStepData();

        const submitBtn = this.form.querySelector('[type="submit"]');
        submitBtn.disabled = true;
        submitBtn.textContent = 'Надсилаємо...';

        try {
            const response = await fetch(this.form.dataset.action, {
                method: 'POST',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({
                    ...this.formData,
                    sessid: this.form.dataset.sessid,
                }),
            });

            const result = await response.json();
            if (result.success) {
                this.showSuccessScreen();
            } else {
                this.showError(result.error || 'Помилка надсилання');
                submitBtn.disabled = false;
                submitBtn.textContent = 'Надіслати';
            }
        } catch (e) {
            this.showError('Помилка з\'єднання. Спробуйте пізніше.');
            submitBtn.disabled = false;
        }
    }

    showSuccessScreen() {
        this.form.innerHTML = `
            <div class="form-success">
                <h3>Дякуємо! Ми зв'яжемося з вами протягом години.</h3>
            </div>
        `;
    }

    showError(message) {
        let errEl = this.form.querySelector('.form-global-error');
        if (!errEl) {
            errEl = document.createElement('div');
            errEl.className = 'form-global-error';
            this.steps[this.current].prepend(errEl);
        }
        errEl.textContent = message;
    }
}

document.querySelectorAll('.multistep-form').forEach(el => new MultistepForm(el));

Серверний обробник

// /local/ajax/multistep_form.php
\Bitrix\Main\Loader::includeModule('crm');

$data    = json_decode(file_get_contents('php://input'), true);
$sessid  = $data['sessid'] ?? '';

if (!check_bitrix_sessid($sessid)) {
    http_response_code(403);
    echo json_encode(['error' => 'Forbidden']);
    exit;
}

$name    = htmlspecialchars(trim($data['name'] ?? ''));
$phone   = htmlspecialchars(trim($data['phone'] ?? ''));
$email   = htmlspecialchars(trim($data['email'] ?? ''));
$service = htmlspecialchars($data['service'] ?? '');
$budget  = htmlspecialchars($data['budget'] ?? '');
$message = htmlspecialchars($data['message'] ?? '');

if (empty($name) || empty($phone)) {
    echo json_encode(['error' => 'Обов\'язкові поля не заповнені']);
    exit;
}

$comments = implode("\n", array_filter([
    $service ? "Послуга: {$service}" : '',
    $budget  ? "Бюджет: {$budget}"   : '',
    $message ? "Коментар: {$message}" : '',
]));

$lead = new \CCrmLead(false);
$leadId = $lead->Add([
    'TITLE'   => 'Заявка з сайту — ' . $name,
    'NAME'    => $name,
    'PHONE'   => [['VALUE' => $phone, 'VALUE_TYPE' => 'WORK']],
    'EMAIL'   => [['VALUE' => $email, 'VALUE_TYPE' => 'WORK']],
    'SOURCE_ID' => 'WEB',
    'COMMENTS'  => $comments,
]);

echo json_encode(['success' => (bool)$leadId]);

Збереження прогресу

Для довгих форм (5+ кроків) — зберігати заповнені дані в localStorage, щоб не втратити при випадковому закритті:

// Після кожного кроку
localStorage.setItem('form_progress', JSON.stringify({
    step: this.current,
    data: this.formData,
    savedAt: Date.now(),
}));

// При ініціалізації — відновити, якщо не старше 30 хвилин
const saved = JSON.parse(localStorage.getItem('form_progress') || 'null');
if (saved && Date.now() - saved.savedAt < 1800000) {
    this.formData = saved.data;
    this.showStep(saved.step);
}

Терміни розробки

Варіант Склад Термін
Базова покрокова форма 2–3 кроки, валідація, лід у CRM 2–4 дні
З кастомними полями Вибір, радіо, файл, динамічні поля 4–7 днів
Повний компонент Конфігуровані кроки, A/B, аналітика, збереження 8–12 днів