Разработка формы подбора товара 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 дней |







