feat: search component and loading splashscreen

This commit is contained in:
2025-08-08 14:36:05 +03:00
parent a8bb5eb493
commit 2fb841ef08
12 changed files with 217 additions and 35 deletions

View File

@@ -1,6 +1,7 @@
<template>
<div ref="goodsRef" class="safe-top">
<CategoriesInline/>
<SearchInput/>
<ProductsList/>
</div>
</template>
@@ -8,4 +9,5 @@
<script setup>
import ProductsList from "@/components/ProductsList.vue";
import CategoriesInline from "../components/CategoriesInline.vue";
import SearchInput from "@/components/SearchInput.vue";
</script>

96
spa/src/views/Search.vue Normal file
View File

@@ -0,0 +1,96 @@
<template>
<div class="max-w-3xl mx-auto p-4 space-y-6 pb-20 safe-top">
<h2 class="text-2xl mb-3">Поиск</h2>
<div class="w-full">
<label class="input w-full">
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g
stroke-linejoin="round"
stroke-linecap="round"
stroke-width="2.5"
fill="none"
stroke="currentColor"
>
<circle cx="11" cy="11" r="8"></circle>
<path d="m21 21-4.3-4.3"></path>
</g>
</svg>
<input
ref="searchInput"
type="search"
class="grow input-lg"
placeholder="Поиск по магазину"
v-model="searchStore.search"
@search="debouncedSearch"
@input="debouncedSearch"
/>
</label>
</div>
<div v-if="searchStore.isLoading === false && searchStore.products.data.length > 0">
<RouterLink
v-for="product in searchStore.products.data"
:key="product.id"
class="flex mb-5"
:to="{name: 'product.show', params: {id: product.id}}"
>
<div v-if="product.images && product.images.length > 0" class="avatar">
<div class="w-24 rounded">
<img :src="product.images[0].url" :alt="product.images[0].alt"/>
</div>
</div>
<div class="ml-5 p-0">
<h2 class="card-title">{{ product.name }}</h2>
<p>{{ product.price }}</p>
</div>
</RouterLink>
</div>
<div v-if="searchStore.isLoading === true">
<div v-for="n in 3" class="flex w-full gap-4 mb-3">
<div class="skeleton h-32 w-32"></div>
<div class="flex flex-col gap-2 w-full">
<div class="skeleton h-4 w-full"></div>
<div class="skeleton h-4 w-28"></div>
<div class="skeleton h-4 w-28"></div>
</div>
</div>
</div>
<div
v-if="searchStore.isSearchPerformed && searchStore.isLoading === false && searchStore.products.data.length === 0"
class="flex flex-col items-center justify-center text-center py-16"
>
<span class="text-5xl mb-4">🛒</span>
<h2 class="text-xl font-semibold mb-2">Товары не найдены</h2>
<p class="text-sm mb-4">Попробуйте изменить или уточнить запрос</p>
</div>
</div>
</template>
<script setup>
import {useSearchStore} from "@/stores/SearchStore.js";
import {useDebounceFn} from "@vueuse/core";
import {nextTick, onMounted, onUnmounted, ref} from "vue";
const searchStore = useSearchStore();
const searchInput = ref(null);
const debouncedSearch = useDebounceFn(() => searchStore.performSearch(), 500);
function handleClickOutside(e) {
if (!e.target.closest('input')) {
document.activeElement?.blur();
}
}
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside);
});
onMounted(() => {
document.addEventListener('click', handleClickOutside);
nextTick(() => searchInput.value.focus());
});
</script>