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