增加页面滚动复位功能

This commit is contained in:
2025-09-03 12:53:36 +08:00
parent ad8a2a3b5d
commit 5825288d87
7 changed files with 236 additions and 18 deletions
+45 -4
View File
@@ -67,19 +67,60 @@ const router = createRouter({
scrollBehavior(to, from, savedPosition) {
// 如果有保存的位置(浏览器前进/后退),则恢复到该位置
if (savedPosition) {
return savedPosition
return new Promise((resolve) => {
// 延迟一点时间确保页面加载完成
setTimeout(() => {
resolve(savedPosition)
}, 100)
})
}
// 检查是否有保存的自定义滚动位置
const savedScrollPosition = sessionStorage.getItem(`scroll_${to.fullPath}`)
if (savedScrollPosition) {
try {
const position = JSON.parse(savedScrollPosition)
// 清除保存的位置,避免重复使用
sessionStorage.removeItem(`scroll_${to.fullPath}`)
return new Promise((resolve) => {
setTimeout(() => {
resolve(position)
}, 100)
})
} catch (error) {
console.warn('解析保存的滚动位置失败:', error)
}
}
// 如果有锚点,则滚动到锚点位置
if (to.hash) {
return {
return new Promise((resolve) => {
// 使用 nextTick 确保 DOM 更新完成
setTimeout(() => {
const element = document.querySelector(to.hash)
if (element) {
resolve({
el: to.hash,
behavior: 'smooth'
behavior: 'smooth',
top: 0 // 添加 top 偏移,确保元素完全可见
})
} else {
// 如果元素不存在,滚动到顶部
resolve({ top: 0 })
}
}, 100)
})
}
// 否则滚动到页面顶部
return { top: 0 }
return new Promise((resolve) => {
// 延迟一点时间,确保页面加载完成
setTimeout(() => {
resolve({ top: 0 })
}, 100)
})
}
})
+109
View File
@@ -0,0 +1,109 @@
/**
* 滚动位置管理工具
* 用于在页面跳转时保存和恢复滚动位置
*/
/**
* 保存当前页面的滚动位置
* @param path 页面路径,用于标识保存的位置
*/
export function saveScrollPosition(path?: string): void {
const currentPath = path || window.location.pathname + window.location.search
const scrollPosition = {
top: window.pageYOffset || document.documentElement.scrollTop,
left: window.pageXOffset || document.documentElement.scrollLeft
}
sessionStorage.setItem(`scroll_${currentPath}`, JSON.stringify(scrollPosition))
}
/**
* 保存指定页面的滚动位置
* @param path 页面路径
* @param position 滚动位置
*/
export function saveScrollPositionForPath(path: string, position: { top: number; left: number }): void {
sessionStorage.setItem(`scroll_${path}`, JSON.stringify(position))
}
/**
* 获取保存的滚动位置
* @param path 页面路径
* @returns 滚动位置或null
*/
export function getSavedScrollPosition(path?: string): { top: number; left: number } | null {
const currentPath = path || window.location.pathname + window.location.search
const saved = sessionStorage.getItem(`scroll_${currentPath}`)
if (saved) {
try {
return JSON.parse(saved)
} catch (error) {
console.warn('解析保存的滚动位置失败:', error)
return null
}
}
return null
}
/**
* 清除保存的滚动位置
* @param path 页面路径,如果不提供则清除当前页面的
*/
export function clearScrollPosition(path?: string): void {
const currentPath = path || window.location.pathname + window.location.search
sessionStorage.removeItem(`scroll_${currentPath}`)
}
/**
* 清除所有保存的滚动位置
*/
export function clearAllScrollPositions(): void {
const keys = Object.keys(sessionStorage)
keys.forEach(key => {
if (key.startsWith('scroll_')) {
sessionStorage.removeItem(key)
}
})
}
/**
* 滚动到指定位置
* @param position 滚动位置
* @param behavior 滚动行为
*/
export function scrollToPosition(
position: { top: number; left: number },
behavior: ScrollBehavior = 'auto'
): void {
window.scrollTo({
top: position.top,
left: position.left,
behavior
})
}
/**
* 在页面即将卸载时保存滚动位置
* 通常用于 beforeunload 事件
*/
export function saveScrollPositionBeforeUnload(): void {
saveScrollPosition()
}
/**
* 在页面加载完成后恢复滚动位置
* 通常用于 mounted 生命周期
*/
export function restoreScrollPosition(path?: string): void {
const savedPosition = getSavedScrollPosition(path)
if (savedPosition) {
// 延迟一点时间确保页面内容完全加载
setTimeout(() => {
scrollToPosition(savedPosition, 'auto')
// 恢复后清除保存的位置
clearScrollPosition(path)
}, 100)
}
}
+11 -1
View File
@@ -172,6 +172,7 @@ import { useAuthStore } from '@/stores/auth';
import artistService from '@/services/artist';
import downloadService from '@/services/download';
import { getImageProxyUrl } from '@/services/api';
import { saveScrollPosition, restoreScrollPosition } from '@/utils/scrollManager';
import type { Artist, Artwork } from '@/types';
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
@@ -512,6 +513,9 @@ const getImageUrl = getImageProxyUrl;
// 点击作品
const handleArtworkClick = (artwork: Artwork) => {
// 保存当前页面的滚动位置
saveScrollPosition(route.fullPath);
// 传递作者ID、作品类型和当前页面信息,用于导航
router.push({
path: `/artwork/${artwork.id}`,
@@ -519,7 +523,8 @@ const handleArtworkClick = (artwork: Artwork) => {
artistId: artist.value?.id.toString(),
artworkType: artworkType.value,
page: currentPage.value.toString(),
returnUrl: route.fullPath
returnUrl: route.fullPath,
scrollTop: (window.scrollY || document.documentElement.scrollTop).toString()
}
});
};
@@ -602,6 +607,11 @@ onMounted(async () => {
} else {
await fetchArtworks(1);
}
// 恢复滚动位置(延迟执行确保页面内容完全加载)
setTimeout(() => {
restoreScrollPosition(route.fullPath);
}, 200);
});
</script>
+19 -4
View File
@@ -43,6 +43,7 @@ import artworkService from '@/services/artwork';
import artistService from '@/services/artist';
import downloadService from '@/services/download';
import { getApiBaseUrl, getImageProxyUrl } from '@/services/api';
import { saveScrollPositionForPath } from '@/utils/scrollManager';
import type { Artwork, DownloadTask } from '@/types';
import ErrorMessage from '@/components/common/ErrorMessage.vue';
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
@@ -360,10 +361,24 @@ const navigateToNext = () => {
// 返回作者页面
const goBackToArtist = () => {
if (route.query.returnUrl) {
router.push(route.query.returnUrl as string);
} else if (route.query.artistId) {
router.push(`/artist/${route.query.artistId}`);
const returnUrl = route.query.returnUrl as string;
const targetPath = returnUrl || `/artist/${route.query.artistId}`;
if (targetPath) {
// 获取当前保存的滚动位置(如果有的话)
const savedScrollKey = `scroll_${targetPath}`;
const savedPosition = sessionStorage.getItem(savedScrollKey);
// 如果没有保存的滚动位置,设置一个默认位置(通常是之前访问时的位置)
if (!savedPosition && route.query.scrollTop) {
const scrollPosition = {
top: parseInt(route.query.scrollTop as string) || 0,
left: 0
};
saveScrollPositionForPath(targetPath, scrollPosition);
}
router.push(targetPath);
}
};
+20 -4
View File
@@ -57,14 +57,16 @@
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { useRouter, useRoute } from 'vue-router';
import { artworkService } from '@/services/artwork';
import { getImageProxyUrl } from '@/services/api';
import { saveScrollPosition, restoreScrollPosition } from '@/utils/scrollManager';
import type { Artwork } from '@/types';
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
const router = useRouter();
const route = useRoute();
// 响应式数据
const artworks = ref<(Artwork & { loaded?: boolean; error?: boolean })[]>([]);
@@ -138,7 +140,16 @@ const loadMore = () => {
// 处理作品点击
const handleArtworkClick = (artwork: Artwork) => {
router.push(`/artwork/${artwork.id}`);
// 保存当前页面的滚动位置
saveScrollPosition(route.fullPath);
router.push({
path: `/artwork/${artwork.id}`,
query: {
returnUrl: route.fullPath,
scrollTop: (window.scrollY || document.documentElement.scrollTop).toString()
}
});
};
// 清除错误
@@ -147,8 +158,13 @@ const clearError = () => {
};
// 页面加载时获取数据
onMounted(() => {
fetchBookmarks();
onMounted(async () => {
await fetchBookmarks();
// 恢复滚动位置
setTimeout(() => {
restoreScrollPosition(route.fullPath);
}, 200);
});
</script>
+11 -1
View File
@@ -75,6 +75,7 @@ import { useRoute, useRouter } from 'vue-router';
import { useAuthStore } from '@/stores/auth';
import rankingService from '@/services/ranking';
import downloadService from '@/services/download';
import { saveScrollPosition, restoreScrollPosition } from '@/utils/scrollManager';
import type { Artwork } from '@/types';
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
@@ -291,13 +292,17 @@ const goToPage = (page: number) => {
// 点击作品
const handleArtworkClick = (artwork: Artwork) => {
// 保存当前页面的滚动位置
saveScrollPosition(route.fullPath);
router.push({
path: `/artwork/${artwork.id}`,
query: {
rankingMode: currentMode.value,
rankingType: currentType.value,
page: currentPage.value.toString(),
returnUrl: route.fullPath
returnUrl: route.fullPath,
scrollTop: (window.scrollY || document.documentElement.scrollTop).toString()
}
});
};
@@ -411,6 +416,11 @@ onMounted(async () => {
} else {
await fetchRankingData(1);
}
// 恢复滚动位置
setTimeout(() => {
restoreScrollPosition(route.fullPath);
}, 200);
});
</script>
+19 -2
View File
@@ -197,10 +197,11 @@
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { ref, computed, watch, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useAuthStore } from '@/stores/auth';
import artworkService from '@/services/artwork';
import { saveScrollPosition, restoreScrollPosition } from '@/utils/scrollManager';
import type { Artwork, SearchParams } from '@/types';
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
@@ -348,7 +349,16 @@ const goToPage = (page: number) => {
};
const handleArtworkClick = (artwork: Artwork) => {
router.push(`/artwork/${artwork.id}`);
// 保存当前页面的滚动位置
saveScrollPosition(route.fullPath);
router.push({
path: `/artwork/${artwork.id}`,
query: {
returnUrl: route.fullPath,
scrollTop: (window.scrollY || document.documentElement.scrollTop).toString()
}
});
};
// 作品ID搜索
@@ -740,6 +750,13 @@ watch(() => route.query, () => {
if (urlSort) searchSort.value = urlSort as 'date_desc' | 'date_asc' | 'popular_desc';
if (urlDuration) searchDuration.value = urlDuration as 'all' | 'within_last_day' | 'within_last_week' | 'within_last_month';
}, { immediate: true });
// 组件挂载时恢复滚动位置
onMounted(() => {
setTimeout(() => {
restoreScrollPosition(route.fullPath);
}, 200);
});
</script>
<style scoped>