UNPKG

barneo-search-widget-lib

Version:

Библиотека для поиска по каталогу Barneo на Vue 3

256 lines (224 loc) 8.82 kB
import { ref, computed, watch } from "vue"; import type { ProductsWidgetConfig, ProductsWidgetEmits } from "../types"; import type { SearchApiService } from "../../searchWidget/types"; import { useProductsApi } from "./useProductsApi"; import { useSharedState } from "../../sharedState/composables/useSharedState"; import { useBarneoConfig } from "../../../config/BarneoConfigManager"; export function useProductsWidget( config: ProductsWidgetConfig, apiService: SearchApiService | undefined, emit: ProductsWidgetEmits ) { const api = useProductsApi(apiService); const sharedState = useSharedState(); const configManager = useBarneoConfig(); // Простое состояние загрузки const isLoading = ref(false); const error = ref<string | null>(null); // Синхронизируем состояние загрузки с sharedState watch( () => sharedState.searchState.isLoading, (loading) => { isLoading.value = loading; } ); // Пагинация const currentPage = ref(1); const itemsPerPage = config.itemsPerPage || 20; // Следим за изменением запроса и сбрасываем страницу watch( () => sharedState.searchState.query, () => { currentPage.value = 1; } ); // Следим за изменением активных фильтров и сбрасываем страницу watch( () => sharedState.searchState.activeFilters, (newFilters) => { // Проверяем, не происходит ли восстановление из URL // Если пагинация уже установлена правильно (не на первой странице), то не сбрасываем const expectedPage = Math.floor( sharedState.searchState.pagination.offset_products / itemsPerPage ) + 1; const isRestoringFromUrl = expectedPage > 1 && currentPage.value === expectedPage; if (!isRestoringFromUrl) { currentPage.value = 1; } }, { deep: true } ); // Следим за изменением сортировки и сбрасываем страницу watch( () => sharedState.searchState.sort, () => { currentPage.value = 1; } ); // Следим за изменением offset_products в sharedState и синхронизируем currentPage watch( () => sharedState.searchState.pagination.offset_products, (newOffset) => { const newPage = Math.floor(newOffset / itemsPerPage) + 1; if (newPage !== currentPage.value) { currentPage.value = newPage; } } ); // Вычисляем общее количество страниц const totalPages = computed(() => { const total = sharedState.searchState.totalProducts || 0; return Math.ceil(total / itemsPerPage); }); // Маппинг внутренних кодов фильтров на названия полей API const filterCodeMapping: Record<string, string> = { brand: "brands", category: "category_ids", country: "countries", availability: "availability", }; // Функция для получения активных фильтров из sharedState const getActiveFilters = () => { const activeFilters = sharedState.searchState.activeFilters; return activeFilters.reduce((acc, filter) => { const apiFieldName = filterCodeMapping[filter.code] || filter.code; acc[apiFieldName] = filter.values .filter((v) => v.isSelected) .map((v) => v.id); return acc; }, {} as Record<string, string[]>); }; // Функция для получения range фильтров из sharedState const getRangeFilters = () => { const activeProducts = sharedState.searchState.activeProducts; return activeProducts .filter((product) => product.value_gte || product.value_lte) .map((product) => ({ code: product.code, value_gte: product.value_gte, value_lte: product.value_lte, })); }; // Функция для загрузки товаров с пагинацией const loadProducts = async (page: number = 1) => { if (!apiService || !sharedState.searchState.query?.trim()) { return; } // Устанавливаем состояние загрузки через sharedState sharedState.setLoading(true); error.value = null; emit("loading", true); try { const offset = (page - 1) * itemsPerPage; const activeFilters = getActiveFilters(); const rangeFilters = getRangeFilters(); // Обновляем пагинацию в sharedState sharedState.setPagination({ limit_products: itemsPerPage, limit_categories: 10, limit_brands: 10, offset_products: offset, }); const response = await api.searchProducts({ query: sharedState.searchState.query, offset, limit: itemsPerPage, filters: activeFilters, products: rangeFilters.length > 0 ? rangeFilters : undefined, sort: sharedState.searchState.sort, }); if (response) { // Обновляем sharedState с новыми товарами const fullProducts = response.products.map((p) => ({ id: p.id, name: p.name, description: p.description, price: p.price, picture: p.image, url: p.url, properties: p.properties, })); sharedState.setSearchResults( sharedState.searchState.query, [...sharedState.searchState.results], sharedState.searchState.isDeepSearch, fullProducts, response.totalProducts ); emit("products-updated", response.products); } } catch (err) { const errorMessage = err instanceof Error ? err.message : "Ошибка загрузки товаров"; error.value = errorMessage; emit("error", errorMessage); } finally { // Снимаем состояние загрузки через sharedState sharedState.setLoading(false); emit("loading", false); } }; const handlePageChange = async (page: number) => { if (page !== currentPage.value && page >= 1 && page <= totalPages.value) { // Отслеживаем использование пагинации для аналитики try { await configManager.apiService.useSearchFunction("filter"); } catch (err) { // Игнорируем ошибки отслеживания } // Сразу устанавливаем текущую страницу currentPage.value = page; emit("page-change", page); // Плавно прокручиваем к началу товаров const productsContainer = document.querySelector( ".barneo-products-container" ); if (productsContainer) { productsContainer.scrollIntoView({ behavior: "smooth", block: "start", }); } // Затем загружаем данные await loadProducts(page); } }; const handleProductSelect = async (product: any, event?: MouseEvent) => { // Отслеживаем конверсию товара для аналитики try { // Вычисляем позицию товара в текущем списке const currentProducts = sharedState.searchState.full_products || []; const productPosition = currentProducts.findIndex((p) => p.id === product.id) + 1; await configManager.apiService.trackProductConversion({ product_id: product.id, conversion: "click", source: "search", position: productPosition > 0 ? productPosition : undefined, query: sharedState.searchState.query || undefined, }); } catch (err) { // Игнорируем ошибки отслеживания } emit("product-select", product, event); }; return { // State isLoading: computed(() => isLoading.value), error: computed(() => error.value), // Pagination pagination: computed(() => ({ currentPage: currentPage.value, totalPages: totalPages.value, totalProducts: sharedState.searchState.totalProducts || 0, itemsPerPage, offset: (currentPage.value - 1) * itemsPerPage, })), // Methods loadProducts, handlePageChange, handleProductSelect, }; }