feat: infinity scroll, load more, resore scroll
This commit is contained in:
@@ -13,6 +13,7 @@ use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
|
|||||||
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||||
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||||
use Openguru\OpenCartFramework\Support\Arr;
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
use Openguru\OpenCartFramework\Support\PaginationHelper;
|
||||||
|
|
||||||
class ProductsHandler
|
class ProductsHandler
|
||||||
{
|
{
|
||||||
@@ -43,7 +44,7 @@ class ProductsHandler
|
|||||||
{
|
{
|
||||||
$languageId = 1;
|
$languageId = 1;
|
||||||
$page = $request->get('page', 1);
|
$page = $request->get('page', 1);
|
||||||
$perPage = $request->get('perPage', 10);
|
$perPage = 10;
|
||||||
$categoryId = (int) $request->get('categoryId', 0);
|
$categoryId = (int) $request->get('categoryId', 0);
|
||||||
$categoryName = '';
|
$categoryName = '';
|
||||||
|
|
||||||
@@ -59,7 +60,7 @@ class ProductsHandler
|
|||||||
->value('name');
|
->value('name');
|
||||||
}
|
}
|
||||||
|
|
||||||
$products = $this->queryBuilder->newQuery()
|
$productsQuery = $this->queryBuilder->newQuery()
|
||||||
->select([
|
->select([
|
||||||
'products.product_id' => 'product_id',
|
'products.product_id' => 'product_id',
|
||||||
'products.quantity' => 'product_quantity',
|
'products.quantity' => 'product_quantity',
|
||||||
@@ -84,7 +85,13 @@ class ProductsHandler
|
|||||||
->where('product_to_category.category_id', '=', $categoryId);
|
->where('product_to_category.category_id', '=', $categoryId);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
})
|
});
|
||||||
|
|
||||||
|
$total = $productsQuery->count();
|
||||||
|
$lastPage = PaginationHelper::calculateLastPage($total, $perPage);
|
||||||
|
$hasMore = $page + 1 <= $lastPage;
|
||||||
|
|
||||||
|
$products = $productsQuery
|
||||||
->forPage($page, $perPage)
|
->forPage($page, $perPage)
|
||||||
->orderBy('date_added', 'DESC')
|
->orderBy('date_added', 'DESC')
|
||||||
->get();
|
->get();
|
||||||
@@ -152,6 +159,7 @@ class ProductsHandler
|
|||||||
|
|
||||||
'meta' => [
|
'meta' => [
|
||||||
'currentCategoryName' => $categoryName,
|
'currentCategoryName' => $categoryName,
|
||||||
|
'hasMore' => $hasMore,
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
45
spa/package-lock.json
generated
45
spa/package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/vue": "^2.2.0",
|
"@heroicons/vue": "^2.2.0",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
|
"@vueuse/core": "^13.5.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"ofetch": "^1.4.1",
|
"ofetch": "^1.4.1",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
@@ -1093,6 +1094,12 @@
|
|||||||
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
"integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/web-bluetooth": {
|
||||||
|
"version": "0.0.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||||
|
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-vue": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.0.tgz",
|
||||||
@@ -1240,6 +1247,44 @@
|
|||||||
"integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==",
|
"integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@vueuse/core": {
|
||||||
|
"version": "13.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.5.0.tgz",
|
||||||
|
"integrity": "sha512-wV7z0eUpifKmvmN78UBZX8T7lMW53Nrk6JP5+6hbzrB9+cJ3jr//hUlhl9TZO/03bUkMK6gGkQpqOPWoabr72g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/web-bluetooth": "^0.0.21",
|
||||||
|
"@vueuse/metadata": "13.5.0",
|
||||||
|
"@vueuse/shared": "13.5.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/metadata": {
|
||||||
|
"version": "13.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.5.0.tgz",
|
||||||
|
"integrity": "sha512-euhItU3b0SqXxSy8u1XHxUCdQ8M++bsRs+TYhOLDU/OykS7KvJnyIFfep0XM5WjIFry9uAPlVSjmVHiqeshmkw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/shared": {
|
||||||
|
"version": "13.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.5.0.tgz",
|
||||||
|
"integrity": "sha512-K7GrQIxJ/ANtucxIXbQlUHdB0TPA8c+q5i+zbrjxuhJCnJ9GtBg75sBSnvmLSxHKPg2Yo8w62PWksl9kwH0Q8g==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/autoprefixer": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.21",
|
"version": "10.4.21",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/vue": "^2.2.0",
|
"@heroicons/vue": "^2.2.0",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
|
"@vueuse/core": "^13.5.0",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"ofetch": "^1.4.1",
|
"ofetch": "^1.4.1",
|
||||||
"pinia": "^3.0.3",
|
"pinia": "^3.0.3",
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mx-auto max-w-2xl px-4 py-4 sm:px-6 sm:py-6 lg:max-w-7xl lg:px-8">
|
<div class="mx-auto max-w-2xl px-4 py-4 sm:px-6 sm:py-6 lg:max-w-7xl lg:px-8">
|
||||||
|
|
||||||
<div v-if="isLoading" class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
<div v-if="productsStore.isLoading"
|
||||||
|
class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
||||||
<div v-for="n in 8" :key="n" class="animate-pulse space-y-2">
|
<div v-for="n in 8" :key="n" class="animate-pulse space-y-2">
|
||||||
<div class="aspect-square bg-gray-200 rounded-md"></div>
|
<div class="aspect-square bg-gray-200 rounded-md"></div>
|
||||||
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
|
<div class="h-4 bg-gray-200 rounded w-3/4"></div>
|
||||||
@@ -10,65 +11,117 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<h2 class="text-lg font-bold mb-5 text-center">{{ meta.currentCategoryName }}</h2>
|
<h2 class="text-lg font-bold mb-5 text-center">{{ productsStore.products.meta.currentCategoryName }}</h2>
|
||||||
|
|
||||||
<div v-if="products.length > 0" class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
<div v-if="productsStore.products.data.length > 0">
|
||||||
<RouterLink
|
<div
|
||||||
v-for="product in products"
|
class="grid grid-cols-2 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8"
|
||||||
:key="product.id"
|
|
||||||
class="group"
|
|
||||||
:to="`/product/${product.id}`"
|
|
||||||
@click="haptic"
|
|
||||||
>
|
>
|
||||||
<ProductImageSwiper :images="product.images"/>
|
<RouterLink
|
||||||
<h3 class="mt-4 text-sm">{{ product.name }}</h3>
|
v-for="product in productsStore.products.data"
|
||||||
<p class="mt-1 text-lg font-medium">{{ product.price }}</p>
|
:key="product.id"
|
||||||
</RouterLink>
|
class="group"
|
||||||
|
:to="`/product/${product.id}`"
|
||||||
|
@click="haptic"
|
||||||
|
>
|
||||||
|
<ProductImageSwiper :images="product.images"/>
|
||||||
|
<h3 class="mt-4 text-sm">{{ product.name }}</h3>
|
||||||
|
<p class="mt-1 text-lg font-medium">{{ product.price }}</p>
|
||||||
|
</RouterLink>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="productsStore.hasMore === false"
|
||||||
|
class="text-gray-500 text-xs text-center mt-4 pt-4 mb-2 border-t"
|
||||||
|
>
|
||||||
|
{{ settings.noMoreProductsMessage }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<NoProducts v-else/>
|
<NoProducts v-else/>
|
||||||
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {ref} from "vue";
|
import NoProducts from "@/components/NoProducts.vue";
|
||||||
import {useHapticFeedback} from 'vue-tg';
|
import ProductImageSwiper from "@/components/ProductImageSwiper.vue";
|
||||||
import NoProducts from "../components/NoProducts.vue";
|
import {useProductsStore} from "@/stores/ProductsStore.js";
|
||||||
import ProductImageSwiper from "../components/ProductImageSwiper.vue";
|
import {useInfiniteScroll} from '@vueuse/core';
|
||||||
|
import {useRoute} from "vue-router";
|
||||||
|
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||||
|
import {nextTick, onMounted, ref, watch} from "vue";
|
||||||
|
|
||||||
const hapticFeedback = useHapticFeedback();
|
const route = useRoute();
|
||||||
|
const categoryId = route.params.id ?? null;
|
||||||
const props = defineProps({
|
const productsStore = useProductsStore();
|
||||||
products: {
|
const settings = useSettingsStore();
|
||||||
type: Array,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
|
|
||||||
meta: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
|
|
||||||
isLoading: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function haptic() {
|
function haptic() {
|
||||||
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
window.Telegram.WebApp.HapticFeedback.selectionChanged();
|
||||||
|
productsStore.savedScrollY = window.scrollY;
|
||||||
|
console.log("Store scrollY: ", productsStore.savedScrollY);
|
||||||
}
|
}
|
||||||
|
|
||||||
const carouselRef = ref();
|
async function loadMore() {
|
||||||
let lastScrollLeft = 0;
|
if (productsStore.isLoading || productsStore.hasMore === false) return;
|
||||||
function onScroll(e) {
|
|
||||||
const scrollLeft = e.target.scrollLeft;
|
|
||||||
const delta = Math.abs(scrollLeft - lastScrollLeft);
|
|
||||||
|
|
||||||
if (delta > 30) {
|
console.debug("Loading more...");
|
||||||
hapticFeedback.impactOccurred('soft');
|
console.debug("Page: ", productsStore.page);
|
||||||
lastScrollLeft = scrollLeft;
|
|
||||||
|
productsStore.isLoading = true;
|
||||||
|
try {
|
||||||
|
const response = await productsStore.fetchProducts(categoryId, productsStore.page);
|
||||||
|
productsStore.hasMore = response.meta.hasMore ?? false;
|
||||||
|
productsStore.products.data.push(...response.data);
|
||||||
|
productsStore.products.meta.currentCategoryName = response.meta.currentCategoryName;
|
||||||
|
productsStore.page++;
|
||||||
|
|
||||||
|
console.log("Loaded products: ", productsStore.products.data.length);
|
||||||
|
console.log("Has More? ", productsStore.hasMore);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Ошибка загрузки', e)
|
||||||
|
} finally {
|
||||||
|
productsStore.isLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useInfiniteScroll(
|
||||||
|
window,
|
||||||
|
loadMore,
|
||||||
|
{distance: 300}
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(() => route.params.id, async newId => {
|
||||||
|
if (newId !== productsStore.savedCategoryId) {
|
||||||
|
productsStore.reset()
|
||||||
|
productsStore.savedCategoryId = newId
|
||||||
|
await loadMore()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
console.log("Mounted");
|
||||||
|
const saved = productsStore.savedCategoryId === categoryId;
|
||||||
|
|
||||||
|
console.log("Saved Category: ", saved);
|
||||||
|
if (saved && productsStore.products.data.length > 0) {
|
||||||
|
await nextTick();
|
||||||
|
console.log("Products exists, scrolling to ", productsStore.savedScrollY);
|
||||||
|
// повторяем до тех пор, пока высота не станет больше savedScrollY
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const maxScroll = document.documentElement.scrollHeight - window.innerHeight
|
||||||
|
if (maxScroll >= productsStore.savedScrollY) {
|
||||||
|
window.scrollTo(0, productsStore.savedScrollY)
|
||||||
|
clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
} else {
|
||||||
|
productsStore.reset();
|
||||||
|
productsStore.savedCategoryId = categoryId;
|
||||||
|
await loadMore();
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -5,14 +5,6 @@ import { VueTelegramPlugin } from 'vue-tg';
|
|||||||
import { router } from './router';
|
import { router } from './router';
|
||||||
import { createPinia } from 'pinia';
|
import { createPinia } from 'pinia';
|
||||||
|
|
||||||
const config = {
|
|
||||||
night_auto: true,
|
|
||||||
theme: {
|
|
||||||
light: 'light',
|
|
||||||
dark: 'dark',
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
app
|
app
|
||||||
@@ -22,21 +14,19 @@ app
|
|||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|
||||||
const productsStore = useProductsStore();
|
const settings = useSettingsStore();
|
||||||
productsStore.fetchHomeProducts();
|
|
||||||
const categoriesStore = useCategoriesStore();
|
const categoriesStore = useCategoriesStore();
|
||||||
categoriesStore.fetchTopCategories();
|
categoriesStore.fetchTopCategories();
|
||||||
|
|
||||||
import {useProductsStore} from "@/stores/ProductsStore.js";
|
|
||||||
import {useCategoriesStore} from "@/stores/CategoriesStore.js";
|
import {useCategoriesStore} from "@/stores/CategoriesStore.js";
|
||||||
|
import {useSettingsStore} from "@/stores/SettingsStore.js";
|
||||||
|
|
||||||
if (config.night_auto) {
|
if (settings.night_auto) {
|
||||||
document.documentElement.setAttribute('data-theme', config.theme[this.colorScheme]);
|
|
||||||
window.Telegram.WebApp.onEvent('themeChanged', function () {
|
window.Telegram.WebApp.onEvent('themeChanged', function () {
|
||||||
document.documentElement.setAttribute('data-theme', config.theme[this.colorScheme]);
|
document.documentElement.setAttribute('data-theme', settings.theme[this.colorScheme]);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
document.documentElement.setAttribute('data-theme', config.theme.light);
|
document.documentElement.setAttribute('data-theme', settings.theme.light);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.Telegram.WebApp.ready();
|
window.Telegram.WebApp.ready();
|
||||||
|
|||||||
@@ -3,34 +3,24 @@ import ftch from "../utils/ftch.js";
|
|||||||
|
|
||||||
export const useProductsStore = defineStore('products', {
|
export const useProductsStore = defineStore('products', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
homeProducts: {
|
|
||||||
data: [],
|
|
||||||
meta: {},
|
|
||||||
},
|
|
||||||
products: {
|
products: {
|
||||||
data: [],
|
data: [],
|
||||||
meta: {},
|
meta: {},
|
||||||
},
|
},
|
||||||
|
page: 1,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
hasMore: true,
|
||||||
|
savedCategoryId: null,
|
||||||
|
savedScrollY: 0,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
async fetchHomeProducts() {
|
async fetchProducts(categoryId = null, page = 1) {
|
||||||
try {
|
try {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.homeProducts = await ftch('products');
|
return await ftch('products', {
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
this.isLoading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async fetchProducts(categoryId = null) {
|
|
||||||
try {
|
|
||||||
this.isLoading = true;
|
|
||||||
this.products = await ftch('products', {
|
|
||||||
categoryId: categoryId,
|
categoryId: categoryId,
|
||||||
|
page: page,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -38,5 +28,14 @@ export const useProductsStore = defineStore('products', {
|
|||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.page = 1;
|
||||||
|
this.hasMore = true;
|
||||||
|
this.products = {
|
||||||
|
data: [],
|
||||||
|
meta: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
12
spa/src/stores/SettingsStore.js
Normal file
12
spa/src/stores/SettingsStore.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import {defineStore} from "pinia";
|
||||||
|
|
||||||
|
export const useSettingsStore = defineStore('settings', {
|
||||||
|
state: () => ({
|
||||||
|
night_auto: true,
|
||||||
|
theme: {
|
||||||
|
light: 'light',
|
||||||
|
dark: 'dark',
|
||||||
|
},
|
||||||
|
noMoreProductsMessage: '🔚 Ну всё, разгрузили всё, что было. Даже кладовщика разбудить не удалось.',
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -1,24 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="goodsRef">
|
<div ref="goodsRef">
|
||||||
<CategoriesInline/>
|
<CategoriesInline/>
|
||||||
<ProductsList
|
<ProductsList/>
|
||||||
:products="productsStore.homeProducts.data"
|
|
||||||
:meta="productsStore.homeProducts.meta"
|
|
||||||
:isLoading="productsStore.isLoading"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {ref} from "vue";
|
|
||||||
import ProductsList from "@/components/ProductsList.vue";
|
import ProductsList from "@/components/ProductsList.vue";
|
||||||
import CategoriesInline from "../components/CategoriesInline.vue";
|
import CategoriesInline from "../components/CategoriesInline.vue";
|
||||||
import {useProductsStore} from "@/stores/ProductsStore.js";
|
|
||||||
|
|
||||||
const productsStore = useProductsStore();
|
|
||||||
|
|
||||||
const goodsRef = ref();
|
|
||||||
function scrollToProducts() {
|
|
||||||
goodsRef.value?.scrollIntoView({ behavior: 'smooth' });
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,22 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="goodsRef">
|
<div ref="goodsRef">
|
||||||
<ProductsList
|
<ProductsList/>
|
||||||
:products="productsStore.products.data"
|
|
||||||
:meta="productsStore.products.meta"
|
|
||||||
:isLoading="productsStore.isLoading"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {useProductsStore} from "@/stores/ProductsStore.js";
|
|
||||||
import ProductsList from "@/components/ProductsList.vue";
|
import ProductsList from "@/components/ProductsList.vue";
|
||||||
import {onMounted} from "vue";
|
|
||||||
import {useRoute} from "vue-router";
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const categoryId = route.params.id ?? null;
|
|
||||||
|
|
||||||
const productsStore = useProductsStore();
|
|
||||||
onMounted(() => productsStore.fetchProducts(categoryId))
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user