增加页面滚动复位功能
This commit is contained in:
+45
-4
@@ -67,19 +67,60 @@ const router = createRouter({
|
|||||||
scrollBehavior(to, from, savedPosition) {
|
scrollBehavior(to, from, savedPosition) {
|
||||||
// 如果有保存的位置(浏览器前进/后退),则恢复到该位置
|
// 如果有保存的位置(浏览器前进/后退),则恢复到该位置
|
||||||
if (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) {
|
if (to.hash) {
|
||||||
return {
|
return new Promise((resolve) => {
|
||||||
|
// 使用 nextTick 确保 DOM 更新完成
|
||||||
|
setTimeout(() => {
|
||||||
|
const element = document.querySelector(to.hash)
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
resolve({
|
||||||
el: to.hash,
|
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)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -172,6 +172,7 @@ import { useAuthStore } from '@/stores/auth';
|
|||||||
import artistService from '@/services/artist';
|
import artistService from '@/services/artist';
|
||||||
import downloadService from '@/services/download';
|
import downloadService from '@/services/download';
|
||||||
import { getImageProxyUrl } from '@/services/api';
|
import { getImageProxyUrl } from '@/services/api';
|
||||||
|
import { saveScrollPosition, restoreScrollPosition } from '@/utils/scrollManager';
|
||||||
import type { Artist, Artwork } from '@/types';
|
import type { Artist, Artwork } from '@/types';
|
||||||
|
|
||||||
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
|
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
|
||||||
@@ -512,6 +513,9 @@ const getImageUrl = getImageProxyUrl;
|
|||||||
|
|
||||||
// 点击作品
|
// 点击作品
|
||||||
const handleArtworkClick = (artwork: Artwork) => {
|
const handleArtworkClick = (artwork: Artwork) => {
|
||||||
|
// 保存当前页面的滚动位置
|
||||||
|
saveScrollPosition(route.fullPath);
|
||||||
|
|
||||||
// 传递作者ID、作品类型和当前页面信息,用于导航
|
// 传递作者ID、作品类型和当前页面信息,用于导航
|
||||||
router.push({
|
router.push({
|
||||||
path: `/artwork/${artwork.id}`,
|
path: `/artwork/${artwork.id}`,
|
||||||
@@ -519,7 +523,8 @@ const handleArtworkClick = (artwork: Artwork) => {
|
|||||||
artistId: artist.value?.id.toString(),
|
artistId: artist.value?.id.toString(),
|
||||||
artworkType: artworkType.value,
|
artworkType: artworkType.value,
|
||||||
page: currentPage.value.toString(),
|
page: currentPage.value.toString(),
|
||||||
returnUrl: route.fullPath
|
returnUrl: route.fullPath,
|
||||||
|
scrollTop: (window.scrollY || document.documentElement.scrollTop).toString()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -602,6 +607,11 @@ onMounted(async () => {
|
|||||||
} else {
|
} else {
|
||||||
await fetchArtworks(1);
|
await fetchArtworks(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 恢复滚动位置(延迟执行确保页面内容完全加载)
|
||||||
|
setTimeout(() => {
|
||||||
|
restoreScrollPosition(route.fullPath);
|
||||||
|
}, 200);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import artworkService from '@/services/artwork';
|
|||||||
import artistService from '@/services/artist';
|
import artistService from '@/services/artist';
|
||||||
import downloadService from '@/services/download';
|
import downloadService from '@/services/download';
|
||||||
import { getApiBaseUrl, getImageProxyUrl } from '@/services/api';
|
import { getApiBaseUrl, getImageProxyUrl } from '@/services/api';
|
||||||
|
import { saveScrollPositionForPath } from '@/utils/scrollManager';
|
||||||
import type { Artwork, DownloadTask } from '@/types';
|
import type { Artwork, DownloadTask } from '@/types';
|
||||||
import ErrorMessage from '@/components/common/ErrorMessage.vue';
|
import ErrorMessage from '@/components/common/ErrorMessage.vue';
|
||||||
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
import LoadingSpinner from '@/components/common/LoadingSpinner.vue';
|
||||||
@@ -360,10 +361,24 @@ const navigateToNext = () => {
|
|||||||
|
|
||||||
// 返回作者页面
|
// 返回作者页面
|
||||||
const goBackToArtist = () => {
|
const goBackToArtist = () => {
|
||||||
if (route.query.returnUrl) {
|
const returnUrl = route.query.returnUrl as string;
|
||||||
router.push(route.query.returnUrl as string);
|
const targetPath = returnUrl || `/artist/${route.query.artistId}`;
|
||||||
} else if (route.query.artistId) {
|
|
||||||
router.push(`/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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -57,14 +57,16 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue';
|
import { ref, computed, onMounted } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { artworkService } from '@/services/artwork';
|
import { artworkService } from '@/services/artwork';
|
||||||
import { getImageProxyUrl } from '@/services/api';
|
import { getImageProxyUrl } from '@/services/api';
|
||||||
|
import { saveScrollPosition, restoreScrollPosition } from '@/utils/scrollManager';
|
||||||
import type { Artwork } from '@/types';
|
import type { Artwork } from '@/types';
|
||||||
|
|
||||||
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
|
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
// 响应式数据
|
// 响应式数据
|
||||||
const artworks = ref<(Artwork & { loaded?: boolean; error?: boolean })[]>([]);
|
const artworks = ref<(Artwork & { loaded?: boolean; error?: boolean })[]>([]);
|
||||||
@@ -138,7 +140,16 @@ const loadMore = () => {
|
|||||||
|
|
||||||
// 处理作品点击
|
// 处理作品点击
|
||||||
const handleArtworkClick = (artwork: Artwork) => {
|
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(() => {
|
onMounted(async () => {
|
||||||
fetchBookmarks();
|
await fetchBookmarks();
|
||||||
|
|
||||||
|
// 恢复滚动位置
|
||||||
|
setTimeout(() => {
|
||||||
|
restoreScrollPosition(route.fullPath);
|
||||||
|
}, 200);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ import { useRoute, useRouter } from 'vue-router';
|
|||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import rankingService from '@/services/ranking';
|
import rankingService from '@/services/ranking';
|
||||||
import downloadService from '@/services/download';
|
import downloadService from '@/services/download';
|
||||||
|
import { saveScrollPosition, restoreScrollPosition } from '@/utils/scrollManager';
|
||||||
import type { Artwork } from '@/types';
|
import type { Artwork } from '@/types';
|
||||||
|
|
||||||
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
|
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
|
||||||
@@ -291,13 +292,17 @@ const goToPage = (page: number) => {
|
|||||||
|
|
||||||
// 点击作品
|
// 点击作品
|
||||||
const handleArtworkClick = (artwork: Artwork) => {
|
const handleArtworkClick = (artwork: Artwork) => {
|
||||||
|
// 保存当前页面的滚动位置
|
||||||
|
saveScrollPosition(route.fullPath);
|
||||||
|
|
||||||
router.push({
|
router.push({
|
||||||
path: `/artwork/${artwork.id}`,
|
path: `/artwork/${artwork.id}`,
|
||||||
query: {
|
query: {
|
||||||
rankingMode: currentMode.value,
|
rankingMode: currentMode.value,
|
||||||
rankingType: currentType.value,
|
rankingType: currentType.value,
|
||||||
page: currentPage.value.toString(),
|
page: currentPage.value.toString(),
|
||||||
returnUrl: route.fullPath
|
returnUrl: route.fullPath,
|
||||||
|
scrollTop: (window.scrollY || document.documentElement.scrollTop).toString()
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -411,6 +416,11 @@ onMounted(async () => {
|
|||||||
} else {
|
} else {
|
||||||
await fetchRankingData(1);
|
await fetchRankingData(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 恢复滚动位置
|
||||||
|
setTimeout(() => {
|
||||||
|
restoreScrollPosition(route.fullPath);
|
||||||
|
}, 200);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -197,10 +197,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch } from 'vue';
|
import { ref, computed, watch, onMounted } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { useAuthStore } from '@/stores/auth';
|
import { useAuthStore } from '@/stores/auth';
|
||||||
import artworkService from '@/services/artwork';
|
import artworkService from '@/services/artwork';
|
||||||
|
import { saveScrollPosition, restoreScrollPosition } from '@/utils/scrollManager';
|
||||||
import type { Artwork, SearchParams } from '@/types';
|
import type { Artwork, SearchParams } from '@/types';
|
||||||
|
|
||||||
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
|
import ArtworkCard from '@/components/artwork/ArtworkCard.vue';
|
||||||
@@ -348,7 +349,16 @@ const goToPage = (page: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleArtworkClick = (artwork: Artwork) => {
|
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搜索
|
// 作品ID搜索
|
||||||
@@ -740,6 +750,13 @@ watch(() => route.query, () => {
|
|||||||
if (urlSort) searchSort.value = urlSort as 'date_desc' | 'date_asc' | 'popular_desc';
|
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';
|
if (urlDuration) searchDuration.value = urlDuration as 'all' | 'within_last_day' | 'within_last_week' | 'within_last_month';
|
||||||
}, { immediate: true });
|
}, { immediate: true });
|
||||||
|
|
||||||
|
// 组件挂载时恢复滚动位置
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
restoreScrollPosition(route.fullPath);
|
||||||
|
}, 200);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user