diff --git a/ui/src/router/index.ts b/ui/src/router/index.ts index 317e219..3a5b3b0 100644 --- a/ui/src/router/index.ts +++ b/ui/src/router/index.ts @@ -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 { - el: to.hash, - behavior: 'smooth' - } + return new Promise((resolve) => { + // 使用 nextTick 确保 DOM 更新完成 + setTimeout(() => { + const element = document.querySelector(to.hash) + + if (element) { + resolve({ + el: to.hash, + behavior: 'smooth', + top: 0 // 添加 top 偏移,确保元素完全可见 + }) + } else { + // 如果元素不存在,滚动到顶部 + resolve({ top: 0 }) + } + }, 100) + }) } // 否则滚动到页面顶部 - return { top: 0 } + return new Promise((resolve) => { + // 延迟一点时间,确保页面加载完成 + setTimeout(() => { + resolve({ top: 0 }) + }, 100) + }) } }) diff --git a/ui/src/utils/scrollManager.ts b/ui/src/utils/scrollManager.ts new file mode 100644 index 0000000..d4386b5 --- /dev/null +++ b/ui/src/utils/scrollManager.ts @@ -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) + } +} \ No newline at end of file diff --git a/ui/src/views/ArtistView.vue b/ui/src/views/ArtistView.vue index 5b79aa2..144513d 100644 --- a/ui/src/views/ArtistView.vue +++ b/ui/src/views/ArtistView.vue @@ -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); }); diff --git a/ui/src/views/ArtworkView.vue b/ui/src/views/ArtworkView.vue index 5d6dfb8..a0ffc8c 100644 --- a/ui/src/views/ArtworkView.vue +++ b/ui/src/views/ArtworkView.vue @@ -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); } }; diff --git a/ui/src/views/BookmarksView.vue b/ui/src/views/BookmarksView.vue index f86bb13..57e6d99 100644 --- a/ui/src/views/BookmarksView.vue +++ b/ui/src/views/BookmarksView.vue @@ -57,14 +57,16 @@ diff --git a/ui/src/views/RankingView.vue b/ui/src/views/RankingView.vue index d357ced..5f060bf 100644 --- a/ui/src/views/RankingView.vue +++ b/ui/src/views/RankingView.vue @@ -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); }); diff --git a/ui/src/views/SearchView.vue b/ui/src/views/SearchView.vue index f686642..5d23eb7 100644 --- a/ui/src/views/SearchView.vue +++ b/ui/src/views/SearchView.vue @@ -197,10 +197,11 @@