Розробка компонента порівняння товарів на Vue.js для 1С-Бітрікс
Стандартний компонент порівняння Бітрікс (bitrix:catalog.compare.buy + bitrix:catalog.compare.result) працює через перезавантаження сторінки: користувач додає товар у порівняння, сторінка оновлюється, лічильник змінюється. На сторінці порівняння — статична таблиця. Все це працювало у 2010 році; сьогодні UX-очікування інші. Vue.js-версія: додавання в порівняння — миттєво, лічильник у шапці оновлюється без перезавантаження, таблиця порівняння вміє приховувати однакові характеристики.
Зберігання списку порівняння
Стандартний Бітрікс зберігає порівняння у сесії PHP ($_SESSION). Для Vue-версії краще localStorage із синхронізацією на сервер для авторизованих користувачів — так список зберігається між сесіями і пристроями.
// stores/compareStore.ts (Pinia)
export const useCompareStore = defineStore('compare', {
state: () => ({
items: [] as number[], // product IDs
}),
actions: {
async add(productId: number) {
if (this.items.includes(productId)) return
if (this.items.length >= 4) {
alert('Можна порівнювати до 4 товарів одночасно')
return
}
this.items.push(productId)
localStorage.setItem('compare_items', JSON.stringify(this.items))
// Синхронізація з сервером для авторизованих
if (isLoggedIn()) {
await fetch('/local/api/compare/add/', {
method: 'POST',
body: JSON.stringify({ product_id: productId }),
})
}
},
remove(productId: number) {
this.items = this.items.filter(id => id !== productId)
localStorage.setItem('compare_items', JSON.stringify(this.items))
},
isInCompare: (state) => (productId: number) => state.items.includes(productId),
},
getters: {
count: (state) => state.items.length,
},
})
При ініціалізації застосунку список завантажується з localStorage (для гостей) або з API (для авторизованих, з мерджем між пристроями).
Кнопка «Порівняти» на картці товару
<!-- CompareButton.vue -->
<template>
<button
:class="['compare-btn', { 'compare-btn--active': isAdded }]"
@click="toggle"
:title="isAdded ? 'Прибрати з порівняння' : 'Додати до порівняння'"
>
<IconScale :filled="isAdded" />
<span>{{ isAdded ? 'У порівнянні' : 'Порівняти' }}</span>
</button>
</template>
<script setup lang="ts">
const props = defineProps<{ productId: number }>()
const store = useCompareStore()
const isAdded = computed(() => store.isInCompare(props.productId))
function toggle() {
isAdded.value ? store.remove(props.productId) : store.add(props.productId)
}
</script>
Кнопка монтується на кожній картці товару. У Бітрікс-шаблоні:
// В template.php картки товару
?>
<div data-compare-btn data-product-id="<?= $arItem['ID'] ?>"></div>
<?php
// Після завантаження сторінки Vue монтує CompareButton у кожен [data-compare-btn]
Лічильник у шапці сайту
<!-- CompareCounter.vue (монтується в header) -->
<template>
<a href="/compare/" class="header-compare">
<IconScale />
<span class="badge" v-if="count > 0">{{ count }}</span>
</a>
</template>
<script setup lang="ts">
const store = useCompareStore()
const count = computed(() => store.count)
</script>
Лічильник реактивно оновлюється через Pinia — жодного глобального EventBus або DOM-маніпуляцій.
Сторінка порівняння
Найскладніша частина — таблиця порівняння. Дані про товари завантажуються з сервера за списком ID:
// В CompareTable.vue
async function loadCompareData(ids: number[]) {
const res = await fetch(`/local/api/compare/?ids=${ids.join(',')}`)
const data = await res.json() // [{id, name, price, specs: {...}}, ...]
products.value = data
buildDiffMap()
}
// Визначаємо однакові характеристики для можливості їх приховати
function buildDiffMap() {
const allSpecs = new Set(products.value.flatMap(p => Object.keys(p.specs)))
diffMap.value = {}
allSpecs.forEach(spec => {
const values = products.value.map(p => p.specs[spec])
diffMap.value[spec] = new Set(values).size > 1 // true = є відмінності
})
}
Фільтр «Показати тільки відмінності» — кнопка, яка приховує рядки, де всі значення однакові:
<template>
<table class="compare-table">
<thead>
<tr>
<th>Характеристика</th>
<th v-for="p in products" :key="p.id">
<ProductCard :product="p" @remove="store.remove(p.id)" />
</th>
</tr>
</thead>
<tbody>
<tr
v-for="spec in visibleSpecs"
:key="spec"
:class="{ 'row--different': diffMap[spec] }"
>
<td class="spec-name">{{ specLabels[spec] }}</td>
<td v-for="p in products" :key="p.id">{{ p.specs[spec] ?? '—' }}</td>
</tr>
</tbody>
</table>
</template>
<script setup lang="ts">
const showOnlyDiff = ref(false)
const visibleSpecs = computed(() =>
showOnlyDiff.value
? Object.keys(diffMap.value).filter(k => diffMap.value[k])
: Object.keys(diffMap.value)
)
</script>
PHP-контролер даних порівняння
// CompareController.php
public function getAction(string $ids): array {
$idArr = array_filter(array_map('intval', explode(',', $ids)));
if (!$idArr) return [];
$products = [];
$res = CIBlockElement::GetList(
[],
['ID' => $idArr, 'IBLOCK_ID' => CATALOG_IBLOCK_ID, 'ACTIVE' => 'Y'],
false, false,
['ID', 'NAME', 'PREVIEW_PICTURE', 'DETAIL_PAGE_URL', 'CATALOG_PRICE_1_']
);
while ($el = $res->GetNext()) {
// Отримуємо властивості для порівняння
$props = [];
$prRes = CIBlockElement::GetProperty(CATALOG_IBLOCK_ID, $el['ID'], [], ['COMPARE' => 'Y']);
while ($pr = $prRes->Fetch()) {
$props[$pr['CODE']] = $pr['VALUE'];
}
$products[] = [
'id' => $el['ID'],
'name' => $el['NAME'],
'price' => $el['CATALOG_PRICE_1_'],
'image' => $el['PREVIEW_PICTURE'] ? CFile::GetPath($el['PREVIEW_PICTURE']) : null,
'url' => $el['DETAIL_PAGE_URL'],
'specs' => $props,
];
}
return $products;
}
Властивості, що беруть участь у порівнянні, позначаються прапором COMPARE = 'Y' у налаштуваннях інфоблоку — стандартний механізм Бітрікс.
Зафіксована перша колонка на мобільному
Таблиця порівняння на мобільному — горизонтальне прокручування. Перша колонка (назви характеристик) має бути зафіксована:
.compare-table {
display: grid;
grid-template-columns: 180px repeat(4, 1fr);
overflow-x: auto;
}
.compare-table td:first-child,
.compare-table th:first-child {
position: sticky;
left: 0;
background: white;
z-index: 1;
}
CSS Grid з position: sticky — надійніший підхід, ніж display: table з фіксованими колонками.
Терміни
| Обсяг | Що входить | Термін |
|---|---|---|
| Базове порівняння | Кнопки + лічильник + таблиця | 1–2 тижні |
| Повноцінний компонент | + diff-фільтр, localStorage, синхронізація | 2–3 тижні |
| + Мобільний адаптив, persist для ЛК | + sticky колонка, збереження для юзера | +1 тиждень |
Порівняння товарів — інструмент для покупців, які вибирають серед схожих позицій. Правильно реалізоване порівняння допомагає прийняти рішення на сайті, а не піти на агрегатор.







