Розробка компонента обраного на Vue.js для 1С-Бітрікс

Наша компанія займається розробкою, підтримкою та обслуговуванням рішень на Бітрікс та Бітрікс24 будь-якої складності. Від простих односторінкових сайтів до складних інтернет-магазинів, CRM систем з інтеграцією 1С та телефонії. Досвід розробників підтверджено сертифікатами від вендора.
Пропоновані послуги
Показано 1 з 1 послугУсі 1626 послуг
Розробка компонента обраного на Vue.js для 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

Розробка компонента вибраного на Vue.js для 1С-Бітрікс

Функціонал вибраного (wishlist) у Бітрікс нерідко реалізується як окремий список порівняння або як кастомний кошик з особливим статусом. Обидва підходи — милиці. Vue.js-компонент вибраного будується правильно: окрема сутність, персистентне зберігання для авторизованих, guest-режим через localStorage, миттєва реакція UI без перезавантаження сторінки.

Що важливо спроектувати до написання коду

Для кого зберігається вибране? Якщо для неавторизованих — тільки localStorage. Якщо хочемо, щоб вибране зберігалося при логіні — потрібна база даних і механізм мерджу: при авторизації гостьове вибране з localStorage об'єднується з серверним.

Скільки списків? Простий варіант — один список на користувача. Просунутий — кілька іменованих вішлістів («Подарунки на день народження», «Хочу навесні»). Другий варіант значно складніший — з окремим інтерфейсом управління списками.

Що робити при додаванні товару, якого немає в наявності? Сповіщати, коли з'явиться — це окремий функціонал «Підписка на наявність», пов'язаний з вибраним.

Структура бази даних

CREATE TABLE b_user_wishlist (
    ID          SERIAL PRIMARY KEY,
    USER_ID     INT NOT NULL REFERENCES b_user(ID),
    LIST_NAME   VARCHAR(255) DEFAULT 'default',
    DATE_CREATE TIMESTAMP DEFAULT NOW()
);

CREATE TABLE b_wishlist_items (
    ID          SERIAL PRIMARY KEY,
    LIST_ID     INT NOT NULL REFERENCES b_user_wishlist(ID),
    PRODUCT_ID  INT NOT NULL,
    DATE_ADD    TIMESTAMP DEFAULT NOW(),
    UNIQUE (LIST_ID, PRODUCT_ID)
);

Для простого варіанту (один список) b_user_wishlist можна не створювати — достатньо таблиці з USER_ID і PRODUCT_ID.

Pinia store для вибраного

// stores/wishlistStore.ts
export const useWishlistStore = defineStore('wishlist', () => {
    const items    = ref<number[]>([])
    const loading  = ref<Set<number>>(new Set())

    // Завантаження при ініціалізації
    async function init() {
        if (isLoggedIn()) {
            const data = await api.get('/local/api/wishlist/')
            items.value = data.map((i: any) => i.product_id)
        } else {
            const stored = localStorage.getItem('wishlist')
            items.value  = stored ? JSON.parse(stored) : []
        }
    }

    async function toggle(productId: number) {
        if (loading.value.has(productId)) return

        loading.value.add(productId)
        const wasAdded = items.value.includes(productId)

        // Оптимістичне оновлення
        if (wasAdded) {
            items.value = items.value.filter(id => id !== productId)
        } else {
            items.value.push(productId)
        }

        // Персист
        if (isLoggedIn()) {
            try {
                await api.post('/local/api/wishlist/' + (wasAdded ? 'remove' : 'add') + '/', { product_id: productId })
            } catch (e) {
                // Відкат при помилці
                if (wasAdded) items.value.push(productId)
                else items.value = items.value.filter(id => id !== productId)
            }
        } else {
            localStorage.setItem('wishlist', JSON.stringify(items.value))
        }

        loading.value.delete(productId)
    }

    const isInWishlist = (id: number) => items.value.includes(id)
    const count        = computed(() => items.value.length)

    return { items, loading, init, toggle, isInWishlist, count }
})

Оптимістичне оновлення (Optimistic UI) — спочатку змінюємо UI, потім відправляємо запит. При помилці відкочуємо. Користувач бачить миттєву реакцію.

Кнопка вибраного на картці товару

<!-- WishlistButton.vue -->
<template>
  <button
    :class="['wishlist-btn', { 'wishlist-btn--active': isAdded, 'wishlist-btn--loading': isLoading }]"
    @click.prevent="handleToggle"
    :aria-label="isAdded ? 'Прибрати з вибраного' : 'У вибране'"
  >
    <HeartIcon :filled="isAdded" />
  </button>
</template>

<script setup lang="ts">
const props  = defineProps<{ productId: number }>()
const store  = useWishlistStore()

const isAdded   = computed(() => store.isInWishlist(props.productId))
const isLoading = computed(() => store.loading.has(props.productId))

async function handleToggle() {
    await store.toggle(props.productId)
    // Показати toast: "Додано до вибраного" / "Видалено"
    showToast(isAdded.value ? 'Додано до вибраного' : 'Видалено з вибраного')
}
</script>

Toast-сповіщення — ненав'язливі повідомлення в куті екрана, які зникають через 3 секунди. Реалізуються через окремий Toast-компонент або невелику бібліотеку (vue-toastification, vuedraggable).

Сторінка вибраного

<!-- WishlistPage.vue -->
<template>
  <div class="wishlist-page">
    <h1>Вибране <span class="count">{{ products.length }}</span></h1>

    <div v-if="!products.length" class="wishlist-empty">
      <p>Список вибраного порожній</p>
      <RouterLink to="/catalog/">Перейти до каталогу</RouterLink>
    </div>

    <div v-else class="wishlist-grid">
      <ProductCard
        v-for="product in products"
        :key="product.id"
        :product="product"
        :show-wishlist-btn="true"
      />
    </div>

    <!-- Додати всі в кошик -->
    <button v-if="availableProducts.length" @click="addAllToCart">
      Додати в кошик доступні ({{ availableProducts.length }})
    </button>
  </div>
</template>

<script setup lang="ts">
const store    = useWishlistStore()
const products = ref([])

onMounted(async () => {
    if (!store.items.length) return
    const res   = await fetch(`/local/api/wishlist/products/?ids=${store.items.join(',')}`)
    products.value = await res.json()
})

const availableProducts = computed(() => products.value.filter(p => p.available))

async function addAllToCart() {
    const cartStore = useCartStore()
    for (const product of availableProducts.value) {
        await cartStore.add(product.id, 1)
    }
    showToast(`Додано ${availableProducts.value.length} товарів у кошик`)
}
</script>

Мердж guest → user при авторизації

При логіні користувача викликається серверний метод мерджу:

// WishlistController.php
public function mergeGuestAction(array $guestIds): array {
    global $USER;
    $userId = (int) $USER->GetID();

    foreach ($guestIds as $productId) {
        // Додаємо тільки якщо ще немає
        WishlistItemTable::merge(['USER_ID' => $userId, 'PRODUCT_ID' => (int)$productId]);
    }

    // Повертаємо повний актуальний список
    return WishlistItemTable::getProductIdsByUser($userId);
}

У JS: при події успішного логіну (або при ініціалізації авторизованого користувача):

async function onUserLogin() {
    const guestIds = JSON.parse(localStorage.getItem('wishlist') ?? '[]')
    if (guestIds.length) {
        const merged = await api.post('/local/api/wishlist/merge/', { ids: guestIds })
        items.value  = merged
        localStorage.removeItem('wishlist')
    } else {
        await init() // Завантажуємо серверний список
    }
}

Підписка на наявність з вибраного

Розширення: якщо товар з вибраного з'явився на складі, користувач отримує сповіщення. Реалізація:

CREATE TABLE b_availability_subscriptions (
    USER_ID    INT NOT NULL,
    PRODUCT_ID INT NOT NULL,
    EMAIL      VARCHAR(255),
    DATE_ADD   TIMESTAMP DEFAULT NOW(),
    PRIMARY KEY (USER_ID, PRODUCT_ID)
);

Агент Бітрікс перевіряє таблицю раз на годину: якщо товар знову з'явився в наявності (QUANTITY > 0), відправляє листа через CEvent::Send() і видаляє підписку.

Аналітика вибраного

Дані вибраного — цінний сигнал про попит. Товари в топі вибраного з нульовими залишками — кандидати на пріоритетне поповнення. Аналітичний дашборд: топ-50 товарів у вибраному, конверсія «з вибраного в покупку» (через JOIN з b_sale_basket).

Терміни

Варіант Що входить Термін
Guest-only wishlist localStorage + кнопки + сторінка 4–7 днів
Авторизований з персистом + БД, API, мердж 1–2 тижні
+ Мульти-списки, підписка + управління списками, сповіщення +1–2 тижні

Вибране — не просто функція зручності. Це механізм повернення покупця: людина додала товар у вішліст, пішла, повернулася через тиждень і купила. Без вибраного цей сценарій закінчується на кроці «пішла».