fix: scroll behaviour

This commit is contained in:
2025-12-08 23:00:21 +03:00
parent 13f63e09fc
commit 359395b7e8
27 changed files with 122 additions and 14 deletions

View File

@@ -7,6 +7,7 @@ services:
- "./scripts:/scripts"
- "./module:/module"
- "./build:/build"
- "/Users/nikitakiselev/code/italy-moda/image/catalog:/web/upload/image/catalog"
ports:
- "8000:80"
restart: always

View File

@@ -12,7 +12,7 @@
<AppDebugMessage v-if="settings.app_debug"/>
<RouterView v-slot="{ Component, route }">
<KeepAlive include="Home" :key="filtersStore.paramsHashForRouter">
<KeepAlive include="Home,Products" :key="filtersStore.paramsHashForRouter">
<component :is="Component" :key="route.fullPath"/>
</KeepAlive>
</RouterView>

View File

@@ -34,6 +34,7 @@ import FullScreenImageViewer from "@/components/FullScreenImageViewer.vue";
import {useHapticFeedback} from "@/composables/useHapticFeedback.js";
import {onMounted, onUnmounted, ref} from "vue";
import {useHapticScroll} from "@/composables/useHapticScroll.js";
import {useRouter} from "vue-router";
const emit = defineEmits(['onLoad']);
@@ -44,6 +45,7 @@ const props = defineProps({
}
});
const router = useRouter();
const haptic = useHapticFeedback();
const hapticScroll = useHapticScroll();
const pagination = {

View File

@@ -35,6 +35,22 @@ export const router = createRouter({
history: createWebHashHistory('/image/catalog/tgshopspa/'),
routes,
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) {
return savedPosition;
}

View File

@@ -20,6 +20,8 @@ export const useProductsStore = defineStore('products', {
loadFinished: false,
savedScrollY: 0,
currentLoadedParamsHash: null,
lastCategoryId: null,
scrollPositions: {}, // Сохраняем позиции скролла для каждой категории
}),
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;
},
},
});

View File

@@ -305,6 +305,9 @@ function setQuantity(newQuantity) {
}
onMounted(async () => {
// Явно сбрасываем скролл наверх при открытии страницы товара
window.scrollTo({ top: 0, behavior: 'instant' });
isLoading.value = true;
imagesLoaded.value = false;

View File

@@ -13,7 +13,7 @@
<script setup>
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 {useProductsStore} from "@/stores/ProductsStore.js";
import {useCategoriesStore} from "@/stores/CategoriesStore.js";
@@ -31,12 +31,77 @@ const yaMetrika = useYaMetrikaStore();
const categoryId = route.params.category_id ?? null;
const category = ref(null);
onMounted(async () => {
console.debug("[Category] Category Products Mounted");
console.debug("[Category] Load products for category: ", categoryId);
category.value = await categoriesStore.findCategoryById(categoryId);
console.debug("[Category] Category Name: ", category.value?.name);
// Опционально сохраняем позицию при деактивации (KeepAlive автоматически сохраняет DOM состояние)
onDeactivated(() => {
const currentCategoryId = route.params.category_id ?? null;
if (currentCategoryId) {
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 ?? 'Неизвестная категория'}`;
yaMetrika.pushHit(route.path, {
title: `${category.value?.name ?? 'Неизвестная категория'}`,
@@ -52,9 +117,7 @@ onMounted(async () => {
type: "product_categories",
params: {
operator: "contains",
value: [
categoryId
]
value: [currentCategoryId]
}
}
}
@@ -73,9 +136,7 @@ onMounted(async () => {
type: "product_categories",
params: {
operator: "contains",
value: [
categoryId
]
value: [currentCategoryId]
}
}
}
@@ -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>

View File

View File

@@ -41,7 +41,7 @@ class BlocksService
{
$blockType = $block['type'];
$cacheKey = "block_{$blockType}_" . md5(serialize($block['data']));
$cacheTtlSeconds = 60;
$cacheTtlSeconds = 3600;
$data = $this->cache->get($cacheKey);
if (! $data) {

View File

View File