barneo-search-widget-lib
Version:
Библиотека для поиска по каталогу Barneo на Vue 3
356 lines (309 loc) • 10.4 kB
text/typescript
import { ref, reactive, computed, watch, readonly } from "vue";
import type {
SearchState,
FilterState,
SharedStateConfig,
SharedStateEvents,
} from "../types";
// Глобальный экземпляр sharedState
let globalSharedState: ReturnType<typeof createSharedState> | null = null;
/**
* Создает экземпляр sharedState
*/
function createSharedState(config: SharedStateConfig = {}) {
// Основное состояние поиска
const searchState = reactive<SearchState>({
query: "",
results: [],
totalResults: 0,
full_products: [],
totalProducts: 0,
filters: [],
activeFilters: [],
products: [],
activeProducts: [],
sort: "relevance",
pagination: {
limit_products: 20,
limit_categories: 10,
limit_brands: 10,
offset_products: 0,
},
isLoading: false,
error: null,
searchTimestamp: 0,
isDeepSearch: false,
});
// События для уведомления других виджетов
const events = ref<SharedStateEvents | null>(null);
// Вычисляемые свойства
const hasResults = computed(() => searchState.results.length > 0);
const hasActiveFilters = computed(() => searchState.activeFilters.length > 0);
const isEmpty = computed(() => !searchState.query && !hasActiveFilters.value);
/**
* Устанавливает результаты поиска (вызывается из searchWidget)
*/
const setSearchResults = (
query: string,
results: any[],
isDeepSearch: boolean = false,
fullProducts?: any[],
totalProducts?: number
) => {
searchState.query = query;
searchState.results = results;
searchState.totalResults = results.length;
searchState.full_products = fullProducts || results;
searchState.totalProducts = totalProducts || results.length;
searchState.isDeepSearch = isDeepSearch;
searchState.searchTimestamp = Date.now();
searchState.error = null;
// Уведомляем другие виджеты
events.value?.onSearchResults(searchState);
};
/**
* Устанавливает фильтры из API (вызывается из searchWidget)
*/
const setFilters = (filters: any[]) => {
searchState.filters = filters;
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onSearchResults(searchState);
};
/**
* Устанавливает состояние загрузки
*/
const setLoading = (loading: boolean) => {
searchState.isLoading = loading;
events.value?.onSearchLoading(loading);
};
/**
* Устанавливает ошибку
*/
const setError = (error: string) => {
searchState.error = error;
events.value?.onSearchError(error);
};
/**
* Применяет фильтр (вызывается из filtersWidget)
*/
const applyFilter = (filter: FilterState) => {
const existingFilterIndex = searchState.activeFilters.findIndex(
(f) => f.id === filter.id
);
if (existingFilterIndex >= 0) {
searchState.activeFilters[existingFilterIndex] = filter;
} else {
searchState.activeFilters.push(filter);
}
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onFilterApplied(filter);
events.value?.onSearchResults(searchState);
// Автоматическое обновление результатов при изменении фильтров
if (config.autoUpdateOnFilterChange) {
// Здесь можно добавить логику для автоматического обновления результатов
}
};
/**
* Удаляет фильтр (вызывается из filtersWidget)
*/
const removeFilter = (filterId: string) => {
const filterIndex = searchState.activeFilters.findIndex(
(f) => f.id === filterId
);
if (filterIndex >= 0) {
const removedFilter = searchState.activeFilters[filterIndex];
searchState.activeFilters.splice(filterIndex, 1);
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onFilterRemoved(filterId);
events.value?.onSearchResults(searchState);
}
};
/**
* Устанавливает активные фильтры
*/
const setActiveFilters = (filters: FilterState[]) => {
searchState.activeFilters = filters;
searchState.searchTimestamp = Date.now();
};
/**
* Очищает все фильтры
*/
const clearFilters = () => {
searchState.filters = [];
searchState.activeFilters = [];
searchState.activeProducts = [];
searchState.searchTimestamp = Date.now();
};
/**
* Обновляет продукты (вызывается из productsWidget)
*/
const updateProducts = (products: any[]) => {
searchState.full_products = products;
searchState.totalProducts = products.length;
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onProductsUpdated(products);
};
/**
* Выбирает продукт (вызывается из productsWidget)
*/
const selectProduct = (product: any) => {
// Уведомляем другие виджеты
events.value?.onProductSelected(product);
};
/**
* Сбрасывает все состояние
*/
const reset = () => {
searchState.query = "";
searchState.results = [];
searchState.totalResults = 0;
searchState.full_products = [];
searchState.totalProducts = 0;
searchState.filters = [];
searchState.activeFilters = [];
searchState.activeProducts = [];
searchState.sort = "relevance";
searchState.pagination = {
limit_products: 20,
limit_categories: 10,
limit_brands: 10,
offset_products: 0,
};
searchState.isLoading = false;
searchState.error = null;
searchState.searchTimestamp = 0;
searchState.isDeepSearch = false;
};
/**
* Устанавливает сортировку
*/
const setSort = (sort: string) => {
searchState.sort = sort;
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onSearchResults(searchState);
};
/**
* Устанавливает пагинацию
*/
const setPagination = (pagination: {
limit_products?: number;
limit_categories?: number;
limit_brands?: number;
offset_products?: number;
}) => {
searchState.pagination = {
...searchState.pagination,
...pagination,
};
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onSearchResults(searchState);
};
/**
* Сбрасывает пагинацию (возвращает на первую страницу)
*/
const resetPagination = () => {
searchState.pagination.offset_products = 0;
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onSearchResults(searchState);
};
/**
* Устанавливает продуктовые фильтры
*/
const setProducts = (products: any[]) => {
searchState.products = products;
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onSearchResults(searchState);
};
/**
* Очищает продуктовые фильтры
*/
const clearProducts = () => {
searchState.products = [];
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onSearchResults(searchState);
};
/**
* Устанавливает активные продуктовые фильтры
*/
const setActiveProducts = (activeProducts: any[]) => {
searchState.activeProducts = activeProducts;
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onSearchResults(searchState);
};
/**
* Очищает активные продуктовые фильтры
*/
const clearActiveProducts = () => {
searchState.activeProducts = [];
searchState.searchTimestamp = Date.now();
// Уведомляем другие виджеты
events.value?.onSearchResults(searchState);
};
/**
* Регистрирует обработчики событий
*/
const registerEvents = (eventHandlers: SharedStateEvents) => {
events.value = eventHandlers;
};
/**
* Отменяет регистрацию обработчиков событий
*/
const unregisterEvents = () => {
events.value = null;
};
return {
// Состояние (только для чтения)
searchState: readonly(searchState),
// Вычисляемые свойства
hasResults,
hasActiveFilters,
isEmpty,
// Методы для управления состоянием
setSearchResults,
setFilters,
setLoading,
setError,
applyFilter,
removeFilter,
setActiveFilters,
clearFilters,
setProducts,
clearProducts,
setActiveProducts,
clearActiveProducts,
updateProducts,
selectProduct,
reset,
setSort,
setPagination,
resetPagination,
// Методы для управления событиями
registerEvents,
unregisterEvents,
};
}
/**
* Composable для управления общим состоянием между виджетами
*
* Этот composable предоставляет централизованное хранилище для обмена данными
* между searchWidget, filtersWidget, productsWidget и sortWidget
*/
export function useSharedState(config?: SharedStateConfig) {
// Создаем глобальный экземпляр, если он еще не существует
if (!globalSharedState) {
// Используем переданную конфигурацию или пустой объект
const finalConfig = config || {};
globalSharedState = createSharedState(finalConfig);
}
return globalSharedState;
}