Розробка форми підбору товару 1С-Бітрікс
Форма підбору товару — інтерактивний помічник, який ставить користувачеві запитання про його потреби та на виході пропонує конкретні товари з каталогу. Застосовується там, де асортимент великий або товари технічно складні: вибір ноутбука, шин, фарби, промислового обладнання. По суті це рекомендаційна система, замаскована під опитувальник. Ключове технічне завдання — алгоритм зіставлення відповідей з товарами в інфоблоці.
Два підходи до підбору
1. Пряма фільтрація. Кожна відповідь користувача = фільтр за властивістю інфоблоку. Підсумок = перетин усіх фільтрів. Просто, передбачувано, але погано працює, коли користувач дав «неточні» відповіді — результат може бути порожнім.
2. Scoring (балова система). Кожен варіант відповіді додає бали певним товарам або категоріям. Товари ранжуються за сумою балів. Краще для рекомендацій, складніше в налаштуванні.
Для більшості каталогів — комбінація: обов'язкові фільтри (наприклад, бюджет) + скоринг за додатковими критеріями.
Структура даних: маппінг запитань на властивості
// Конфігурація підбирача (зберігається в b_option або HL-блоці)
$selectorConfig = [
'iblock_id' => CATALOG_IBLOCK_ID,
'questions' => [
[
'id' => 'q_budget',
'text' => 'Ваш бюджет',
'type' => 'select',
'filter_mode' => 'range', // Тип фільтрації
'options' => [
['value' => 'low', 'label' => 'до 30 000 ₴', 'filter' => ['<CATALOG_PRICE_1' => 30000]],
['value' => 'mid', 'label' => '30–60 000 ₴', 'filter' => ['>=CATALOG_PRICE_1' => 30000, '<CATALOG_PRICE_1' => 60000]],
['value' => 'high', 'label' => 'від 60 000 ₴', 'filter' => ['>=CATALOG_PRICE_1' => 60000]],
],
],
[
'id' => 'q_purpose',
'text' => 'Для яких завдань',
'type' => 'radio',
'filter_mode' => 'property',
'property_code' => 'PURPOSE', // Код властивості в інфоблоці
'options' => [
['value' => 'work', 'label' => 'Робота / офіс', 'property_value' => 'WORK'],
['value' => 'gaming', 'label' => 'Ігри', 'property_value' => 'GAMING'],
['value' => 'study', 'label' => 'Навчання', 'property_value' => 'STUDY'],
],
],
[
'id' => 'q_weight',
'text' => 'Чи важлива вага пристрою?',
'type' => 'radio',
'filter_mode' => 'scoring',
'options' => [
['value' => 'yes', 'label' => 'Так, беру з собою', 'scores' => ['lightweight' => 10]],
['value' => 'no', 'label' => 'Ні, стаціонарний', 'scores' => ['performance' => 5]],
],
],
],
];
Алгоритм фільтрації та скорингу
class ProductSelector
{
private array $config;
public function findProducts(array $answers): array
{
$hardFilters = ['IBLOCK_ID' => $this->config['iblock_id'], 'ACTIVE' => 'Y'];
$scoringTags = []; // tag => score
foreach ($this->config['questions'] as $question) {
$answer = $answers[$question['id']] ?? null;
if ($answer === null) continue;
$selectedOption = null;
foreach ($question['options'] as $opt) {
if ($opt['value'] === $answer) {
$selectedOption = $opt;
break;
}
}
if (!$selectedOption) continue;
switch ($question['filter_mode']) {
case 'range':
case 'property':
// Додати до жорстких фільтрів
$hardFilters = array_merge($hardFilters, $selectedOption['filter'] ?? []);
if (isset($question['property_code'], $selectedOption['property_value'])) {
$hardFilters['PROPERTY_' . $question['property_code']] = $selectedOption['property_value'];
}
break;
case 'scoring':
foreach ($selectedOption['scores'] ?? [] as $tag => $score) {
$scoringTags[$tag] = ($scoringTags[$tag] ?? 0) + $score;
}
break;
}
}
// Отримати товари за жорсткими фільтрами
$result = \CIBlockElement::GetList(
['SORT' => 'ASC'],
$hardFilters,
false,
['nPageSize' => 20],
['ID', 'NAME', 'DETAIL_PAGE_URL', 'PREVIEW_PICTURE', 'CATALOG_PRICE_1', 'PROPERTY_TAGS']
);
$products = [];
while ($item = $result->GetNext()) {
$score = 0;
// Застосувати скоринг за тегами товару
$productTags = explode(',', $item['PROPERTY_TAGS_VALUE'] ?? '');
foreach ($scoringTags as $tag => $tagScore) {
if (in_array(trim($tag), array_map('trim', $productTags))) {
$score += $tagScore;
}
}
$item['_SCORE'] = $score;
$products[] = $item;
}
// Сортувати за скорингом (спадання)
usort($products, fn($a, $b) => $b['_SCORE'] <=> $a['_SCORE']);
return $products;
}
}
Клієнтська частина
class ProductSelectorUI {
constructor(configJson) {
this.config = configJson;
this.answers = {};
this.step = 0;
this.renderStep();
}
renderStep() {
const question = this.config.questions[this.step];
const container = document.getElementById('selector-step');
container.innerHTML = `
<h3>${question.text}</h3>
<div class="selector-options">
${question.options.map(opt => `
<button class="selector-option" data-value="${opt.value}" data-question="${question.id}">
${opt.label}
</button>
`).join('')}
</div>
`;
container.querySelectorAll('.selector-option').forEach(btn => {
btn.addEventListener('click', e => {
const questionId = e.target.dataset.question;
const value = e.target.dataset.value;
this.selectAnswer(questionId, value);
});
});
this.updateProgress();
}
selectAnswer(questionId, value) {
this.answers[questionId] = value;
if (this.step < this.config.questions.length - 1) {
this.step++;
this.renderStep();
} else {
this.fetchResults();
}
}
async fetchResults() {
document.getElementById('selector-step').innerHTML = '<div class="loading">Підбираємо для вас...</div>';
const response = await fetch('/local/ajax/product_selector.php', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({answers: this.answers, sessid: BX.bitrix_sessid()}),
});
const data = await response.json();
this.renderResults(data.products);
}
renderResults(products) {
const container = document.getElementById('selector-results');
if (products.length === 0) {
container.innerHTML = '<p>На жаль, за вашими критеріями нічого не знайдено. <a href="/catalog/">Переглянути весь каталог</a></p>';
return;
}
container.innerHTML = products.slice(0, 6).map(p => `
<div class="product-card">
<img src="${p.PREVIEW_PICTURE?.SRC || ''}" alt="${p.NAME}">
<h4><a href="${p.DETAIL_PAGE_URL}">${p.NAME}</a></h4>
<div class="price">${p.CATALOG_PRICE_1} ₴</div>
<a href="${p.DETAIL_PAGE_URL}" class="btn">Детальніше</a>
</div>
`).join('');
document.getElementById('selector-container').style.display = 'none';
document.getElementById('selector-results-container').style.display = 'block';
}
updateProgress() {
const bar = document.getElementById('selector-progress');
if (bar) bar.style.width = ((this.step / this.config.questions.length) * 100) + '%';
}
}
Результат і лід
Якщо підходящих товарів немає — форма пропонує залишити контакт для консультації:
if (empty($products)) {
echo json_encode([
'products' => [],
'show_contact_form' => true,
'message' => 'Ми підберемо для вас індивідуально',
]);
exit;
}
Якщо користувач залишає контакт після підбору — зберігаємо обрані параметри в коментарі ліда (маркетингова цінність: менеджер одразу знає, що шукає клієнт).
Терміни розробки
| Варіант | Склад | Термін |
|---|---|---|
| Пряма фільтрація | Запитання → фільтри → список товарів | 4–6 днів |
| Зі скорингом | + Балова система, ранжування | 6–10 днів |
| З конфігуратором | Управління запитаннями через адміністративну панель | 10–15 днів |







