feat(search): improvement search cache (#44)
This commit is contained in:
@@ -17,6 +17,13 @@ export const useSearchStore = defineStore('search', {
|
|||||||
isLoadingMore: false,
|
isLoadingMore: false,
|
||||||
isSearchPerformed: false,
|
isSearchPerformed: false,
|
||||||
hasMore: false,
|
hasMore: false,
|
||||||
|
|
||||||
|
// Placeholder товары для пустого состояния поиска
|
||||||
|
placeholderProducts: {
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
isLoadingPlaceholder: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
@@ -91,6 +98,35 @@ export const useSearchStore = defineStore('search', {
|
|||||||
this.isSearchPerformed = true;
|
this.isSearchPerformed = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async loadSearchPlaceholder() {
|
||||||
|
// Если данные уже есть в store, возвращаем их
|
||||||
|
if (this.placeholderProducts.data.length > 0) {
|
||||||
|
return {
|
||||||
|
data: this.placeholderProducts.data,
|
||||||
|
meta: {
|
||||||
|
total: this.placeholderProducts.total,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.isLoadingPlaceholder = true;
|
||||||
|
// Иначе загружаем с сервера
|
||||||
|
const response = await ftch('productsSearchPlaceholder');
|
||||||
|
this.placeholderProducts.data = response.data.slice(0, 3);
|
||||||
|
this.placeholderProducts.total = response?.meta?.total || 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
data: this.placeholderProducts.data,
|
||||||
|
meta: {
|
||||||
|
total: this.placeholderProducts.total,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} finally {
|
||||||
|
this.isLoadingPlaceholder = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -79,25 +79,38 @@
|
|||||||
class="flex flex-col items-center justify-center text-center py-16 px-10"
|
class="flex flex-col items-center justify-center text-center py-16 px-10"
|
||||||
>
|
>
|
||||||
<div class="avatar-group -space-x-6 mb-4">
|
<div class="avatar-group -space-x-6 mb-4">
|
||||||
<template v-for="product in preloadedProducts" :key="product.id">
|
<!-- Skeleton при загрузке -->
|
||||||
<div v-if="product.image" class="avatar">
|
<template v-if="searchStore.isLoadingPlaceholder">
|
||||||
<div class="w-12">
|
<div v-for="n in 3" :key="n" class="avatar">
|
||||||
<img :src="product.image" :alt="product.name"/>
|
<div class="w-12 skeleton rounded-full"></div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="avatar avatar-placeholder">
|
||||||
<div v-else class="avatar avatar-placeholder">
|
<div class="bg-neutral text-neutral-content w-12 skeleton"></div>
|
||||||
<div class="bg-neutral text-neutral-content w-12 rounded-full">
|
|
||||||
<span class="text-3xl">{{ product.name.charAt(0).toUpperCase() }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="avatar avatar-placeholder">
|
<!-- Товары после загрузки -->
|
||||||
<div class="bg-neutral text-neutral-content w-12">
|
<template v-else>
|
||||||
<span>{{ renderSmartNumber(productsTotal) }}</span>
|
<template v-for="product in preloadedProducts" :key="product.id">
|
||||||
|
<div v-if="product.image" class="avatar">
|
||||||
|
<div class="w-12">
|
||||||
|
<img :src="product.image" :alt="product.name"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="avatar avatar-placeholder">
|
||||||
|
<div class="bg-neutral text-neutral-content w-12 rounded-full">
|
||||||
|
<span class="text-3xl">{{ product.name.charAt(0).toUpperCase() }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="avatar avatar-placeholder">
|
||||||
|
<div class="bg-neutral text-neutral-content w-12">
|
||||||
|
<span>{{ renderSmartNumber(productsTotal) }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="text-xl font-semibold mb-2">Поиск товаров</h2>
|
<h2 class="text-xl font-semibold mb-2">Поиск товаров</h2>
|
||||||
<p class="text-sm mb-4">Введите запрос, чтобы отобразить подходящие товары.</p>
|
<p class="text-sm mb-4">Введите запрос, чтобы отобразить подходящие товары.</p>
|
||||||
@@ -185,8 +198,8 @@ const handleHideKeyboardClick = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const preloadedProducts = ref([]);
|
const preloadedProducts = computed(() => searchStore.placeholderProducts.data);
|
||||||
const productsTotal = ref(0);
|
const productsTotal = computed(() => searchStore.placeholderProducts.total);
|
||||||
|
|
||||||
const searchWrapperStyle = computed(() => {
|
const searchWrapperStyle = computed(() => {
|
||||||
const safeTop = getComputedStyle(document.documentElement)
|
const safeTop = getComputedStyle(document.documentElement)
|
||||||
@@ -208,8 +221,6 @@ onMounted(async () => {
|
|||||||
});
|
});
|
||||||
yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_SEARCH);
|
yaMetrika.reachGoal(YA_METRIKA_GOAL.VIEW_SEARCH);
|
||||||
|
|
||||||
const response = await searchStore.fetchProducts('', 1, 3);
|
await searchStore.loadSearchPlaceholder();
|
||||||
productsTotal.value = response?.meta?.total || 0;
|
|
||||||
preloadedProducts.value = response.data.splice(0, 3);
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Handlers;
|
|||||||
use App\Services\ProductsService;
|
use App\Services\ProductsService;
|
||||||
use App\Services\SettingsService;
|
use App\Services\SettingsService;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Openguru\OpenCartFramework\Cache\CacheInterface;
|
||||||
use Openguru\OpenCartFramework\Exceptions\EntityNotFoundException;
|
use Openguru\OpenCartFramework\Exceptions\EntityNotFoundException;
|
||||||
use Openguru\OpenCartFramework\Http\Request;
|
use Openguru\OpenCartFramework\Http\Request;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
@@ -19,12 +20,18 @@ class ProductsHandler
|
|||||||
private SettingsService $settings;
|
private SettingsService $settings;
|
||||||
private ProductsService $productsService;
|
private ProductsService $productsService;
|
||||||
private LoggerInterface $logger;
|
private LoggerInterface $logger;
|
||||||
|
private CacheInterface $cache;
|
||||||
|
|
||||||
public function __construct(SettingsService $settings, ProductsService $productsService, LoggerInterface $logger)
|
public function __construct(
|
||||||
{
|
SettingsService $settings,
|
||||||
|
ProductsService $productsService,
|
||||||
|
LoggerInterface $logger,
|
||||||
|
CacheInterface $cache
|
||||||
|
) {
|
||||||
$this->settings = $settings;
|
$this->settings = $settings;
|
||||||
$this->productsService = $productsService;
|
$this->productsService = $productsService;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
|
$this->cache = $cache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index(Request $request): JsonResponse
|
public function index(Request $request): JsonResponse
|
||||||
@@ -85,4 +92,34 @@ class ProductsHandler
|
|||||||
'data' => $images,
|
'data' => $images,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSearchPlaceholder(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$storeId = $this->settings->get('store.oc_store_id', 0);
|
||||||
|
$languageId = $this->settings->config()->getApp()->getLanguageId();
|
||||||
|
$cacheKey = "products.search_placeholder.{$storeId}.{$languageId}";
|
||||||
|
|
||||||
|
$cached = $this->cache->get($cacheKey);
|
||||||
|
|
||||||
|
if ($cached !== null) {
|
||||||
|
return new JsonResponse($cached);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = $this->productsService->getProductsResponse(
|
||||||
|
[
|
||||||
|
'page' => 1,
|
||||||
|
'perPage' => 3,
|
||||||
|
'search' => '',
|
||||||
|
'filters' => [],
|
||||||
|
'maxPages' => 1,
|
||||||
|
],
|
||||||
|
$languageId,
|
||||||
|
$storeId,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Кешируем на 24 часа
|
||||||
|
$this->cache->set($cacheKey, $response, 60 * 60 * 24);
|
||||||
|
|
||||||
|
return new JsonResponse($response);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ return [
|
|||||||
'processBlock' => [BlocksHandler::class, 'processBlock'],
|
'processBlock' => [BlocksHandler::class, 'processBlock'],
|
||||||
'product_show' => [ProductsHandler::class, 'show'],
|
'product_show' => [ProductsHandler::class, 'show'],
|
||||||
'products' => [ProductsHandler::class, 'index'],
|
'products' => [ProductsHandler::class, 'index'],
|
||||||
|
'productsSearchPlaceholder' => [ProductsHandler::class, 'getSearchPlaceholder'],
|
||||||
'saveTelegramCustomer' => [TelegramCustomerHandler::class, 'saveOrUpdate'],
|
'saveTelegramCustomer' => [TelegramCustomerHandler::class, 'saveOrUpdate'],
|
||||||
'getCurrentCustomer' => [TelegramCustomerHandler::class, 'getCurrent'],
|
'getCurrentCustomer' => [TelegramCustomerHandler::class, 'getCurrent'],
|
||||||
'settings' => [SettingsHandler::class, 'index'],
|
'settings' => [SettingsHandler::class, 'index'],
|
||||||
|
|||||||
Reference in New Issue
Block a user