fix: scroll behaviour
This commit is contained in:
@@ -7,6 +7,7 @@ services:
|
|||||||
- "./scripts:/scripts"
|
- "./scripts:/scripts"
|
||||||
- "./module:/module"
|
- "./module:/module"
|
||||||
- "./build:/build"
|
- "./build:/build"
|
||||||
|
- "/Users/nikitakiselev/code/italy-moda/image/catalog:/web/upload/image/catalog"
|
||||||
ports:
|
ports:
|
||||||
- "8000:80"
|
- "8000:80"
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<AppDebugMessage v-if="settings.app_debug"/>
|
<AppDebugMessage v-if="settings.app_debug"/>
|
||||||
|
|
||||||
<RouterView v-slot="{ Component, route }">
|
<RouterView v-slot="{ Component, route }">
|
||||||
<KeepAlive include="Home" :key="filtersStore.paramsHashForRouter">
|
<KeepAlive include="Home,Products" :key="filtersStore.paramsHashForRouter">
|
||||||
<component :is="Component" :key="route.fullPath"/>
|
<component :is="Component" :key="route.fullPath"/>
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import FullScreenImageViewer from "@/components/FullScreenImageViewer.vue";
|
|||||||
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
|
||||||
import {onMounted, onUnmounted, ref} from "vue";
|
import {onMounted, onUnmounted, ref} from "vue";
|
||||||
import {useHapticScroll} from "@/composables/useHapticScroll.js";
|
import {useHapticScroll} from "@/composables/useHapticScroll.js";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
const emit = defineEmits(['onLoad']);
|
const emit = defineEmits(['onLoad']);
|
||||||
|
|
||||||
@@ -44,6 +45,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
const haptic = useHapticFeedback();
|
const haptic = useHapticFeedback();
|
||||||
const hapticScroll = useHapticScroll();
|
const hapticScroll = useHapticScroll();
|
||||||
const pagination = {
|
const pagination = {
|
||||||
|
|||||||
@@ -35,6 +35,22 @@ export const router = createRouter({
|
|||||||
history: createWebHashHistory('/image/catalog/tgshopspa/'),
|
history: createWebHashHistory('/image/catalog/tgshopspa/'),
|
||||||
routes,
|
routes,
|
||||||
scrollBehavior(to, from, savedPosition) {
|
scrollBehavior(to, from, savedPosition) {
|
||||||
|
// Для страницы товара всегда скроллим наверх мгновенно
|
||||||
|
if (to.name === 'product.show') {
|
||||||
|
return {top: 0, behavior: 'instant'};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для страницы категории скролл будет восстановлен в компоненте через onActivated
|
||||||
|
// Здесь просто предотвращаем автоматический скролл наверх
|
||||||
|
if (to.name === 'product.categories.show') {
|
||||||
|
// Если возвращаемся назад на категорию - используем savedPosition
|
||||||
|
if (savedPosition) {
|
||||||
|
return savedPosition;
|
||||||
|
}
|
||||||
|
return false; // Не скроллить автоматически
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для остальных страниц используем savedPosition если есть, иначе наверх
|
||||||
if (savedPosition) {
|
if (savedPosition) {
|
||||||
return savedPosition;
|
return savedPosition;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ export const useProductsStore = defineStore('products', {
|
|||||||
loadFinished: false,
|
loadFinished: false,
|
||||||
savedScrollY: 0,
|
savedScrollY: 0,
|
||||||
currentLoadedParamsHash: null,
|
currentLoadedParamsHash: null,
|
||||||
|
lastCategoryId: null,
|
||||||
|
scrollPositions: {}, // Сохраняем позиции скролла для каждой категории
|
||||||
}),
|
}),
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
@@ -118,5 +120,16 @@ export const useProductsStore = defineStore('products', {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveScrollPosition(categoryId, position) {
|
||||||
|
if (categoryId) {
|
||||||
|
this.scrollPositions[categoryId] = position;
|
||||||
|
this.savedScrollY = position;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getScrollPosition(categoryId) {
|
||||||
|
return this.scrollPositions[categoryId] || 0;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -305,6 +305,9 @@ function setQuantity(newQuantity) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
// Явно сбрасываем скролл наверх при открытии страницы товара
|
||||||
|
window.scrollTo({ top: 0, behavior: 'instant' });
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true;
|
||||||
imagesLoaded.value = false;
|
imagesLoaded.value = false;
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ProductsList from "@/components/ProductsList.vue";
|
import ProductsList from "@/components/ProductsList.vue";
|
||||||
import {onMounted, ref} from "vue";
|
import {onMounted, onActivated, onDeactivated, ref, nextTick} from "vue";
|
||||||
import {useRoute} from "vue-router";
|
import {useRoute} from "vue-router";
|
||||||
import {useProductsStore} from "@/stores/ProductsStore.js";
|
import {useProductsStore} from "@/stores/ProductsStore.js";
|
||||||
import {useCategoriesStore} from "@/stores/CategoriesStore.js";
|
import {useCategoriesStore} from "@/stores/CategoriesStore.js";
|
||||||
@@ -31,12 +31,77 @@ const yaMetrika = useYaMetrikaStore();
|
|||||||
const categoryId = route.params.category_id ?? null;
|
const categoryId = route.params.category_id ?? null;
|
||||||
const category = ref(null);
|
const category = ref(null);
|
||||||
|
|
||||||
onMounted(async () => {
|
// Опционально сохраняем позицию при деактивации (KeepAlive автоматически сохраняет DOM состояние)
|
||||||
console.debug("[Category] Category Products Mounted");
|
onDeactivated(() => {
|
||||||
console.debug("[Category] Load products for category: ", categoryId);
|
const currentCategoryId = route.params.category_id ?? null;
|
||||||
category.value = await categoriesStore.findCategoryById(categoryId);
|
if (currentCategoryId) {
|
||||||
console.debug("[Category] Category Name: ", category.value?.name);
|
const scrollY = window.scrollY || window.pageYOffset || document.documentElement.scrollTop;
|
||||||
|
if (scrollY > 0) {
|
||||||
|
productsStore.saveScrollPosition(currentCategoryId, scrollY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик активации компонента (KeepAlive)
|
||||||
|
onActivated(async () => {
|
||||||
|
console.debug("[Category] Products component activated");
|
||||||
|
const currentCategoryId = route.params.category_id ?? null;
|
||||||
|
|
||||||
|
// Если категория изменилась, загружаем новые товары
|
||||||
|
if (currentCategoryId !== categoryId) {
|
||||||
|
console.debug("[Category] Category changed, reloading products");
|
||||||
|
const newCategory = await categoriesStore.findCategoryById(currentCategoryId);
|
||||||
|
category.value = newCategory;
|
||||||
|
window.document.title = `${newCategory?.name ?? 'Неизвестная категория'}`;
|
||||||
|
yaMetrika.pushHit(route.path, {
|
||||||
|
title: `${newCategory?.name ?? 'Неизвестная категория'}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
productsStore.reset();
|
||||||
|
productsStore.filtersFullUrl = route.fullPath;
|
||||||
|
await productsStore.loadProducts({
|
||||||
|
operand: "AND",
|
||||||
|
rules: {
|
||||||
|
RULE_PRODUCT_CATEGORIES: {
|
||||||
|
criteria: {
|
||||||
|
product_category_ids: {
|
||||||
|
type: "product_categories",
|
||||||
|
params: {
|
||||||
|
operator: "contains",
|
||||||
|
value: [currentCategoryId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
productsStore.lastCategoryId = currentCategoryId;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если возвращаемся на ту же категорию - не перезагружаем товары
|
||||||
|
const isReturningToSameCategory = productsStore.lastCategoryId === currentCategoryId &&
|
||||||
|
productsStore.products.data.length > 0 &&
|
||||||
|
productsStore.filtersFullUrl === route.fullPath;
|
||||||
|
|
||||||
|
if (isReturningToSameCategory) {
|
||||||
|
// KeepAlive должен восстановить позицию автоматически, но если нужно - восстанавливаем явно
|
||||||
|
await nextTick();
|
||||||
|
const savedPosition = productsStore.getScrollPosition(currentCategoryId);
|
||||||
|
if (savedPosition > 0) {
|
||||||
|
// Используем requestAnimationFrame для плавного восстановления
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
window.scrollTo({
|
||||||
|
top: savedPosition,
|
||||||
|
behavior: 'instant'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Первая загрузка категории
|
||||||
|
category.value = await categoriesStore.findCategoryById(currentCategoryId);
|
||||||
window.document.title = `${category.value?.name ?? 'Неизвестная категория'}`;
|
window.document.title = `${category.value?.name ?? 'Неизвестная категория'}`;
|
||||||
yaMetrika.pushHit(route.path, {
|
yaMetrika.pushHit(route.path, {
|
||||||
title: `${category.value?.name ?? 'Неизвестная категория'}`,
|
title: `${category.value?.name ?? 'Неизвестная категория'}`,
|
||||||
@@ -52,9 +117,7 @@ onMounted(async () => {
|
|||||||
type: "product_categories",
|
type: "product_categories",
|
||||||
params: {
|
params: {
|
||||||
operator: "contains",
|
operator: "contains",
|
||||||
value: [
|
value: [currentCategoryId]
|
||||||
categoryId
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,9 +136,7 @@ onMounted(async () => {
|
|||||||
type: "product_categories",
|
type: "product_categories",
|
||||||
params: {
|
params: {
|
||||||
operator: "contains",
|
operator: "contains",
|
||||||
value: [
|
value: [currentCategoryId]
|
||||||
categoryId
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,5 +144,17 @@ onMounted(async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
productsStore.lastCategoryId = currentCategoryId;
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// Только для первой загрузки, если компонент не был в KeepAlive
|
||||||
|
const currentCategoryId = route.params.category_id ?? null;
|
||||||
|
category.value = await categoriesStore.findCategoryById(currentCategoryId);
|
||||||
|
window.document.title = `${category.value?.name ?? 'Неизвестная категория'}`;
|
||||||
|
yaMetrika.pushHit(route.path, {
|
||||||
|
title: `${category.value?.name ?? 'Неизвестная категория'}`,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
0
module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/ImageHandler.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/bastion/Handlers/ImageHandler.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/bastion/ScheduledTasks/TeleCartPulseSendEventsTask.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/bastion/ScheduledTasks/TeleCartPulseSendEventsTask.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/console/Commands/PulseSendEventsCommand.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/console/Commands/PulseSendEventsCommand.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Exceptions/HttpNotFoundException.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Exceptions/HttpNotFoundException.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageFactory.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageFactory.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageNotFoundException.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageNotFoundException.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageToolServiceProvider.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageToolServiceProvider.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageUtils.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/ImageTool/ImageUtils.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/OpenCart/Currency.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/OpenCart/Currency.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/OpenCart/PriceCalculator.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/OpenCart/PriceCalculator.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/OpenCart/PriceFormatter.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/OpenCart/PriceFormatter.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Support/DateUtils.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Support/DateUtils.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/phpstan.neon
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/phpstan.neon
Normal file → Executable file
@@ -41,7 +41,7 @@ class BlocksService
|
|||||||
{
|
{
|
||||||
$blockType = $block['type'];
|
$blockType = $block['type'];
|
||||||
$cacheKey = "block_{$blockType}_" . md5(serialize($block['data']));
|
$cacheKey = "block_{$blockType}_" . md5(serialize($block['data']));
|
||||||
$cacheTtlSeconds = 60;
|
$cacheTtlSeconds = 3600;
|
||||||
|
|
||||||
$data = $this->cache->get($cacheKey);
|
$data = $this->cache->get($cacheKey);
|
||||||
if (! $data) {
|
if (! $data) {
|
||||||
|
|||||||
0
module/oc_telegram_shop/upload/oc_telegram_shop/stubs/config.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/stubs/config.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/stubs/currency.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/stubs/currency.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/ImageTool/ImageFactoryTest.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/ImageTool/ImageFactoryTest.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/Scheduler/JobTest.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/Scheduler/JobTest.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/Scheduler/ScheduleJobRegistryTest.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/Scheduler/ScheduleJobRegistryTest.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/Scheduler/SchedulerResultTest.php
Normal file → Executable file
0
module/oc_telegram_shop/upload/oc_telegram_shop/tests/Unit/Framework/Scheduler/SchedulerResultTest.php
Normal file → Executable file
Reference in New Issue
Block a user