Подмены блоков
Блок «Ничего не найдено» и витрину товаров в нём можно заменить своим компонентом — например, показать там рекомендации Mindbox или популярные товары раздела, в котором сейчас покупатель. Откуда брать товары и как их рисовать, решаете вы; рисуются они вашей же карточкой товара, со всеми её фото, метками и ценами.
Подмены пишутся в редакторе виджета, на вкладке «Блоки» блока «Код виджета». Нужна роль владельца аккаунта или Суперадмина.
Что можно подменить
| Блок | Что заменяется | Где |
|---|---|---|
| Блок «Ничего не найдено» | весь экран пустой выдачи: сообщение с кнопкой выхода («Сбросить фильтры» / «Изменить запрос»), блок помощи, витрина | отдельно для автодополнения и для полностраничного поиска |
| Популярные товары на пустой выдаче | только витрина товаров под сообщением | так же |
Подмена действует во всех темах поверхности: смените тему автодополнения — ваш блок останется на месте. Удалите подмену — вернётся блок темы.
В теме classic автодополнения витрина на пустой выдаче показывается на компьютере (на телефоне панель товаров не рисует вообще). Выключить её можно в настройках сборки — блок «Популярные товары на пустой выдаче».
Как добавить
- Вкладка «Блоки» → «Добавить подмену» → выберите блок. В списке только блоки включённых поверхностей.
- Редактор заполнится заготовкой: она рисует блок темы, поэтому сохранение без правок ничего на сайте не меняет.
- Замените разметку своей и нажмите «Сохранить и опубликовать».
Блок появляется, когда поиск окончательно ничего не нашёл — по набранному запросу или по выбранным фильтрам, — и живёт, пока выдача остаётся пустой. Внешний источник вызывается один раз на показ пустой выдачи. Свойство query — запрос, на который выдача пуста, одинаково во всех темах: пока покупатель набирает следующий, компонент видит прежний. Сменил запрос на другой, тоже пустой, — блок не пересоздаётся, новый запрос приходит в query один раз, когда поиск ответил; компоненту, которому он важен, стоит за ним следить (watch(() => props.query, …)).
Что доступно компоненту
Компонент импортирует всё из @searchforce/tenant-kit; карточку — из @tenant/card, как и остальной код виджета.
Свойства блока:
| Блок | Свойства |
|---|---|
«Ничего не найдено» (NoResultsBlockProps) | query — запрос; hasActiveFilters — пусто из-за фильтров, а не из-за запроса; clearFilters(), clearQuery() — выходы из тупика; surface — autocomplete или fullpage |
«Популярные товары» (PopularProductsBlockProps) | query; limit — сколько товаров вмещает раскладка темы (5 или 6); view — строки или сетка (тема modern); surface |
Инструменты:
| Что | Зачем |
|---|---|
useProductsByIds(ids) | список id из любого источника → ваши карточки. Порядок — как в списке; товары, которых нет в каталоге или которые скрыты правилами и настройками наличия, пропускаются. До 30 товаров |
useCatalogProducts({ filters, limit }) | популярные товары каталога, суженные фильтрами: { category_names: 'Кроссовки' } |
DefaultBlock | блок темы на этом месте — запасной вариант, если источник не ответил |
PopularProducts | витрина темы внутри вашего блока «Ничего не найдено» |
ProductCarousel | лента карточек со стрелками и свайпом |
ContactHelp, useContactHelp | блок помощи с контактами из настроек проекта |
Оба загрузчика отдают { items, loading, error }. Товары в них — те же, что в выдаче: действует ограничение выдачи с сайта (scope), клики и заказы с этих карточек попадают в аналитику поиска как переходы из блока популярных товаров — в том числе когда товары подобраны по id или по разделу.
Рекомендации Mindbox
vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import {
DefaultBlock,
ProductCarousel,
useProductsByIds,
type Hit,
type PopularProductsBlockProps,
} from '@searchforce/tenant-kit'
const props = defineProps<PopularProductsBlockProps>()
const ids = ref<string[] | null>(null)
type MindboxWindow = Window & { mindbox?: (mode: 'sync', request: Record<string, unknown>) => void }
onMounted(() => {
const mindbox = (window as MindboxWindow).mindbox
if (!mindbox) {
ids.value = []
return
}
// Не ответил за 2 секунды — показываем блок темы.
const timer = setTimeout(() => {
ids.value ??= []
}, 2000)
mindbox('sync', {
operation: 'Website.GetPopularProducts', // имя операции из вашей точки интеграции
data: { recommendation: { limit: props.limit } },
onSuccess: (response: { recommendations?: Array<{ ids?: Record<string, string> }> }) => {
clearTimeout(timer)
ids.value = (response.recommendations ?? []).map((item) => item.ids?.website ?? '').filter(Boolean)
},
onError: () => {
clearTimeout(timer)
ids.value = []
},
})
})
const { items, loading } = useProductsByIds<Hit>(ids, { limit: props.limit })
</script>
<template>
<!-- Блок темы — только когда ответ уже есть и пуст: пока идёт подстановка по
id (loading), не рисуем ничего, иначе блок темы мигнёт своим запросом. -->
<DefaultBlock v-if="ids !== null && !loading && items.length === 0" />
<section v-else-if="items.length > 0" class="reco">
<h3 class="reco__title">Рекомендуем вам</h3>
<ProductCarousel :items="items" size="s" label="Рекомендуем вам" />
</section>
</template>Классы reco и reco__title — ваши: стили для них кладите в theme/tokens.css. Раскладка из набора (ProductCarousel) от темы не зависит, поэтому блок переживёт смену темы автодополнения.
Перед запуском проверьте:
- Вызов —
sync: в режимеasyncMindbox ответа не возвращает. - Домен магазина указан в точке интеграции Mindbox («Сайты с JavaScript SDK»), а политика безопасности сайта (CSP) пропускает
https://*.mindbox.ru— иначе запрос отклонят. - Ключ в
ids— внешняя система, под которой Mindbox знает товары сайта (website,bitrixIdили своя). Эти id должны совпадать сidофферов в вашем фиде: сверьте на паре товаров. - Ошибки внутри колбэков Mindbox обрабатывайте сами (
onError): SearchForce их не видит.
Популярное в разделе страницы
vue
<script setup lang="ts">
import { computed } from 'vue'
import { DefaultBlock, ProductCarousel, useCatalogProducts, type PopularProductsBlockProps } from '@searchforce/tenant-kit'
const props = defineProps<PopularProductsBlockProps>()
// Раздел, где сейчас покупатель: последняя хлебная крошка страницы.
const section = document.querySelector('.breadcrumbs li:last-child')?.textContent?.trim() ?? ''
const { items, loading } = useCatalogProducts(() =>
section ? { filters: { category_names: section }, limit: props.limit } : null,
)
const title = computed(() => `Популярное в разделе «${section}»`)
</script>
<template>
<DefaultBlock v-if="!section || (!loading && items.length === 0)" />
<section v-else-if="items.length > 0">
<h2>{{ title }}</h2>
<ProductCarousel :items="items" :label="title" />
</section>
</template>Значение фильтра — как в каталоге (название категории из фида). Если раздел на странице не определился, остаётся блок темы.
Если что-то пошло не так
- Компонент упал при отрисовке или при запуске — на сайте останется блок темы, а в консоли браузера и в событии
diagnosticпоявится сообщение с именем файла. Покупатель пустого места не увидит. - Ошибка в обработчике (клик по кнопке внутри вашего блока) блок не снимает: он уже нарисован, ошибка попадёт в консоль как обычно.
- Ошибки внутри колбэков Mindbox SearchForce не видит — обрабатывайте их в
onError, например показавDefaultBlock. - Опечатка в имени файла не пройдёт публикацию: имя блока выбирается из списка, а сборка не принимает файлы, которые ничего не подменяют.
- Вспомогательные компоненты для подмены кладите в компоненты карточки и импортируйте относительным путём (
../../card/Tile.vue).