UNPKG

barneo-search-widget-lib

Version:

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

1,121 lines (1,055 loc) 43.4 kB
/** * **Composable для работы с системой рекомендаций Ensi Cloud Adviser** * * Предоставляет реактивные методы для взаимодействия с API рекомендаций Ensi Cloud Adviser. * Позволяет получать умные рекомендации товаров на основе поведения пользователей. * * ## Основные возможности: * - Получение рекомендаций "People also search for this product" - что искали с этим товаром * - Получение рекомендаций "Searchers also viewed" - что смотрели искавшие * - Получение рекомендаций "Cross Sell" - с этим товаром покупают * - Получение рекомендаций "Up Sell" - более дорогие альтернативы * - Получение рекомендаций "Up Sell New" - новая версия up sell * - Получение недавно просмотренных товаров * - Получение популярных товаров * - Получение похожих товаров * - Система кэширования с настраиваемым TTL * - Автоматическая интеграция с BarneoConfigManager * * ## Пример использования: * ```typescript * import { useAdviser } from 'barneo-search-widget-lib' * * const adviser = useAdviser({ * enableCache: true, * cacheTTL: 300000, // 5 минут * apiLimit: 8 * }) * * // Получение рекомендаций * await adviser.getCrossSell('product-123') * await adviser.getPopularProducts() * ``` * * @module useAdviser */ import { ref, computed, readonly, onMounted, onUnmounted, inject } from "vue"; import { AdviserService, type AdviserServiceConfig, } from "../services/AdviserService"; import type { BarneoConfigManager } from "../../../config/BarneoConfigManager"; import type { RecommendationProductsRequest, RecommendationQueryProductsRequest, CrossSellProductsRequest, UpSellProductsRequest, RecentlyWatchedProductsRequest, PopularProductsSearchRequest, SimilarProductsSearchRequest, } from "../types/requests"; import type { RecommendationResponse, PopularProductsResponse, SimilarProductsResponse, FullProduct, } from "../types/responses"; import { AdviserBlockType, LoadingStatus } from "../types/enums"; /** * Интерфейс состояния советника * * Содержит все данные о рекомендациях и состоянии загрузки. * Все поля являются реактивными и автоматически обновляются при получении новых данных. */ interface AdviserState { /** Статус загрузки: 'idle' | 'loading' | 'success' | 'error' */ loadingStatus: LoadingStatus; /** Сообщение об ошибке или null если ошибок нет */ error: string | null; /** Рекомендации "People also search for this product" - что искали с этим товаром */ peopleAlsoSearch: FullProduct[]; /** Рекомендации "Searchers also viewed" - что смотрели искавшие */ searchersAlsoViewed: FullProduct[]; /** Рекомендации "Cross Sell" - с этим товаром покупают */ crossSell: FullProduct[]; /** Рекомендации "Up Sell" - более дорогие альтернативы */ upSell: FullProduct[]; /** Рекомендации "Up Sell New" - новая версия up sell */ upSellNew: FullProduct[]; /** Недавно просмотренные товары */ recentlyWatched: FullProduct[]; /** Популярные товары */ popularProducts: FullProduct[]; /** Похожие товары */ similarProducts: FullProduct[]; /** Время последнего обновления данных */ lastUpdated: Date | null; } /** * Интерфейс настроек советника * * Позволяет настроить поведение composable при инициализации. * Все параметры являются опциональными и имеют разумные значения по умолчанию. */ interface AdviserOptions { /** * Конфигурация сервиса (опционально, если доступен BarneoConfigManager) * Если не указана, будет автоматически получена из BarneoConfigManager */ serviceConfig?: AdviserServiceConfig; /** * Автоматическое обновление данных * @default false */ autoRefresh?: boolean; /** * Интервал обновления в миллисекундах * @default 30000 (30 секунд) */ refreshInterval?: number; /** * Максимальное количество рекомендаций для отображения * @default 10 */ maxRecommendations?: number; /** * Включить кэширование для оптимизации производительности * @default true */ enableCache?: boolean; /** * Время жизни кэша в миллисекундах * @default 300000 (5 минут) */ cacheTTL?: number; /** * Лимит записей для API запросов * @default 5 */ apiLimit?: number; } /** * Composable для работы с системой рекомендаций Ensi Cloud Adviser * * Основная функция для получения умных рекомендаций товаров на основе поведения пользователей. * Автоматически интегрируется с BarneoConfigManager для получения конфигурации API. * * ## Возвращаемые значения: * * ### Состояние: * - `state` - реактивное состояние с данными рекомендаций * - `isLoading` - вычисляемое свойство, true если идет загрузка * - `hasError` - вычисляемое свойство, true если есть ошибка * - `errorMessage` - вычисляемое свойство с текстом ошибки * - `lastUpdateTime` - вычисляемое свойство с временем последнего обновления * * ### Методы получения рекомендаций: * - `getPeopleAlsoSearch()` - что искали с этим товаром * - `getSearchersAlsoViewed()` - что смотрели искавшие * - `getCrossSell()` - с этим товаром покупают * - `getUpSell()` - более дорогие альтернативы * - `getUpSellNew()` - новая версия up sell * - `getRecentlyWatched()` - недавно просмотренные товары * - `getPopularProducts()` - популярные товары * - `getSimilarProducts()` - похожие товары * * ### Утилиты: * - `clearData()` - очистить все данные * - `refreshData()` - обновить все данные * - `clearCache()` - очистить кэш * - `forceClear()` - принудительно очистить все данные и кэш * - `startAutoRefresh()` - запустить автообновление * - `stopAutoRefresh()` - остановить автообновление * * @param options - Опциональные настройки для настройки поведения * @returns Объект с состоянием и методами для работы с рекомендациями * * @example * ```typescript * const adviser = useAdviser({ * enableCache: true, * maxRecommendations: 8, * apiLimit: 10 * }) * * // Получение рекомендаций * await adviser.getCrossSell('product-123') * await adviser.getPopularProducts() * * // Проверка состояния * console.log('Загрузка:', adviser.isLoading.value) * console.log('Ошибка:', adviser.errorMessage.value) * console.log('Данные:', adviser.state.value.crossSell) * ``` */ export function useAdviser(options?: Partial<AdviserOptions>) { // Получаем менеджер конфигурации из Vue контекста или напрямую const configManager = inject<BarneoConfigManager>("barneoConfigManager") || (typeof window !== "undefined" ? (window as any).__barneoConfigManager : null); const { autoRefresh = false, refreshInterval = 30000, // 30 секунд maxRecommendations = 10, enableCache = true, cacheTTL = 300000, // 5 минут apiLimit = 5, // Лимит для API запросов } = options || {}; // Автоматически создаем конфигурацию из менеджера, если не передана let serviceConfig: AdviserServiceConfig; if (options?.serviceConfig) { serviceConfig = { ...options.serviceConfig, apiLimit }; } else if (configManager) { try { const apiConfig = configManager.getApiConfig(); serviceConfig = { baseUrl: apiConfig.baseUrl, token: apiConfig.token, customerId: apiConfig.customerId, locationId: apiConfig.locationId, apiVersion: apiConfig.apiVersion, // Может быть undefined apiLimit, }; } catch (err) { console.warn("Failed to initialize adviser with config manager:", err); throw new Error( "Adviser requires serviceConfig when BarneoConfigManager is not available" ); } } else { throw new Error( "Adviser requires serviceConfig when BarneoConfigManager is not available" ); } // Создаем экземпляр сервиса const adviserService = new AdviserService(serviceConfig); // Состояние const state = ref<AdviserState>({ loadingStatus: LoadingStatus.IDLE, error: null, peopleAlsoSearch: [], searchersAlsoViewed: [], crossSell: [], upSell: [], upSellNew: [], recentlyWatched: [], popularProducts: [], similarProducts: [], lastUpdated: null, }); // Кэш const cache = ref<Map<string, { data: any; timestamp: number }>>(new Map()); // Принудительно очищаем состояние при инициализации, если кэш отключен if (!enableCache) { console.log("🧹 Очищаем кэш и состояние (enableCache: false)"); console.log("📊 Размер кэша до очистки:", cache.value.size); // Очищаем кэш cache.value.clear(); console.log("📊 Размер кэша после очистки:", cache.value.size); // Сбрасываем состояние state.value = { loadingStatus: LoadingStatus.IDLE, error: null, peopleAlsoSearch: [], searchersAlsoViewed: [], crossSell: [], upSell: [], upSellNew: [], recentlyWatched: [], popularProducts: [], similarProducts: [], lastUpdated: null, }; console.log("✅ Состояние очищено"); } // Таймер для автообновления let refreshTimer: NodeJS.Timeout | null = null; // Вычисляемые свойства const isLoading = computed( () => state.value.loadingStatus === LoadingStatus.LOADING ); const hasError = computed(() => !!state.value.error); const errorMessage = computed(() => state.value.error); const lastUpdateTime = computed(() => state.value.lastUpdated); /** * Проверить актуальность кэша по ключу * * Проверяет, не истек ли срок действия кэшированных данных для указанного ключа. * * @param key - Ключ кэша для проверки * @returns true если кэш актуален, false если истек или отсутствует * * @internal */ const isCacheValid = (key: string): boolean => { if (!enableCache) return false; const cached = cache.value.get(key); if (!cached) return false; return Date.now() - cached.timestamp < cacheTTL; }; /** * Получить данные из кэша по ключу * * Извлекает данные из кэша, если они актуальны. * * @param key - Ключ кэша для получения данных * @returns Кэшированные данные или null если кэш неактуален или отсутствует * * @internal */ const getFromCache = <T>(key: string): T | null => { if (!enableCache || !isCacheValid(key)) return null; const cached = cache.value.get(key)?.data as T; if (cached) { console.log("📋 Данные получены из кэша:", key); } return cached; }; /** * Сохранить данные в кэш * * Сохраняет данные в кэш с текущим временем для последующего использования. * * @param key - Ключ кэша для сохранения данных * @param data - Данные для сохранения в кэше * * @internal */ const setCache = <T>(key: string, data: T): void => { if (!enableCache) return; cache.value.set(key, { data, timestamp: Date.now(), }); }; /** * Очистить кэш рекомендаций * * Очищает внутренний кэш, заставляя все последующие запросы обращаться к API. * Полезно для получения свежих данных при изменении конфигурации. * * @example * ```typescript * // Очистить кэш * adviser.clearCache() * * // Теперь запросы будут обращаться к API * await adviser.getPopularProducts() * ``` */ const clearCache = (): void => { cache.value.clear(); }; /** * Принудительно очистить все данные и кэш * * Полностью очищает все данные рекомендаций и кэш, сбрасывая состояние * к начальному. Полезно для полного сброса при тестировании или смене пользователя. * * @example * ```typescript * // Принудительно очистить все * adviser.forceClear() * * // Проверка очистки * console.log('Все очищено:', { * hasData: adviser.state.value.crossSell.length === 0, * hasError: adviser.state.value.error === null, * status: adviser.state.value.loadingStatus === 'idle' * }) * ``` */ const forceClear = (): void => { cache.value.clear(); state.value = { loadingStatus: LoadingStatus.IDLE, error: null, peopleAlsoSearch: [], searchersAlsoViewed: [], crossSell: [], upSell: [], upSellNew: [], recentlyWatched: [], popularProducts: [], similarProducts: [], lastUpdated: null, }; }; /** * Установить состояние загрузки * * Обновляет статус загрузки и время последнего обновления. * * @param loading - true для установки статуса загрузки, false для успешного завершения * * @internal */ const setLoading = (loading: boolean): void => { state.value.loadingStatus = loading ? LoadingStatus.LOADING : LoadingStatus.SUCCESS; if (!loading) { state.value.lastUpdated = new Date(); } }; /** * Установить ошибку * * Обновляет сообщение об ошибке и статус загрузки. * * @param error - Сообщение об ошибке или null для очистки ошибки * * @internal */ const setError = (error: string | null): void => { state.value.error = error; state.value.loadingStatus = error ? LoadingStatus.ERROR : LoadingStatus.SUCCESS; }; /** * Получить рекомендации "People also search for this product" * * Предлагает пользователям товары, которые были просмотрены другими покупателями * с идентичным поисковым запросом. Это помогает расширить выбор и повысить * вероятность покупки. * * **Рекомендации по использованию:** Используйте этот блок на странице поиска при * нулевых результатах, что позволит удержать клиента, если нужный товар не найден. * * @param productId - ID товара для получения рекомендаций * @param request - Опциональные параметры запроса (сортировка, пагинация, загрузка полных данных) * * @example * ```typescript * // Базовое использование * await adviser.getPeopleAlsoSearch('product-123') * * // С дополнительными параметрами * await adviser.getPeopleAlsoSearch('product-123', { * pagination: { limit: 10 }, * sort: 'name', * load_full_products: { * location_id: '1', * include: ['properties'] * } * }) * * // Проверка результата * console.log('Рекомендации:', adviser.state.value.peopleAlsoSearch) * ``` */ const getPeopleAlsoSearch = async ( productId: string, request?: Partial<Omit<RecommendationProductsRequest, "filter">> ): Promise<void> => { console.log("🔍 getPeopleAlsoSearch вызван для productId:", productId); console.log("📊 Состояние ДО вызова:", { peopleAlsoSearch: state.value.peopleAlsoSearch.length, totalProducts: Object.values(state.value) .filter(Array.isArray) .reduce((sum, arr) => sum + arr.length, 0), }); try { setLoading(true); setError(null); const cacheKey = `people_also_search_${productId}_${JSON.stringify( request )}`; const cached = getFromCache<RecommendationResponse>(cacheKey); if (cached) { const fullProducts = cached.data?.full_products || []; state.value.peopleAlsoSearch = fullProducts.slice( 0, maxRecommendations ); console.log("📋 Данные получены из кэша для peopleAlsoSearch"); return; } const response = await adviserService.getRecommendationProducts( productId, request ); const fullProducts = response.data?.full_products || []; state.value.peopleAlsoSearch = fullProducts.slice(0, maxRecommendations); setCache(cacheKey, response); console.log("🌐 Данные получены из API для peopleAlsoSearch"); } catch (error) { setError(error instanceof Error ? error.message : "Неизвестная ошибка"); console.error( "Ошибка при получении рекомендаций 'People also search':", error ); } finally { setLoading(false); } console.log("📊 Состояние ПОСЛЕ вызова:", { peopleAlsoSearch: state.value.peopleAlsoSearch.length, totalProducts: Object.values(state.value) .filter(Array.isArray) .reduce((sum, arr) => sum + arr.length, 0), }); }; /** * Получить рекомендации "Searchers also viewed" * * Предлагает пользователям товары, которые были просмотрены другими покупателями * с идентичным поисковым запросом. Это помогает расширить выбор и повысить * вероятность покупки. * * **Рекомендации по использованию:** Используйте этот блок на странице поиска при * нулевых результатах, что позволит удержать клиента, если нужный товар не найден. * * @param query - Поисковый запрос для получения рекомендаций * @param request - Опциональные параметры запроса (сортировка, пагинация, загрузка полных данных) * * @example * ```typescript * // Базовое использование * await adviser.getSearchersAlsoViewed('сковорода') * * // С дополнительными параметрами * await adviser.getSearchersAlsoViewed('сковорода', { * pagination: { limit: 8 }, * sort: '-price', * load_full_products: { * location_id: '1', * include: ['properties'] * } * }) * * // Проверка результата * console.log('Рекомендации:', adviser.state.value.searchersAlsoViewed) * ``` */ const getSearchersAlsoViewed = async ( query: string, request?: Partial<Omit<RecommendationQueryProductsRequest, "filter">> ): Promise<void> => { try { setLoading(true); setError(null); const cacheKey = `searchers_also_viewed_${query}_${JSON.stringify( request )}`; const cached = getFromCache<RecommendationResponse>(cacheKey); if (cached) { const fullProducts = cached.data?.full_products || []; state.value.searchersAlsoViewed = fullProducts.slice( 0, maxRecommendations ); return; } const response = await adviserService.getRecommendationQueryProducts( query, request ); const fullProducts = response.data?.full_products || []; state.value.searchersAlsoViewed = fullProducts.slice( 0, maxRecommendations ); setCache(cacheKey, response); } catch (error) { setError(error instanceof Error ? error.message : "Неизвестная ошибка"); console.error( "Ошибка при получении рекомендаций 'Searchers also viewed':", error ); } finally { setLoading(false); } }; /** * Получить рекомендации "Cross Sell" (с этим товаром покупают) * * Рекомендует сопутствующие товары, которые часто покупаются вместе с основным * товаром. Это увеличивает средний чек за счет дополнительных продаж. * * **Рекомендации по использованию:** Размещайте этот блок на страницах товара и в * корзине. * * @param productId - ID товара для получения рекомендаций * @param request - Опциональные параметры запроса (сортировка, пагинация, загрузка полных данных) * * @example * ```typescript * // Базовое использование * await adviser.getCrossSell('product-123') * * // С дополнительными параметрами * await adviser.getCrossSell('product-123', { * pagination: { limit: 6 }, * sort: 'price', * load_full_products: { * location_id: '1', * include: ['properties'] * } * }) * * // Проверка результата * console.log('Сопутствующие товары:', adviser.state.value.crossSell) * ``` */ const getCrossSell = async ( productId: string, request?: Partial<Omit<CrossSellProductsRequest, "filter">> ): Promise<void> => { try { setLoading(true); setError(null); const cacheKey = `cross_sell_${productId}_${JSON.stringify(request)}`; const cached = getFromCache<RecommendationResponse>(cacheKey); if (cached) { const fullProducts = cached.data?.full_products || []; state.value.crossSell = fullProducts.slice(0, maxRecommendations); return; } const response = await adviserService.getCrossSellProducts( productId, request ); const fullProducts = response.data?.full_products || []; state.value.crossSell = fullProducts.slice(0, maxRecommendations); setCache(cacheKey, response); } catch (error) { setError(error instanceof Error ? error.message : "Неизвестная ошибка"); console.error("Ошибка при получении рекомендаций 'Cross Sell':", error); } finally { setLoading(false); } }; /** * Получить рекомендации "Up Sell" (более дорогие альтернативы) * * Предлагает более дорогие или качественные альтернативы выбранному товару. Это * может помочь повысить прибыль за счет увеличения среднего чека. * * **Рекомендации по использованию:** Размещайте этот блок на страницах товара. * * @param productId - ID товара для получения рекомендаций * @param request - Опциональные параметры запроса (сортировка, пагинация, загрузка полных данных) * * @example * ```typescript * // Базовое использование * await adviser.getUpSell('product-123') * * // С дополнительными параметрами * await adviser.getUpSell('product-123', { * pagination: { limit: 5 }, * sort: 'price', * load_full_products: { * location_id: '1', * include: ['properties'] * } * }) * * // Проверка результата * console.log('Дорогие альтернативы:', adviser.state.value.upSell) * ``` */ const getUpSell = async ( productId: string, request?: Partial<Omit<UpSellProductsRequest, "filter">> ): Promise<void> => { try { setLoading(true); setError(null); const cacheKey = `up_sell_${productId}_${JSON.stringify(request)}`; const cached = getFromCache<RecommendationResponse>(cacheKey); if (cached) { const fullProducts = cached.data?.full_products || []; state.value.upSell = fullProducts.slice(0, maxRecommendations); return; } const response = await adviserService.getUpSellProducts( productId, request ); const fullProducts = response.data?.full_products || []; state.value.upSell = fullProducts.slice(0, maxRecommendations); setCache(cacheKey, response); } catch (error) { setError(error instanceof Error ? error.message : "Неизвестная ошибка"); console.error("Ошибка при получении рекомендаций 'Up Sell':", error); } finally { setLoading(false); } }; /** * Получить рекомендации "Up Sell New" (новая версия up sell) * * Новая версия алгоритма Up Sell, предлагающая более дорогие или качественные * альтернативы выбранному товару с улучшенной логикой подбора. * * **Рекомендации по использованию:** Размещайте этот блок на страницах товара. * * @param productId - ID товара для получения рекомендаций * @param request - Опциональные параметры запроса (сортировка, пагинация, загрузка полных данных) * * @example * ```typescript * // Базовое использование * await adviser.getUpSellNew('product-123') * * // С дополнительными параметрами * await adviser.getUpSellNew('product-123', { * pagination: { limit: 5 }, * sort: 'price', * load_full_products: { * location_id: '1', * include: ['properties'] * } * }) * * // Проверка результата * console.log('Новые дорогие альтернативы:', adviser.state.value.upSellNew) * ``` */ const getUpSellNew = async ( productId: string, request?: Partial<Omit<UpSellProductsRequest, "filter">> ): Promise<void> => { try { setLoading(true); setError(null); const cacheKey = `up_sell_new_${productId}_${JSON.stringify(request)}`; const cached = getFromCache<RecommendationResponse>(cacheKey); if (cached) { const fullProducts = cached.data?.full_products || []; state.value.upSellNew = fullProducts.slice(0, maxRecommendations); return; } const response = await adviserService.getUpSellProductsNew( productId, request ); const fullProducts = response.data?.full_products || []; state.value.upSellNew = fullProducts.slice(0, maxRecommendations); setCache(cacheKey, response); } catch (error) { setError(error instanceof Error ? error.message : "Неизвестная ошибка"); console.error("Ошибка при получении рекомендаций 'Up Sell New':", error); } finally { setLoading(false); } }; /** * Получить недавно просмотренные товары * * Позволяет пользователям быстро вернуться к ранее просмотренным товарам, что * повышает вероятность завершения покупки. * * **Рекомендации по использованию:** Размещайте этот блок в личных кабинетах * пользователей или на страницах товаров для легкого доступа к интересующим * позициям. * * @param customerId - ID клиента для получения истории просмотров * @param request - Опциональные параметры запроса (пагинация, загрузка полных данных) * * @example * ```typescript * // Базовое использование * await adviser.getRecentlyWatched('customer-456') * * // С дополнительными параметрами * await adviser.getRecentlyWatched('customer-456', { * pagination: { limit: 10 }, * load_full_products: { * location_id: '1', * include: ['properties'] * } * }) * * // Проверка результата * console.log('Недавно просмотренные:', adviser.state.value.recentlyWatched) * ``` */ const getRecentlyWatched = async ( customerId: string, request?: Partial<Omit<RecentlyWatchedProductsRequest, "filter">> ): Promise<void> => { try { setLoading(true); setError(null); const cacheKey = `recently_watched_${customerId}_${JSON.stringify( request )}`; const cached = getFromCache<RecommendationResponse>(cacheKey); if (cached) { const fullProducts = cached.data?.full_products || []; state.value.recentlyWatched = fullProducts.slice(0, maxRecommendations); return; } const response = await adviserService.getRecentlyWatchedProducts( customerId, request ); const fullProducts = response.data?.full_products || []; state.value.recentlyWatched = fullProducts.slice(0, maxRecommendations); setCache(cacheKey, response); } catch (error) { setError(error instanceof Error ? error.message : "Неизвестная ошибка"); console.error( "Ошибка при получении недавно просмотренных товаров:", error ); } finally { setLoading(false); } }; /** * Получить популярные товары * * Демонстрирует товары, которые пользуются высоким спросом среди других * покупателей. Это создает ощущение доверия и может побудить пользователей к * покупке. * * **Рекомендации по использованию:** Размещайте этот блок на главной странице или * странице корзины, чтобы привлечь внимание к наиболее востребованным товарам. * * @param request - Опциональные параметры запроса (сортировка, пагинация, загрузка полных данных) * * @example * ```typescript * // Базовое использование * await adviser.getPopularProducts() * * // С дополнительными параметрами * await adviser.getPopularProducts({ * pagination: { limit: 12 }, * sort: '-popularity', * load_full_products: { * location_id: '1', * include: ['properties'] * } * }) * * // Проверка результата * console.log('Популярные товары:', adviser.state.value.popularProducts) * ``` */ const getPopularProducts = async ( request?: Partial<PopularProductsSearchRequest> ): Promise<void> => { try { setLoading(true); setError(null); const cacheKey = `popular_products_${JSON.stringify(request)}`; const cached = getFromCache<PopularProductsResponse>(cacheKey); if (cached) { const fullProducts = cached.data?.full_products || []; state.value.popularProducts = fullProducts.slice(0, maxRecommendations); return; } const response = await adviserService.getPopularProducts(request); const fullProducts = response.data?.full_products || []; state.value.popularProducts = fullProducts.slice(0, maxRecommendations); setCache(cacheKey, response); } catch (error) { setError(error instanceof Error ? error.message : "Неизвестная ошибка"); console.error("Ошибка при получении популярных товаров:", error); } finally { setLoading(false); } }; /** * Получить похожие товары * * Демонстрирует связанные товары, которые имеют сходство по одной из характеристик * – категория, бренд или единая группа (например, один товар разных цветов). * * **Рекомендации по использованию:** Размещайте этот блок на страницах товара. * * @param request - Опциональные параметры запроса (фильтр по товару, сортировка, пагинация, загрузка полных данных) * * @example * ```typescript * // Базовое использование * await adviser.getSimilarProducts() * * // С дополнительными параметрами * await adviser.getSimilarProducts({ * filter: { product_id: 'product-123' }, * pagination: { limit: 8 }, * sort: 'name', * load_full_products: { * location_id: '1', * include: ['properties'] * } * }) * * // Проверка результата * console.log('Похожие товары:', adviser.state.value.similarProducts) * ``` */ const getSimilarProducts = async ( request?: Partial<SimilarProductsSearchRequest> ): Promise<void> => { try { setLoading(true); setError(null); const cacheKey = `similar_products_${JSON.stringify(request)}`; const cached = getFromCache<SimilarProductsResponse>(cacheKey); if (cached) { const fullProducts = cached.data?.full_products || []; state.value.similarProducts = fullProducts.slice(0, maxRecommendations); return; } const response = await adviserService.getSimilarProducts(request); const fullProducts = response.data?.full_products || []; state.value.similarProducts = fullProducts.slice(0, maxRecommendations); setCache(cacheKey, response); } catch (error) { setError(error instanceof Error ? error.message : "Неизвестная ошибка"); console.error("Ошибка при получении похожих товаров:", error); } finally { setLoading(false); } }; /** * Очистить все данные рекомендаций * * Очищает все массивы рекомендаций в состоянии, но сохраняет кэш. * Полезно для сброса отображаемых данных без потери кэшированных результатов. * * @example * ```typescript * // Очистить все данные * adviser.clearData() * * // Проверка результата * console.log('Данные очищены:', adviser.state.value.crossSell.length === 0) * ``` */ const clearData = (): void => { state.value.peopleAlsoSearch = []; state.value.searchersAlsoViewed = []; state.value.crossSell = []; state.value.upSell = []; state.value.upSellNew = []; state.value.recentlyWatched = []; state.value.popularProducts = []; state.value.similarProducts = []; state.value.error = null; clearCache(); }; /** * Обновить все данные рекомендаций * * Очищает все данные и кэш, подготавливая к новым запросам. * Полезно для принудительного обновления всех рекомендаций. * * @example * ```typescript * // Обновить все данные * await adviser.refreshData() * * // Теперь можно загружать новые данные * await adviser.getPopularProducts() * ``` */ const refreshData = async (): Promise<void> => { clearData(); // Здесь можно добавить логику для обновления всех данных }; /** * Запустить автообновление данных * * Запускает автоматическое обновление данных с заданным интервалом. * Работает только если `autoRefresh: true` в настройках. * * @example * ```typescript * // Запустить автообновление * adviser.startAutoRefresh() * * // Проверка активности * console.log('Автообновление запущено') * ``` */ const startAutoRefresh = (): void => { if (autoRefresh && !refreshTimer) { refreshTimer = setInterval(() => { refreshData(); }, refreshInterval); } }; /** * Остановить автообновление данных * * Останавливает автоматическое обновление данных. * Полезно для экономии ресурсов при неактивном состоянии. * * @example * ```typescript * // Остановить автообновление * adviser.stopAutoRefresh() * * // Проверка остановки * console.log('Автообновление остановлено') * ``` */ const stopAutoRefresh = (): void => { if (refreshTimer) { clearInterval(refreshTimer); refreshTimer = null; } }; // Жизненный цикл onMounted(() => { if (autoRefresh) { startAutoRefresh(); } }); onUnmounted(() => { stopAutoRefresh(); }); return { // Состояние /** Реактивное состояние с данными рекомендаций (только для чтения) */ state: readonly(state), /** Вычисляемое свойство: true если идет загрузка */ isLoading, /** Вычисляемое свойство: true если есть ошибка */ hasError, /** Вычисляемое свойство: текст ошибки или null */ errorMessage, /** Вычисляемое свойство: время последнего обновления */ lastUpdateTime, // Методы получения рекомендаций /** Получить рекомендации "People also search for this product" - что искали с этим товаром */ getPeopleAlsoSearch, /** Получить рекомендации "Searchers also viewed" - что смотрели искавшие */ getSearchersAlsoViewed, /** Получить рекомендации "Cross Sell" - с этим товаром покупают */ getCrossSell, /** Получить рекомендации "Up Sell" - более дорогие альтернативы */ getUpSell, /** Получить рекомендации "Up Sell New" - новая версия up sell */ getUpSellNew, /** Получить недавно просмотренные товары */ getRecentlyWatched, /** Получить популярные товары */ getPopularProducts, /** Получить похожие товары */ getSimilarProducts, // Утилиты /** Очистить все данные рекомендаций */ clearData, /** Обновить все данные рекомендаций */ refreshData, /** Запустить автообновление данных */ startAutoRefresh, /** Остановить автообновление данных */ stopAutoRefresh, /** Очистить кэш рекомендаций */ clearCache, /** Принудительно очистить все данные и кэш */ forceClear, }; }