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" - "./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

View File

@@ -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>

View File

@@ -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 = {

View File

@@ -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;
} }

View File

@@ -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;
},
}, },
}); });

View File

@@ -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;

View File

@@ -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>

View File

View 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) {

View File

View File