前端页面细节优化:图片查看页面悬浮窗按钮被遮挡的问题修复;首页大图使用独立的变量控制,不受下方筛选结果影响

This commit is contained in:
2026-01-29 12:37:07 +08:00
parent 2e5eeaf425
commit 86d6517267
2 changed files with 152 additions and 35 deletions

View File

@@ -258,12 +258,33 @@ import {
const router = useRouter() const router = useRouter()
// 获取图片列表使用服务端分页和筛选每页15张 // 顶部最新图片(独立加载,不受筛选影响
const latestImage = ref<any>(null)
const todayLoading = ref(false)
// 历史图片列表使用服务端分页和筛选每页15张
const { images, loading, hasMore, loadMore, filterByMonth } = useImageList(15) const { images, loading, hasMore, loadMore, filterByMonth } = useImageList(15)
// 从图片列表获取最新图片作为顶部展示 // 加载顶部最新图片
const latestImage = computed(() => images.value.length > 0 ? images.value[0] : null) const loadLatestImage = async () => {
const todayLoading = computed(() => loading.value && images.value.length === 0) todayLoading.value = true
try {
const params = { page: 1, page_size: 1 }
const result = await bingPaperApi.getImages(params)
if (result.length > 0) {
latestImage.value = result[0]
}
} catch (error) {
console.error('Failed to load latest image:', error)
} finally {
todayLoading.value = false
}
}
// 初始化加载顶部图片
onMounted(() => {
loadLatestImage()
})
// 判断最新图片是否为今天的图片 // 判断最新图片是否为今天的图片
const isToday = computed(() => { const isToday = computed(() => {
@@ -306,11 +327,11 @@ let observer: IntersectionObserver | null = null
const loadMoreTrigger = ref<HTMLElement | null>(null) const loadMoreTrigger = ref<HTMLElement | null>(null)
let loadMoreObserver: IntersectionObserver | null = null let loadMoreObserver: IntersectionObserver | null = null
// 计算可用的年份列表(基于当前日期生成,从2020年到当前年份 // 计算可用的年份列表(基于当前日期生成,计算前20年
const availableYears = computed(() => { const availableYears = computed(() => {
const currentYear = new Date().getFullYear() const currentYear = new Date().getFullYear()
const years: number[] = [] const years: number[] = []
for (let year = currentYear; year >= 2020; year--) { for (let year = currentYear; year >= currentYear - 20; year--) {
years.push(year) years.push(year)
} }
return years return years
@@ -463,8 +484,10 @@ const setupLoadMoreObserver = () => {
} }
} }
// 初始化时设置无限滚动 // 初始化
onMounted(() => { onMounted(() => {
loadLatestImage()
if (images.value.length > 0) { if (images.value.length > 0) {
imageVisibility.value = new Array(images.value.length).fill(false) imageVisibility.value = new Array(images.value.length).fill(false)
setTimeout(() => { setTimeout(() => {
@@ -527,3 +550,25 @@ const openCopyrightLink = (link: string) => {
overflow: hidden; overflow: hidden;
} }
</style> </style>
<style>
/* 隐藏滚动条但保持滚动功能 */
body {
overflow-y: scroll;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
}
body::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
html {
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
}
html::-webkit-scrollbar {
display: none; /* Chrome, Safari, Opera */
}
</style>

View File

@@ -43,9 +43,16 @@
<div <div
v-if="showInfo && !showCalendar" v-if="showInfo && !showCalendar"
ref="infoPanel" ref="infoPanel"
class="fixed w-[90%] max-w-md bg-black/40 backdrop-blur-lg rounded-xl p-4 transform transition-all duration-300 z-10 select-none" class="fixed w-[90%] max-w-md bg-black/40 backdrop-blur-lg rounded-xl p-4 z-20 select-none"
:style="{ left: infoPanelPos.x + 'px', top: infoPanelPos.y + 'px' }" :class="{
:class="{ 'opacity-100': showInfo && !showCalendar, 'opacity-0 pointer-events-none': showCalendar }" 'opacity-100': showInfo && !showCalendar,
'opacity-0 pointer-events-none': showCalendar,
'transition-opacity duration-300': !isDragging
}"
:style="{
transform: `translate(${infoPanelPos.x}px, ${infoPanelPos.y}px)`,
willChange: isDragging ? 'transform' : 'auto'
}"
> >
<!-- 拖动手柄 --> <!-- 拖动手柄 -->
<div <div
@@ -208,16 +215,57 @@ const infoPanel = ref<HTMLElement | null>(null)
const infoPanelPos = ref({ x: 0, y: 0 }) const infoPanelPos = ref({ x: 0, y: 0 })
const isDragging = ref(false) const isDragging = ref(false)
const dragStart = ref({ x: 0, y: 0 }) const dragStart = ref({ x: 0, y: 0 })
let animationFrameId: number | null = null
// 初始化浮窗位置(居中偏下 // 计算图片实际显示区域考虑图片宽高比和object-contain
const getImageDisplayBounds = () => {
const windowWidth = window.innerWidth
const windowHeight = window.innerHeight
// 必应图片通常是16:9或类似宽高比
// 使用UHD分辨率: 1920x1080 (16:9)
const imageAspectRatio = 16 / 9
const windowAspectRatio = windowWidth / windowHeight
let displayWidth: number
let displayHeight: number
let offsetX: number
let offsetY: number
if (windowAspectRatio > imageAspectRatio) {
// 窗口更宽,图片上下占满,左右留黑边
displayHeight = windowHeight
displayWidth = displayHeight * imageAspectRatio
offsetX = (windowWidth - displayWidth) / 2
offsetY = 0
} else {
// 窗口更高,图片左右占满,上下留黑边
displayWidth = windowWidth
displayHeight = displayWidth / imageAspectRatio
offsetX = 0
offsetY = (windowHeight - displayHeight) / 2
}
return {
left: offsetX,
top: offsetY,
right: offsetX + displayWidth,
bottom: offsetY + displayHeight,
width: displayWidth,
height: displayHeight
}
}
// 初始化浮窗位置(居中偏下,限制在图片显示区域内)
const initPanelPosition = () => { const initPanelPosition = () => {
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
const windowWidth = window.innerWidth const bounds = getImageDisplayBounds()
const windowHeight = window.innerHeight const panelWidth = Math.min(bounds.width * 0.9, 448) // max-w-md = 448px
const panelWidth = Math.min(windowWidth * 0.9, 448) // max-w-md = 448px const bottomControlHeight = 80 // 底部控制栏高度
infoPanelPos.value = { infoPanelPos.value = {
x: (windowWidth - panelWidth) / 2, x: bounds.left + (bounds.width - panelWidth) / 2,
y: windowHeight - 200 // 距底部200px y: Math.max(bounds.top, bounds.bottom - 280) // 距底部280px,避免与控制栏重叠
} }
} }
} }
@@ -235,42 +283,61 @@ const startDrag = (e: MouseEvent | TouchEvent) => {
y: clientY - infoPanelPos.value.y y: clientY - infoPanelPos.value.y
} }
document.addEventListener('mousemove', onDrag) document.addEventListener('mousemove', onDrag, { passive: false })
document.addEventListener('mouseup', stopDrag) document.addEventListener('mouseup', stopDrag)
document.addEventListener('touchmove', onDrag, { passive: false }) document.addEventListener('touchmove', onDrag, { passive: false })
document.addEventListener('touchend', stopDrag) document.addEventListener('touchend', stopDrag)
} }
// 拖动中 // 拖动中 - 使用 requestAnimationFrame 优化性能
const onDrag = (e: MouseEvent | TouchEvent) => { const onDrag = (e: MouseEvent | TouchEvent) => {
if (!isDragging.value) return if (!isDragging.value) return
if (e instanceof TouchEvent) { e.preventDefault()
e.preventDefault()
// 取消之前的动画帧
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId)
} }
const clientX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX // 使用 requestAnimationFrame 进行节流优化
const clientY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY animationFrameId = requestAnimationFrame(() => {
const clientX = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX
const newX = clientX - dragStart.value.x const clientY = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY
const newY = clientY - dragStart.value.y
// 限制在视口内
if (infoPanel.value) {
const rect = infoPanel.value.getBoundingClientRect()
const maxX = window.innerWidth - rect.width
const maxY = window.innerHeight - rect.height
infoPanelPos.value = { const newX = clientX - dragStart.value.x
x: Math.max(0, Math.min(newX, maxX)), const newY = clientY - dragStart.value.y
y: Math.max(0, Math.min(newY, maxY))
// 限制在图片实际显示区域内考虑底部控制栏高度约80px
if (infoPanel.value) {
const rect = infoPanel.value.getBoundingClientRect()
const bounds = getImageDisplayBounds()
const minX = bounds.left
const maxX = bounds.right - rect.width
const minY = bounds.top
const maxY = bounds.bottom - rect.height - 80 // 预留底部控制栏空间
infoPanelPos.value = {
x: Math.max(minX, Math.min(newX, maxX)),
y: Math.max(minY, Math.min(newY, maxY))
}
} }
}
animationFrameId = null
})
} }
// 停止拖动 // 停止拖动
const stopDrag = () => { const stopDrag = () => {
isDragging.value = false isDragging.value = false
// 清理动画帧
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
document.removeEventListener('mousemove', onDrag) document.removeEventListener('mousemove', onDrag)
document.removeEventListener('mouseup', stopDrag) document.removeEventListener('mouseup', stopDrag)
document.removeEventListener('touchmove', onDrag) document.removeEventListener('touchmove', onDrag)
@@ -430,6 +497,11 @@ onUnmounted(() => {
document.removeEventListener('mouseup', stopDrag) document.removeEventListener('mouseup', stopDrag)
document.removeEventListener('touchmove', onDrag) document.removeEventListener('touchmove', onDrag)
document.removeEventListener('touchend', stopDrag) document.removeEventListener('touchend', stopDrag)
// 清理动画帧
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId)
}
} }
}) })
</script> </script>