barneo-search-widget-lib

Библиотека для поиска по каталогу Barneo на Vue 3
2,089
Install
npm install barneo-search-widget-lib
Latest Version:2.2.1
Vue:3
License:MIT
Last Updated:Aug 29, 2025
Links: npm
Maintainer: ademaru

Barneo Search Widget

Библиотека компонентов для поиска по каталогу Barneo с помощью Ensi

📋 Содержание

📦 Установка

npm install barneo-search-widget-lib
# или
yarn add barneo-search-widget-lib

⚙️ Подключение и конфигурация

Vue 3

Глобальная регистрация (рекомендуется)
// main.ts
import { createApp } from "vue";
import App from "./App.vue";
import BarneoSearchPlugin from "barneo-search-widget-lib";

const app = createApp(App);

const config = {
  api: {
    baseUrl: "https://api.search.ensi.cloud",
    token: "your-api-token",
    customerId: "4",
    locationId: "1",
    apiVersion: "v1",
  },
  searchWidget: {
    placeholder: "Найти товары",
    debounceMs: 300,
    minSearchLength: 2,
    darkMode: false,
    focusMode: false,
  },
  filtersWidget: {
    maxValuesPerFilter: 5,
    debounceMs: 300,
    darkMode: false,
  },
  productsWidget: {
    itemsPerPage: 20,
    mobileItemWidth: 160,
    desktopItemWidth: 200,
    darkMode: false,
    showPagination: true,
    showTotalCount: true,
  },
  sortWidget: {
    darkMode: false,
    defaultSort: "relevance",
  },
};

app.use(BarneoSearchPlugin, config);
app.mount("#app");

Примечание: URL менеджер инициализируется в компонентах через useUrlManagerInit() composable, а не автоматически в плагине.

Локальная регистрация
<template>
  <div>
    <barneo-search-widget v-model="query" />
    <barneo-filters-widget />
    <barneo-sort-widget />
    <barneo-products-widget />
  </div>
</template>

<script setup>
import { ref } from "vue";
import {
  BarneoSearchWidget,
  BarneoFiltersWidget,
  BarneoSortWidget,
  BarneoProductsWidget,
} from "barneo-search-widget-lib";

const query = ref("");
</script>

Nuxt 3

Создание плагина
// plugins/barneo-search.client.ts
import { defineNuxtPlugin } from "nuxt/app";
import BarneoSearchPlugin from "barneo-search-widget-lib";

export default defineNuxtPlugin((nuxtApp: any) => {
  const config = {
    api: {
      baseUrl:
        process.env.NUXT_BARNEO_ENSI_BASE_URL ||
        "https://api.search.ensi.cloud",
      token:
        process.env.NUXT_BARNEO_ENSI_TOKEN ||
        "95246bb090387d5c92186f1b6d25cc72",
      customerId: process.env.NUXT_BARNEO_ENSI_CUSTOMER_ID || "4",
      locationId: process.env.NUXT_BARNEO_ENSI_LOCATION_ID || "1",
      apiVersion: process.env.NUXT_BARNEO_ENSI_API_VERSION || "v1",
    },
    searchWidget: {
      placeholder: "Найти товары",
      debounceMs: 300,
      minSearchLength: 2,
      darkMode: false,
      focusMode: false,
    },
    filtersWidget: {
      maxValuesPerFilter: 5,
      debounceMs: 300,
      darkMode: false,
    },
    productsWidget: {
      itemsPerPage: 20,
      mobileItemWidth: 160,
      desktopItemWidth: 200,
      darkMode: false,
      showPagination: true,
      showTotalCount: true,
    },
    sortWidget: {
      darkMode: false,
      defaultSort: "relevance",
    },
  };

  // Используем плагин библиотеки для глобальной регистрации
  nuxtApp.vueApp.use(BarneoSearchPlugin, config);

  // URL менеджер инициализируется в компонентах через useUrlManagerInit()
});
Настройка в nuxt.config.ts
// nuxt.config.ts
export default defineNuxtConfig({
  css: ["barneo-search-widget-lib/dist/style.css"],
  runtimeConfig: {
    public: {
      barneoEnsiBaseUrl: process.env.NUXT_BARNEO_ENSI_BASE_URL,
      barneoEnsiToken: process.env.NUXT_BARNEO_ENSI_TOKEN,
      barneoEnsiCustomerId: process.env.NUXT_BARNEO_ENSI_CUSTOMER_ID,
      barneoEnsiLocationId: process.env.NUXT_BARNEO_ENSI_LOCATION_ID,
      barneoEnsiApiVersion: process.env.NUXT_BARNEO_ENSI_API_VERSION,
    },
  },
});

Описание конфигурации

API конфигурация
api: {
  baseUrl: string,        // Базовый URL API (обязательно)
  token: string,          // API токен для авторизации (обязательно)
  customerId: string,     // ID клиента (обязательно)
  locationId: string,     // ID локации (обязательно)
  apiVersion?: string     // Версия API (опционально, если не указана используется только baseUrl)
}
Конфигурация поиска
searchWidget: {
  placeholder: string,    // Placeholder для поля поиска (по умолчанию: 'Поиск...')
  debounceMs: number,     // Задержка перед отправкой запроса в мс (по умолчанию: 300)
  minSearchLength: number, // Минимальная длина для поиска (по умолчанию: 2)
  darkMode: boolean,      // Принудительная темная тема (по умолчанию: false)
  focusMode: boolean      // Автофокус при монтировании (по умолчанию: false)
}
Конфигурация фильтров
filtersWidget: {
  maxValuesPerFilter: number, // Максимум значений в фильтре (по умолчанию: 5)
  debounceMs: number,         // Задержка применения фильтров в мс (по умолчанию: 300)
  darkMode: boolean           // Принудительная темная тема (по умолчанию: false)
}
Конфигурация товаров
productsWidget: {
  itemsPerPage: number,       // Количество товаров на странице (по умолчанию: 20)
  mobileItemWidth: number,    // Ширина элемента на мобильных (по умолчанию: 160)
  desktopItemWidth: number,   // Ширина элемента на десктопе (по умолчанию: 200)
  darkMode: boolean,          // Принудительная темная тема (по умолчанию: false)
  showPagination: boolean,    // Показывать пагинацию (по умолчанию: true)
  showTotalCount: boolean     // Показывать общее количество (по умолчанию: true)
}
Конфигурация сортировки
sortWidget: {
  darkMode: boolean,          // Принудительная темная тема (по умолчанию: false)
  defaultSort: string         // Сортировка по умолчанию (по умолчанию: 'relevance')
}

🚀 Использование библиотеки

Базовое использование

<template>
  <div>
    <barneo-search-widget v-model="searchQuery" @search="handleSearch" />
    <barneo-filters-widget @filter-applied="handleFilter" />
    <barneo-sort-widget @sort-changed="handleSort" />
    <barneo-products-widget @product-selected="handleProductSelect" />
    <barneo-range-selector
      ref="rangeSelector"
      :min="0"
      :max="10000"
      @change="handleRangeChange"
    />
  </div>
</template>

<script setup>
import { ref } from "vue";

const searchQuery = ref("");
const rangeSelector = ref();

const handleSearch = (query) => {
  console.log("Поиск выполнен:", query);
};

const handleFilter = (filterCode, valueId, isSelected) => {
  console.log("Фильтр применен:", filterCode, valueId, isSelected);
};

const handleSort = (sortValue) => {
  console.log("Сортировка изменена:", sortValue);
};

const handleProductSelect = (product) => {
  console.log("Товар выбран:", product);
};

const handleRangeChange = ([min, max]) => {
  console.log("Диапазон изменен:", { min, max });
};
</script>

Использование composables

<template>
  <div>
    <input v-model="searchQuery" @input="handleSearch" />
    <div v-if="isLoading">Загрузка...</div>
    <div v-else>
      <div v-for="result in searchResults" :key="result.id">
        {{ result.name }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import { useSearchWidget, SearchApiService } from "barneo-search-widget-lib";

const searchQuery = ref("");
const isLoading = ref(false);
const searchResults = ref([]);

const { performSearch } = useSearchWidget();
const apiService = new SearchApiService({
  baseUrl: "https://api.search.ensi.cloud",
  token: "your-token",
});

const handleSearch = async () => {
  if (searchQuery.value.length < 2) return;

  isLoading.value = true;
  try {
    const results = await performSearch(searchQuery.value, apiService);
    searchResults.value = results.data?.full_products || [];
  } catch (error) {
    console.error("Ошибка поиска:", error);
  } finally {
    isLoading.value = false;
  }
};
</script>

🧩 Компоненты

BarneoSearchWidget

Компонент поиска с автодополнением, быстрыми результатами и полным поиском.

Props
interface SearchWidgetProps {
  modelValue?: string; // Значение поля поиска (v-model)
  placeholder?: string; // Placeholder для поля ввода
  debounceMs?: number; // Задержка перед отправкой запроса
  minSearchLength?: number; // Минимальная длина для поиска
  darkMode?: boolean; // Принудительная темная тема
  focusMode?: boolean; // Автофокус при монтировании
  apiService?: SearchApiService; // Кастомный API сервис
}
События
  • @search - выполнен поиск (автоматический или ручной)

    (query: string) => void
    
  • @select - выбран товар из результатов

    (product: Product, event?: MouseEvent) => void
    

    Примечание: Событие поддерживает MouseEvent для обработки средней кнопки мыши. При event.button === 1 рекомендуется открывать товар в новой вкладке.

  • @searchResults - получены результаты поиска

    (results: SearchResults) => void
    
  • @loading - изменение состояния загрузки

    (isLoading: boolean) => void
    
  • @error - произошла ошибка

    (error: string) => void
    
  • @focus - поле получило фокус

    () => void
    
  • @blur - поле потеряло фокус

    () => void
    
  • @input - изменение значения поля

    (value: string) => void
    
Слоты
  • search-header - заголовок компонента поиска
<template #search-header="{ config }">
  <h1>🔍 Поиск товаров</h1>
</template>
  • search-input - кастомное поле ввода
    <template #search-input="{ value, onInput, onFocus, onBlur }">
      <input :value="value" @input="onInput" @focus="onFocus" @blur="onBlur" />
    </template>
    
  • search-results - кастомные результаты поиска
    <template
      #search-results="{ results, isLoading, selectProduct, handleAuxClick }"
    >
      <div v-if="isLoading">Загрузка...</div>
      <div v-else>
        <div
          v-for="result in results"
          :key="result.id"
          @click="(event) => selectProduct(result, event)"
          @auxclick="(event) => handleAuxClick(result, event)"
        >
          {{ result.name }}
        </div>
      </div>
    </template>
    
  • search-empty - пустое состояние
    <template #search-empty="{ query }">
      <p>По запросу "{{ query }}" ничего не найдено</p>
    </template>
    
  • loading - состояние загрузки
    <template #loading="{ isLoading }">
      <div v-if="isLoading">⏳ Загрузка...</div>
    </template>
    
  • error - состояние ошибки
    <template #error="{ error }">
      <div v-if="error">❌ {{ error }}</div>
    </template>
    

BarneoFiltersWidget

Компонент фильтров с динамической загрузкой, автоматическим применением и кастомными слотами.

Props
interface FiltersWidgetProps {
  loading?: boolean; // Состояние загрузки
  error?: string | null; // Текст ошибки
  activeFilters?: Record<string, string[]>; // Активные фильтры
  config?: FiltersWidgetConfig; // Локальная конфигурация
  apiService?: SearchApiService; // Кастомный API сервис
}
События
  • @filter-applied - применен фильтр
    (filterCode: string, valueId: string, isSelected: boolean) => void
    
  • @filter-removed - удален фильтр
    (filterCode: string, valueId: string) => void
    
  • @filters-reset - сброшены все фильтры
    () => void
    
  • @filters-applied - применены фильтры
    (activeFilters: Record<string, string[]>) => void
    
  • @active-filters-changed - изменились активные фильтры
    (activeFilters: Record<string, string[]>) => void
    
  • @loading - изменение состояния загрузки
    (isLoading: boolean) => void
    
  • @error - произошла ошибка
    (error: string) => void
    
Слоты
  • filters-title - заголовок фильтров
<template #filters-title="{ config }">
  <h2>🎯 Фильтры ({{ config.maxValuesPerFilter }} значений)</h2>
</template>
  • filters-skeleton - скелетон загрузки
    <template #filters-skeleton="{ isLoading, skeletonData }">
      <div v-if="isLoading">
        <div v-for="group in skeletonData" :key="group.id">
          <!-- Скелетон группы -->
        </div>
      </div>
    </template>
    
- `filter-group` - группа фильтров
```vue
  <template
    #filter-group="{ filter, visibleValues, isExpanded, toggleFilterExpansion }"
  >
    <div class="filter-group">
      <h3>{{ filter.name }}</h3>
      <div v-if="isExpanded">
        <!-- Значения фильтров -->
        </div>
      <button @click="toggleFilterExpansion(filter.code)">
        {{ isExpanded ? "Свернуть" : "Развернуть" }}
          </button>
        </div>
  </template>
  • filter-value - значение фильтра
    <template #filter-value="{ filterCode, value, isActive, handleClick }">
      <div @click="handleClick(filterCode, value.id)">
        <input type="checkbox" :checked="isActive" />
        <label>{{ value.name }}</label>
      </div>
    </template>
    
  • filter-checkbox - кастомный чекбокс
    <template #filter-checkbox="{ filterCode, value, isActive, handleClick }">
      <div class="custom-checkbox">
        <input
          type="checkbox"
          :checked="isActive"
          @change="handleClick(filterCode, value.id)"
        />
        <label>{{ value.name }}</label>
      </div>
    </template>
    
- `filter-toggle` - кнопка "Показать все"
```vue
  <template #filter-toggle="{ filterCode, isExpanded, handleToggle }">
    <button @click="handleToggle(filterCode)">
      {{ isExpanded ? "Скрыть" : "Показать все" }}
    </button>
  </template>
  • filter-range - кастомный range фильтр с под-слотами

    <template
      #filter-range="{
        filter,
        handleRangeSelectorChange,
        rangeSelectorValue,
        rangeStep,
        rangeFormatValue,
        isRangeActive,
        rangeMin,
        rangeMax,
      }"
    >
      <div class="custom-range-filter">
        <!-- Заголовок range фильтра -->
        <template #filter-range-header="{ filter }">
          <h4>{{ filter.description }}</h4>
        </template>
    
        <!-- Индикатор активного диапазона -->
        <template #filter-range-indicator="{ isRangeActive, rangeFormatValue }">
          <div v-if="isRangeActive" class="range-indicator">
            Выбран диапазон: {{ rangeFormatValue }}
          </div>
        </template>
    
        <!-- Кнопка сброса -->
        <template
          #filter-range-reset="{ handleRangeSelectorChange, rangeMin, rangeMax }"
        >
          <button
            @click="handleRangeSelectorChange(filter.code, [rangeMin, rangeMax])"
          >
            Сбросить
          </button>
        </template>
      </div>
    </template>
    
  • filters-empty - пустое состояние

    <template #filters-empty="{ hasFilters }">
      <div v-if="!hasFilters">
        <p>🔍 Выполните поиск для получения фильтров</p>
      </div>
    </template>
    
  • loading - состояние загрузки

    <template #loading="{ isLoading }">
      <div v-if="isLoading">⏳ Загрузка фильтров...</div>
    </template>
    
  • error - состояние ошибки

    <template #error="{ error }">
      <div v-if="error">❌ {{ error }}</div>
    </template>
    

### BarneoSortWidget

Компонент сортировки с предустановленными опциями и кастомными слотами.

#### Props

```typescript
interface SortWidgetProps {
  modelValue?: string; // Текущая сортировка (v-model)
  options?: SortOption[]; // Кастомные опции сортировки
  showLabels?: boolean; // Показывать метки опций
  defaultSort?: string; // Сортировка по умолчанию
  darkMode?: boolean; // Принудительная темная тема
  disabled?: boolean; // Отключить компонент
}
События
  • @update:modelValue - изменена сортировка
  (sortValue: string) => void
  • @sort-changed - изменена сортировка
    (sortValue: string) => void
    
  • @loading - изменение состояния загрузки
    (isLoading: boolean) => void
    
  • @error - произошла ошибка
    (error: string) => void
    
Слоты
  • sort-options - кастомный контейнер с опциями сортировки
<template
  #sort-options="{ sortOptions, currentSort, isLoading, handleSortChange }"
>
  <div class="custom-sort-container">
    <div
      v-for="option in sortOptions"
      :key="option.value"
      class="custom-sort-option"
      :class="{ active: option.value === currentSort }"
      @click="handleSortChange(option.value)"
    >
      {{ option.label }}
    </div>
  </div>
</template>
  • sort-option - кастомная опция сортировки
<template #sort-option="{ option, isActive, isDisabled }">
  <div
    class="custom-sort-option"
    :class="{ active: isActive, disabled: isDisabled }"
  >
    <span>{{ option.label }}</span>
    <span v-if="isActive" class="loading-indicator">⏳</span>
  </div>
</template>
  • loading - состояние загрузки
    <template #loading="{ isLoading }">
      <div v-if="isLoading">⏳ Обновление...</div>
    </template>
    
  • error - состояние ошибки
<template #error="{ error }">
  <div v-if="error">❌ {{ error }}</div>
</template>

BarneoRangeSelector

Компонент для выбора диапазонов с прогрессивной шкалой, двухползунковым слайдером и капсуловидными полями ввода.

Особенности
  • Прогрессивная шкала: Более точный выбор в нижних диапазонах (первые 50% движения покрывают 25% значений)
  • Двухползунковый слайдер: Интуитивный выбор минимального и максимального значения
  • Капсуловидные поля ввода: Прямой ввод значений с метками "ОТ" и "ДО"
  • Конвертация цен: Автоматическая конвертация копеек в рубли для ценовых фильтров
  • Адаптивная разделительная линия: Линия между полями ввода растягивается на доступное пространство
  • События при отпускании: События эмитятся только при отпускании ползунка для оптимизации производительности
Props
interface RangeSelectorProps {
  min: number; // Минимальное значение
  max: number; // Максимальное значение
  value?: [number, number]; // Текущие значения [min, max]
  step?: number; // Шаг изменения (по умолчанию: 1)
  formatValue?: (value: number) => string; // Функция форматирования значений
}
События
  • @update:value - изменены значения
    (value: [number, number]) => void
    
  • @change - изменены значения (эмитится при отпускании ползунка или потере фокуса)
    (value: [number, number]) => void
    
Методы (через ref)
interface RangeSelectorExpose {
  setValue: (min: number, max: number) => void; // Установить значения
  getValue: () => [number, number]; // Получить текущие значения
}
Пример использования
<template>
  <barneo-range-selector
    ref="rangeSelector"
    :min="0"
    :max="10000"
    :value="[1000, 5000]"
    :step="100"
    :format-value="formatPrice"
    @change="handleRangeChange"
  />
</template>

<script setup>
import { ref } from "vue";

const rangeSelector = ref();

const formatPrice = (value: number) => {
  return `${Math.floor(value / 100)} ₽`; // Конвертация копеек в рубли
};

const handleRangeChange = ([min, max]: [number, number]) => {
  console.log("Диапазон изменен:", { min, max });
};

// Программное изменение значений
const resetRange = () => {
  rangeSelector.value?.setValue(0, 10000);
};
</script>

BarneoProductsWidget

Компонент отображения товаров с пагинацией, адаптивной сеткой и кастомными слотами.

Props
interface ProductsWidgetProps {
  products?: Product[]; // Список товаров для отображения
  loading?: boolean; // Состояние загрузки
  error?: string | null; // Текст ошибки
  pagination?: PaginationState; // Состояние пагинации
  config?: ProductsWidgetConfig; // Локальная конфигурация
  apiService?: SearchApiService; // Кастомный API сервис
}
События
  • @product-selected - выбран товар
  (product: Product, event?: MouseEvent) => void

Примечание: Событие поддерживает MouseEvent для обработки средней кнопки мыши. При event.button === 1 рекомендуется открывать товар в новой вкладке.

  • @page-changed - изменена страница
    (page: number) => void
    
  • @loading - изменение состояния загрузки
    (isLoading: boolean) => void
    
  • @error - произошла ошибка
    (error: string) => void
    
Слоты
  • products-grid - кастомная сетка товаров
<template #products-grid="{ products, isLoading, itemWidth }">
  <div class="custom-grid" :style="{ '--item-width': itemWidth + 'px' }">
    <div v-for="product in products" :key="product.id" class="grid-item">
      <!-- Кастомная карточка товара -->
    </div>
  </div>
</template>
  • product-card - кастомная карточка товара
    <template #product-card="{ product, index, onSelect, handleAuxClick }">
      <div
        class="custom-product-card"
        @click="(event) => onSelect(product, event)"
        @auxclick="(event) => handleAuxClick(product, event)"
      >
        <img :src="https://raw.githubusercontent.com/denis-muhortov/barneo-search-widget-lib/HEAD/product.image" :alt="product.name" />
        <h3>{{ product.name }}</h3>
        <p class="price">{{ formatPrice(product.price) }}</p>
      </div>
    </template>
    
  • products-empty - пустое состояние
    <template #products-empty="{ hasProducts, query }">
      <div v-if="!hasProducts && query">
        <p>По запросу "{{ query }}" товары не найдены</p>
      </div>
    </template>
    
  • products-pagination - кастомная пагинация
    <template #products-pagination="{ pagination, onPageChange }">
      <div class="custom-pagination">
        <button
          v-for="page in pagination.totalPages"
          :key="page"
          :class="{ active: page === pagination.currentPage }"
          @click="onPageChange(page)"
        >
          {{ page }}
        </button>
      </div>
    </template>
    
  • loading - состояние загрузки
    <template #loading="{ isLoading }">
      <div v-if="isLoading">⏳ Загрузка товаров...</div>
    </template>
    
  • error - состояние ошибки
    <template #error="{ error }">
      <div v-if="error">❌ {{ error }}</div>
    </template>
    

🎨 Кастомные слоты - примеры

Поиск с кастомными слотами

<barneo-search-widget v-model="query">
  <template #search-header="{ config }">
    <div class="custom-header">
      <h1>🔍 Поиск товаров</h1>
      <p>Конфигурация: {{ JSON.stringify(config) }}</p>
    </div>
  </template>
  
  <template #search-results="{ results, isLoading }">
    <div v-if="isLoading" class="loading">Загрузка...</div>
    <div v-else class="results">
      <div v-for="result in results" :key="result.id" class="result-item">
        <h3>{{ result.name }}</h3>
        <p>{{ result.description }}</p>
      </div>
    </div>
  </template>
</barneo-search-widget>

Фильтры с кастомными слотами

<barneo-filters-widget>
  <template #filters-title="{ config }">
    <div class="custom-filters-title">
      🎯 Фильтры ({{ config.maxValuesPerFilter }} значений)
    </div>
  </template>
  
  <template #filter-value="{ filterCode, value, isActive, handleClick }">
    <div class="custom-filter-value" @click="handleClick(filterCode, value.id)">
      <input 
        type="checkbox" 
        :checked="isActive"
        :id="`${filterCode}-${value.id}`"
      />
      <label :for="`${filterCode}-${value.id}`">
        {{ value.name }}
      </label>
  </div>
</template>

  <template #filter-range="{ 
    filter, 
    handleRangeSelectorChange, 
    rangeSelectorValue, 
    rangeStep, 
    rangeFormatValue, 
    isRangeActive, 
    rangeMin, 
    rangeMax 
  }">
    <div class="custom-range-filter">
      <!-- Кастомный заголовок range фильтра -->
      <template #filter-range-header="{ filter }">
        <h4 class="range-title">{{ filter.description }}</h4>
      </template>
      
      <!-- Кастомный индикатор активного диапазона -->
      <template #filter-range-indicator="{ isRangeActive, rangeFormatValue }">
        <div v-if="isRangeActive" class="custom-range-indicator">
          <span class="indicator-icon">🎯</span>
          <span class="indicator-text">{{ rangeFormatValue }}</span>
        </div>
      </template>
      
      <!-- Кастомная кнопка сброса -->
      <template #filter-range-reset="{ handleRangeSelectorChange, rangeMin, rangeMax }">
        <button 
          class="custom-reset-button"
          @click="handleRangeSelectorChange(filter.code, [rangeMin, rangeMax])"
        >
          <span class="reset-icon">🔄</span>
          Сбросить
        </button>
      </template>
    </div>
  </template>

  <template #filters-empty="{ hasFilters }">
    <div v-if="!hasFilters" class="custom-empty">
      <p>🔍 Выполните поиск для получения фильтров</p>
    </div>
  </template>
</barneo-filters-widget>

Сортировка с кастомными слотами

<barneo-sort-widget v-model="sortValue">
  <template #sort-options="{ sortOptions, currentSort, isLoading, handleSortChange }">
    <div class="custom-sort-container">
      <div 
        v-for="option in sortOptions" 
        :key="option.value"
        class="custom-sort-option"
        :class="{ active: option.value === currentSort }"
        @click="handleSortChange(option.value)"
      >
        {{ option.label }}
      </div>
    </div>
  </template>
  
  <template #sort-option="{ option, isActive, isDisabled }">
    <div 
      class="custom-sort-option" 
      :class="{ active: isActive, disabled: isDisabled }"
    >
      <span class="sort-label">{{ option.label }}</span>
      <span v-if="isActive" class="loading-spinner">⏳</span>
    </div>
  </template>
  
  <template #loading="{ isLoading }">
    <div v-if="isLoading" class="sort-loading">
      Обновление результатов...
    </div>
  </template>
</barneo-sort-widget>

Товары с кастомными слотами

<barneo-products-widget>
  <template #product-card="{ product, index, onSelect, handleAuxClick }">
    <div 
      class="custom-product-card" 
      @click="(event) => onSelect(product, event)"
      @auxclick="(event) => handleAuxClick(product, event)"
    >
      <div class="product-image">
        <img :src="https://raw.githubusercontent.com/denis-muhortov/barneo-search-widget-lib/HEAD/product.image" :alt="product.name" />
      </div>
      <div class="product-info">
        <h3 class="product-title">{{ product.name }}</h3>
        <p class="product-price">{{ formatPrice(product.price) }}</p>
        <p class="product-description">{{ product.description }}</p>
      </div>
    </div>
  </template>
  
  <template #products-pagination="{ pagination, onPageChange }">
    <div class="custom-pagination">
      <button 
        :disabled="pagination.currentPage === 1"
        @click="onPageChange(pagination.currentPage - 1)"
      >
        ← Назад
      </button>
      
      <span class="page-info">
        Страница {{ pagination.currentPage }} из {{ pagination.totalPages }}
      </span>
      
      <button 
        :disabled="pagination.currentPage === pagination.totalPages"
        @click="onPageChange(pagination.currentPage + 1)"
      >
        Вперед →
      </button>
    </div>
  </template>
  
  <template #products-empty="{ hasProducts, query }">
    <div v-if="!hasProducts && query" class="custom-empty">
      <p>😔 По запросу "{{ query }}" товары не найдены</p>
      <p>Попробуйте изменить параметры поиска</p>
  </div>
</template>
</barneo-products-widget>

Обработка средней кнопки мыши

Библиотека поддерживает открытие товаров в новой вкладке при нажатии средней кнопки мыши (колесико).

Пример обработки в BarneoSearchWidget
<template>
  <barneo-search-widget v-model="query" @select="handleProductSelect" />
</template>

<script setup>
import { useRouter } from "vue-router";

const router = useRouter();

const handleProductSelect = (product, event) => {
  console.log("Выбран товар:", product);

  // Если это средняя кнопка мыши (колесико), открываем в новой вкладке
  if (event && event.button === 1) {
    window.open(`/product/${product.id}`, "_blank");
  } else {
    // Обычный клик - переходим в текущей вкладке
    router.push(`/product/${product.id}`);
  }
};
</script>
Пример обработки в BarneoProductsWidget
<template>
  <barneo-products-widget @product-select="handleProductSelect" />
</template>

<script setup>
import { useRouter } from "vue-router";

const router = useRouter();

const handleProductSelect = (product, event) => {
  console.log("Выбран товар из списка:", product);

  // Если это средняя кнопка мыши (колесико), открываем в новой вкладке
  if (event && event.button === 1) {
    window.open(`/product/${product.id}`, "_blank");
  } else {
    // Обычный клик - переходим в текущей вкладке
    router.push(`/product/${product.id}`);
  }
};
</script>
Использование в кастомных слотах
<template>
  <barneo-search-widget>
    <template
      #search-results="{ searchResults, selectProduct, handleAuxClick }"
    >
      <div v-for="product in searchResults" :key="product.id">
        <div
          class="product-item"
          @click="(event) => selectProduct(product, event)"
          @auxclick="(event) => handleAuxClick(product, event)"
        >
          <h3>{{ product.name }}</h3>
          <p>{{ product.price }}</p>
        </div>
      </div>
    </template>
  </barneo-search-widget>
</template>

📊 Аналитика

Библиотека включает в себя модуль аналитики для отслеживания пользовательского поведения и взаимодействий с поисковым интерфейсом.

Использование аналитики

Базовое использование
import { useAnalytics } from "barneo-search-widget-lib";

// Инициализация аналитики (автоматически использует конфигурацию из BarneoConfigManager)
const analytics = useAnalytics();

// Проверка инициализации
console.log("Аналитика инициализирована:", analytics.isInitialized.value);
console.log("Конфигурация:", analytics.getConfigInfo.value);
Отслеживание событий
// Объединение неавторизованного и авторизованного пользователя
await analytics.mergeCustomers("guest-id-123", "authorized-id-456");

// Отслеживание поискового запроса
await analytics.trackQueryUse("сковорода");

// Отслеживание использования функций поиска
await analytics.trackFunctionUse("filter"); // фильтры
await analytics.trackFunctionUse("hint"); // подсказки
await analytics.trackFunctionUse("history"); // история

// Отслеживание применения подсказки
await analytics.trackHintUse("сков", "сковорода");

// Отслеживание конверсии товара
await analytics.trackProductConversion("product-123", "click", {
  position: 1,
  query: "сковорода",
  source: "search",
});

// Отслеживание конверсии категории
await analytics.trackCategoryConversion("category-456", {
  query: "посуда",
  source: "search",
});
Быстрые методы (quickTrack)
// Быстрое отслеживание клика по товару
await analytics.quickTrack.productClick("product-123", {
  position: 1,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание добавления в корзину
await analytics.quickTrack.productBasket("product-123", {
  position: 2,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание заказа
await analytics.quickTrack.productOrder("product-123", {
  position: 3,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание клика по категории
await analytics.quickTrack.categoryClick("category-456", {
  query: "посуда",
  source: "search",
});

// Быстрое отслеживание использования функций
await analytics.quickTrack.filterUse(); // фильтры
await analytics.quickTrack.hintUse(); // подсказки
await analytics.quickTrack.historyUse(); // история

🎯 Рекомендации (Adviser)

Библиотека включает в себя модуль рекомендаций для получения умных рекомендаций товаров на основе поведения пользователей.

Использование рекомендаций

Базовое использование
import { useAdviser } from "barneo-search-widget-lib";

// Инициализация рекомендаций (автоматически использует конфигурацию из BarneoConfigManager)
const adviser = useAdviser({
  enableCache: true,
  cacheTTL: 300000, // 5 минут
  apiLimit: 8,
});

// Проверка инициализации
console.log("Рекомендации инициализированы:", adviser.state.value);
Получение рекомендаций
// "People also search for this product" - что искали с этим товаром
const peopleAlsoSearch = await adviser.getPeopleAlsoSearch("product-123");

// "Searchers also viewed" - что смотрели искавшие
const searchersAlsoViewed = await adviser.getSearchersAlsoViewed("сковорода");

// "Cross Sell" - с этим товаром покупают
const crossSell = await adviser.getCrossSell("product-123");

// "Up Sell" - более дорогие альтернативы
const upSell = await adviser.getUpSell("product-123");

// "Up Sell New" - новая версия up sell
const upSellNew = await adviser.getUpSellNew("product-123");

// "Recently watched" - недавно просмотренные
const recentlyWatched = await adviser.getRecentlyWatched("customer-456");

// "Popular products" - популярные товары
const popularProducts = await adviser.getPopularProducts();

// "Similar products" - похожие товары
const similarProducts = await adviser.getSimilarProducts("product-123");
Состояние и кэширование
// Проверка состояния загрузки
if (adviser.state.value.loadingStatus === "loading") {
  console.log("Загрузка рекомендаций...");
}

// Обработка ошибок
if (adviser.state.value.error) {
  console.error("Ошибка рекомендаций:", adviser.state.value.error);
}

// Принудительная очистка кэша
adviser.forceClear();

// Получение всех рекомендаций сразу
const allRecommendations = adviser.state.value;
console.log("Все рекомендации:", allRecommendations);
Тестирование всех рекомендаций
// Тестирование всех типов рекомендаций
const testAdviser = async () => {
  console.log("🎯 Тестирование модуля adviser...");

  // Очищаем состояние для чистого теста
  adviser.forceClear();

  // Тестируем все типы рекомендаций
  await adviser.getPeopleAlsoSearch("test-product");
  await adviser.getSearchersAlsoViewed("тестовый запрос");
  await adviser.getCrossSell("test-product");
  await adviser.getUpSell("test-product");
  await adviser.getUpSellNew("test-product");
  await adviser.getRecentlyWatched("test-customer");
  await adviser.getPopularProducts();
  await adviser.getSimilarProducts("test-product");

  console.log("✅ Все тесты рекомендаций завершены");
};

Использование аналитики

Базовое использование
import { useAnalytics } from "barneo-search-widget-lib";

// Инициализация аналитики (автоматически использует конфигурацию из BarneoConfigManager)
const analytics = useAnalytics();

// Проверка инициализации
console.log("Аналитика инициализирована:", analytics.isInitialized.value);
console.log("Конфигурация:", analytics.getConfigInfo.value);
Отслеживание событий
// Объединение неавторизованного и авторизованного пользователя
await analytics.mergeCustomers("guest-id-123", "authorized-id-456");

// Отслеживание поискового запроса
await analytics.trackQueryUse("сковорода");

// Отслеживание использования функций поиска
await analytics.trackFunctionUse("filter"); // фильтры
await analytics.trackFunctionUse("hint"); // подсказки
await analytics.trackFunctionUse("history"); // история

// Отслеживание применения подсказки
await analytics.trackHintUse("сков", "сковорода");

// Отслеживание конверсии товара
await analytics.trackProductConversion("product-123", "click", {
  position: 1,
  query: "сковорода",
  source: "search",
});

// Отслеживание конверсии категории
await analytics.trackCategoryConversion("category-456", {
  query: "посуда",
  source: "search",
});
Быстрые методы (quickTrack)
// Быстрое отслеживание клика по товару
await analytics.quickTrack.productClick("product-123", {
  position: 1,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание добавления в корзину
await analytics.quickTrack.productBasket("product-123", {
  position: 2,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание заказа
await analytics.quickTrack.productOrder("product-123", {
  position: 3,
  query: "сковорода",
  source: "search",
});

// Быстрое отслеживание клика по категории
await analytics.quickTrack.categoryClick("category-456", {
  query: "посуда",
  source: "search",
});

// Быстрое отслеживание использования функций
await analytics.quickTrack.filterUse(); // фильтры
await analytics.quickTrack.hintUse(); // подсказки
await analytics.quickTrack.historyUse(); // история
Состояние и обработка ошибок
// Проверка состояния загрузки
if (analytics.isLoading.value) {
  console.log("Отправка аналитических данных...");
}

// Обработка ошибок
if (analytics.error.value) {
  console.error("Ошибка аналитики:", analytics.error.value);
}

// Очистка ошибки
analytics.clearError();
Ручная инициализация
// Если нужно инициализировать аналитику с кастомной конфигурацией
analytics.initializeAnalytics({
  baseUrl: "https://api.search.ensi.cloud",
  token: "your-token",
  customerId: "4",
  locationId: "1",
  apiVersion: "v1",
});

Доступные типы событий

SearchFunction (функции поиска)
  • filter - использование фильтров
  • hint - использование подсказок
  • history - использование истории поиска
  • popular_auto_queries - популярные автозапросы
  • popular_manual_queries - популярные ручные запросы
  • recommended_queries - рекомендуемые запросы
ProductConversionType (типы конверсии товаров)
  • click - клик по товару
  • basket - добавление в корзину
  • order - заказ товара
ProductConversionSource (источники конверсии товаров)
  • search - из поиска
  • category - из категории
  • recommendation - из рекомендаций
CategoryConversionSource (источники конверсии категорий)
  • search - из поиска
  • navigation - из навигации

Интеграция с компонентами

Аналитика автоматически интегрируется с компонентами библиотеки через BarneoConfigManager:

import { useBarneoConfig } from "barneo-search-widget-lib";

const { analyticsService } = useBarneoConfig();

// Прямой доступ к сервису аналитики
await analyticsService.trackQueryUse("тестовый запрос");

Пример полного использования

<template>
  <div>
    <barneo-search-widget v-model="query" @search="handleSearch" />
    <barneo-products-widget @product-select="handleProductSelect" />

    <!-- Кнопка для тестирования аналитики -->
    <button @click="testAnalytics">🧪 Тестировать аналитику</button>
  </div>
</template>

<script setup>
import { ref } from "vue";
import {
  useAnalytics,
  ProductConversionSource,
} from "barneo-search-widget-lib";

const query = ref("");
const analytics = useAnalytics();

const handleSearch = async (searchQuery) => {
  console.log("Поиск:", searchQuery);

  // Отслеживаем поисковый запрос
  await analytics.trackQueryUse(searchQuery);
};

const handleProductSelect = async (product, event) => {
  console.log("Выбран товар:", product);

  // Отслеживаем клик по товару
  await analytics.quickTrack.productClick(product.id, {
    position: 1,
    query: query.value,
    source: ProductConversionSource.SEARCH,
  });

  // Обработка средней кнопки мыши
  if (event && event.button === 1) {
    window.open(`/product/${product.id}`, "_blank");
  } else {
    router.push(`/product/${product.id}`);
  }
};

const testAnalytics = async () => {
  try {
    // Тестируем все функции аналитики
    await analytics.mergeCustomers("guest-123", "auth-456");
    await analytics.trackQueryUse("тестовый запрос");
    await analytics.quickTrack.filterUse();
    await analytics.quickTrack.productClick("test-product", {
      position: 1,
      source: ProductConversionSource.SEARCH,
    });

    console.log("✅ Все тесты аналитики завершены");
  } catch (error) {
    console.error("❌ Ошибка аналитики:", error);
  }
};
</script>

🔧 Дополнительные возможности

URL менеджер

Библиотека поддерживает управление URL параметрами для сохранения состояния поиска. URL менеджер инициализируется в компонентах через composable.

Инициализация URL менеджера
<script setup>
import { useUrlManagerInit } from "barneo-search-widget-lib";

// Инициализируем URL менеджер для работы с query параметрами
const { urlManager } = useUrlManagerInit();
</script>
Использование в компонентах
<template>
  <div>
    <barneo-search-widget v-model="query" />
    <barneo-filters-widget />
    <barneo-sort-widget />
    <barneo-products-widget />
  </div>
</template>

<script setup>
import { useUrlManagerInit } from "barneo-search-widget-lib";

// Инициализируем URL менеджер в компоненте
const { urlManager } = useUrlManagerInit();

// URL менеджер автоматически синхронизирует состояние с URL параметрами
</script>
Управляемые URL параметры
  • q - поисковый запрос
  • sort - текущая сортировка
  • page - текущая страница
  • filters - активные фильтры (JSON)

Динамическое обновление конфигурации

import { BarneoConfigManager } from "barneo-search-widget-lib";

const configManager = BarneoConfigManager.getInstance();

// Обновление конфигурации поиска
configManager.updateSearchWidgetConfig({
  placeholder: "Новый placeholder",
  darkMode: true,
});

// Обновление конфигурации фильтров
configManager.updateFiltersWidgetConfig({
  maxValuesPerFilter: 10,
  darkMode: true,
});

// Обновление конфигурации товаров
configManager.updateProductsWidgetConfig({
  itemsPerPage: 30,
  showPagination: false,
});

// Обновление конфигурации сортировки
configManager.updateSortWidgetConfig({
  defaultSort: "price",
  darkMode: true,
});

Управление URL параметрами

Библиотека автоматически управляет URL параметрами для сохранения состояния поиска:

  • q - поисковый запрос
  • sort - текущая сортировка
  • page - текущая страница
  • filters - активные фильтры (JSON)
// Пример URL с параметрами
// https://example.com/search?q=сковорода&sort=price&page=2&filters=%7B%22brands%22%3A%5B%22Tefal%22%5D%7D

Использование composables

import {
  useSearchWidget,
  useFiltersWidget,
  useSortWidget,
  useProductsWidget,
  useSharedState,
} from "barneo-search-widget-lib";

// Общее состояние
const { searchState } = useSharedState();

// Поиск
const { performSearch, searchQuery } = useSearchWidget();

// Фильтры
const { applyFilter, clearFilters, activeFilters } = useFiltersWidget();

// Сортировка
const { setSort, currentSort } = useSortWidget();

// Товары
const { products, pagination, changePage } = useProductsWidget();

📘 TypeScript поддержка

Автоматическая типизация глобальных компонентов

При использовании плагина библиотеки глобальные компоненты автоматически получают типизацию:

<template>
  <!-- TypeScript автоматически распознает типы этих компонентов -->
  <barneo-search-widget v-model="query" @search="handleSearch" />
  <barneo-filters-widget @filter-applied="handleFilter" />
  <barneo-sort-widget @sort-changed="handleSort" />
  <barneo-products-widget @product-selected="handleProductSelect" />
  <barneo-range-selector
    ref="rangeSelector"
    :min="0"
    :max="10000"
    @change="handleRangeChange"
  />
</template>
<script setup lang="ts"> // Типизация работает автоматически без дополнительных импортов const query = ref("") const handleSearch = (query: string) => { console.log("Поиск:", query) } </script>

### Ручная типизация (если нужно)

Если вы используете локальную регистрацию компонентов, добавьте типизацию:

```typescript
// types/barneo.d.ts
import type { BarneoSearchWidget } from 'barneo-search-widget-lib'
import type { BarneoFiltersWidget } from 'barneo-search-widget-lib'
import type { BarneoSortWidget } from 'barneo-search-widget-lib'
import type { BarneoProductsWidget } from 'barneo-search-widget-lib'
import type { BarneoRangeSelector } from 'barneo-search-widget-lib'

declare module '@vue/runtime-core' {
  export interface GlobalComponents {
    'barneo-search-widget': typeof BarneoSearchWidget
    'barneo-filters-widget': typeof BarneoFiltersWidget
    'barneo-sort-widget': typeof BarneoSortWidget
    'barneo-products-widget': typeof BarneoProductsWidget
    'barneo-range-selector': typeof BarneoRangeSelector
    BarneoSearchWidget: typeof BarneoSearchWidget
    BarneoFiltersWidget: typeof BarneoFiltersWidget
    BarneoSortWidget: typeof BarneoSortWidget
    BarneoProductsWidget: typeof BarneoProductsWidget
    BarneoRangeSelector: typeof BarneoRangeSelector
  }
}

export {}

Импорт типов

import type {
  BarneoSearchWidget,
  BarneoFiltersWidget,
  BarneoSortWidget,
  BarneoProductsWidget,
  BarneoRangeSelector,
  SearchConfig,
  SearchResult,
  SearchHint,
  SearchApiService,
  SearchRequest,
  SearchResponse,
  // Типы аналитики
  SearchFunction,
  ProductConversionType,
  ProductConversionSource,
  CategoryConversionSource,
  CategoryConversionType,
  AuthorizeCustomerRequest,
  QueryUseRequest,
  FunctionalUseRequest,
  HintsUseRequest,
  ProductsUseRequest,
  CategoriesUseRequest,
  // Типы рекомендаций
  AdviserBlockType,
  AdviserFilterType,
  AdviserSortType,
  LoadingStatus,
  IncludeType,
  FullProduct,
  ProductProperty,
} from "barneo-search-widget-lib";

🤝 Поддержка

  • Vue 3.x
  • TypeScript
  • Nuxt 3
  • Vite
  • Webpack

📄 Лицензия

MIT License

Related Packages

@adkit/meta-pixel-nuxt

Meta (Facebook) Pixel module for Nuxt 3 and 4. Auto-imported composable, per-rou...

1,268 1
mf-vue

Updated Package for Mouseflow installation on Vue.

275
mesh-ui-components

UI Component library

9,059
z0nka1ui

一个基于Vue2的移动端UI组件库

565

Version History

Version Released Vue License
2.2.1 ^3.0.0 MIT
2.2.0 ^3.0.0 MIT
2.1.0 ^3.0.0 MIT
2.0.19 ^3.0.0 MIT
2.0.18 ^3.0.0 MIT