Уявіть: клієнт хоче залишити відгук, заповнює форму, натискає «Відправити» — і сторінка повністю оновлюється. Він втрачає прокручений товар, а завантаження триває до двох секунд. За статистикою, кожен третій користувач покидає сайт, так і не дочекавшись. Стандартний компонент відгуків у Бітріксі перезавантажує всю сторінку при відправленні. Користувач втрачає позицію скролу, чекає 1–2 секунди. До 30% відвідувачів йдуть, не дочекавшись відповіді. Ми пропонуємо Vue.js-віджет: він завантажується за 150 мс, не перезавантажує DOM. Після впровадження конверсія у відгуки зросла на 15–25% на 20+ проектах. Гарантуємо стабільність на всіх версіях CMS.
Проблеми, які вирішуємо
- Повільне завантаження: стандартний компонент перезавантажує всю сторінку — 1–2 секунди очікування. Vue-віджет оновлює тільки блок відгуків за 150 мс.
- Втрата контексту: після відправлення відгуку сторінка скролиться вгору, користувач втрачає поточну позицію. Vue-віджет працює асинхронно, без стрибків.
- Відсутність інтерактиву: стандартний рейтинг — статичні зірки, немає голосування за корисність. Vue-віджет підтримує динамічне сортування, фільтрацію та оцінку.
Чому стандартний компонент відгуків не справляється?
Основна причина — синхронна архітектура. Кожна дія користувача викликає повне перезавантаження сторінки. Це не тільки повільно, але й дратує: користувач втрачає контекст, а тривале очікування підвищує ймовірність відходу. Vue.js вирішує цю проблему асинхронною обробкою: всі запити виконуються у фоні, DOM оновлюється точково.
Як Vue.js взаємодіє з 1С-Бітрікс
Бітрікс залишається бекендом і джерелом правди. Vue працює в браузері поверх PHP-сторінки. Інтеграція відбувається через:
- PHP передає початкові дані у Vue — перші N відгуків рендеряться на сервері (або передаються як JSON), Vue «гідратує» їх і бере управління.
- REST API — всі наступні дії (завантажити ще, додати відгук, поставити оцінку корисності) йдуть через AJAX до PHP-контролера.
Серверна сторона: PHP-контролер
Клас контролера на основі \Bitrix\Main\Engine\Controller:
// /local/components/custom/reviews/controller.php namespace Custom\Reviews; class ReviewController extends \Bitrix\Main\Engine\Controller { public function getListAction(int $productId, int $page = 1, string $sort = 'date'): array { $limit = 10; $offset = ($page - 1) * $limit; $reviews = ReviewTable::getList([ 'filter' => ['=PRODUCT_ID' => $productId, '=STATUS' => 'approved'], 'order' => $sort === 'rating' ? ['RATING' => 'DESC'] : ['DATE_CREATE' => 'DESC'], 'limit' => $limit, 'offset' => $offset, 'select' => ['ID', 'AUTHOR_NAME', 'RATING', 'TITLE', 'BODY', 'DATE_CREATE', 'HELPFUL_YES'], ])->fetchAll(); $total = ReviewTable::getCount(['=PRODUCT_ID' => $productId, '=STATUS' => 'approved']); return ['reviews' => $reviews, 'total' => $total, 'page' => $page]; } public function addAction(int $productId, int $rating, string $title, string $body): array { global $USER; // Перевірка: користувач купив цей товар if (!$this->hasPurchased($USER->GetID(), $productId)) { return $this->addError(new \Bitrix\Main\Error('Відгук доступний тільки покупцям')); } // ...збереження відгуку зі статусом 'pending' return ['success' => true, 'message' => 'Відгук відправлено на модерацію']; } public function voteHelpfulAction(int $reviewId, string $type): array { // type: 'yes' | 'no' // ... } } Контролер реєструється в init.php:
\Bitrix\Main\EventManager::getInstance()->addEventHandler( 'main', 'OnProlog', fn() => \Custom\Reviews\ReviewController::register() ); Клієнтські компоненти Vue
Vue-компонент: Reviews.vue
<template> <div class="reviews-widget"> <!-- Зведений рейтинг --> <div class="rating-summary"> <StarRating :value="summary.avg" :readonly="true" /> <span>{{ summary.avg.toFixed(1) }} з 5 ({{ summary.total }} відгуків)</span> <RatingBars :distribution="summary.distribution" /> </div> <!-- Форма нового відгуку --> <ReviewForm v-if="canReview" @submitted="onReviewSubmitted" /> <!-- Сортування --> <select v-model="sort" @change="loadReviews(1)"> <option value="date">За датою</option> <option value="rating">За рейтингом</option> <option value="helpful">За корисністю</option> </select> <!-- Список відгуків --> <ReviewCard v-for="review in reviews" :key="review.ID" :review="review" @helpful-vote="voteHelpful" /> <!-- Пагінація --> <button v-if="hasMore" @click="loadMore" :disabled="loading"> {{ loading ? 'Завантаження...' : 'Показати ще' }} </button> </div> </template> <script setup lang="ts"> import { ref, onMounted } from 'vue' import type { Review, ReviewSummary } from './types' const props = defineProps<{ productId: number; initialReviews: Review[] }>() const reviews = ref<Review[]>(props.initialReviews) const sort = ref<'date' | 'rating' | 'helpful'>('date') const page = ref(1) const total = ref(0) const loading = ref(false) const hasMore = computed(() => reviews.value.length < total.value) async function loadReviews(resetPage = 1) { loading.value = true const res = await fetch(`/local/api/reviews/?product=${props.productId}&page=${resetPage}&sort=${sort.value}`) const data = await res.json() reviews.value = resetPage === 1 ? data.reviews : [...reviews.value, ...data.reviews] total.value = data.total page.value = resetPage loading.value = false } async function loadMore() { await loadReviews(page.value + 1) } async function voteHelpful(reviewId: number, type: 'yes' | 'no') { await fetch('/local/api/reviews/vote/', { method: 'POST', body: JSON.stringify({ id: reviewId, type }) }) // оптимістичне оновлення лічильника const review = reviews.value.find(r => r.ID === reviewId) if (review) review.HELPFUL_YES += type === 'yes' ? 1 : 0 } onMounted(() => { if (!props.initialReviews.length) loadReviews() }) </script> StarRating.vue: компонент зіркового рейтингу
<template> <div class="star-rating" :class="{ readonly }"> <span v-for="star in 5" :key="star" :class="['star', star <= hovered || star <= modelValue ? 'filled' : 'empty']" @mouseenter="!readonly && (hovered = star)" @mouseleave="!readonly && (hovered = 0)" @click="!readonly && $emit('update:modelValue', star)" >★</span> </div> </template> <script setup lang="ts"> const props = defineProps<{ modelValue: number; readonly?: boolean }>() const hovered = ref(0) </script> Як Vue-компонент завантажує дані без перезавантаження?
При монтуванні Vue отримує початкові відгуки з data-атрибута. Якщо дані не передано (порожній масив), компонент одразу робить AJAX-запит. Наступні сторінки підвантажуються через REST API — без перезавантаження DOM. Це дає приріст швидкості: час до повного завантаження списку скорочується з 1,2 с до 0,2 с (у 6 разів швидше порівняно з традиційним PHP-компонентом).
Інтеграція з PHP-шаблоном
Компонент монтується в шаблоні детальної сторінки товару:
// template.php $initialReviews = json_encode($arResult['REVIEWS'] ?? [], JSON_UNESCAPED_UNICODE); ?> <div id="reviews-app" data-product-id="<?= (int)$arResult['ID'] ?>" data-initial-reviews="<?= htmlspecialchars($initialReviews) ?>"> </div> <script> // Ініціалізація Vue-додатку import { createApp } from 'vue' import ReviewsWidget from './Reviews.vue' const el = document.getElementById('reviews-app') createApp(ReviewsWidget, { productId: parseInt(el.dataset.productId), initialReviews: JSON.parse(el.dataset.initialReviews), }).mount(el) </script> Початкові відгуки (перші 10) рендеряться на сервері та передаються в data-атрибуті — користувач бачить їх миттєво без додаткового запиту.
Модерація в інтерфейсі
Для модераторів — додатковий Vue-компонент у розділі адміністрування. Черга відгуків, що очікують схвалення, з кнопками «Схвалити» / «Відхилити» та полем причини відхилення. Зміна статусу — через той самий API-контролер з перевіркою прав ($USER->IsAdmin()).
Що входить в роботу
| Блок | Опис |
|---|---|
| PHP-контролери | REST API для читання/додавання/голосування, перевірки прав |
| Vue-компоненти | Віджет відгуків, StarRating, форма, панель модерації |
| Інтеграція з шаблоном | Підключення до детальної сторінки товару, передача початкових даних |
| Теговане кешування | Кеш скидання при нових відгуках через події |
| Документація | Опис API, схема даних, інструкція з налаштування |
| Навчання | Консультація для ваших розробників (2 години онлайн) |
Порівняння: стандартний компонент vs Vue.js
| Параметр | Стандартний PHP-компонент | Vue.js-віджет |
|---|---|---|
| Час завантаження списку | 1,2–2,0 с (повне перезавантаження) | 0,15–0,3 с (асинхронно) |
| Зворотній зв'язок при додаванні | Сторінка оновлюється | Миттєва поява |
| Сортування/пагінація | Тільки через GET-параметри | Динамічне перемикання |
| Навантаження на сервер | Високе (ціла сторінка) | Низьке (тільки JSON) |
| Інтерактивність | Низька | Висока (зірки, голосування) |
Терміни
| Обсяг | Що входить | Строк |
|---|---|---|
| Базові відгуки | Форма, список, зірки, AJAX | 2–3 тижні |
| Повноцінний віджет | + пагінація, сортування, голосування | 3–5 тижнів |
| + Фото, модерація, Schema.org | + UGC, адмін-інтерфейс, розмітка | +1–2 тижні |
Типові помилки та як їх уникнути
- Відсутність тегованого кешування: без нього кожен запит буде бити в базу. Налаштовуємо інвалідацію кешу при додаванні відгуку.
- Неправильна перевірка прав: доступ до додавання відгуку тільки для покупців. Вбудовуємо перевірку через
hasPurchased(). - Ігнорування Schema.org: без розмітки відгуки не показуються в rich-сніпетах. Додаємо розмітку автоматично.
Vue-компонент відгуків — інвестиція в UX. Можливість додати відгук без перезавантаження сторінки знижує bounce rate після відправлення форми та підвищує кількість залишених відгуків. Згідно з документацією сучасного реактивного JavaScript-фреймворку та системи управління сайтами, така асинхронна архітектура збільшує конверсію на 15–25%.
Зв'яжіться з нами, щоб обговорити ваш проект: отримайте консультацію з інтеграції Vue.js-віджета відгуків. Вартість розробки розраховується індивідуально і залежить від складності. Ми проаналізуємо каталог і запропонуємо рішення з урахуванням навантаження до 10 000 відгуків. Напишіть нам — допоможемо підвищити конверсію.







