mirror of
https://git.fightbot.fun/hxuanyu/BingPaper.git
synced 2026-02-15 07:29:33 +08:00
更新前端:实现无限滚动加载及前后日期可用性检测
This commit is contained in:
@@ -173,13 +173,18 @@
|
|||||||
<span>加载中...</span>
|
<span>加载中...</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<div
|
||||||
v-else-if="hasMore"
|
v-else-if="hasMore"
|
||||||
@click="loadMore"
|
ref="loadMoreTrigger"
|
||||||
class="px-8 py-3 bg-white/10 backdrop-blur-md text-white rounded-lg font-semibold hover:bg-white/20 transition-all border border-white/30"
|
class="inline-block"
|
||||||
>
|
>
|
||||||
加载更多
|
<button
|
||||||
</button>
|
@click="loadMore"
|
||||||
|
class="px-8 py-3 bg-white/10 backdrop-blur-md text-white rounded-lg font-semibold hover:bg-white/20 transition-all border border-white/30"
|
||||||
|
>
|
||||||
|
加载更多
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p v-else class="text-white/40">
|
<p v-else class="text-white/40">
|
||||||
已加载全部图片
|
已加载全部图片
|
||||||
@@ -246,8 +251,8 @@ const router = useRouter()
|
|||||||
// 获取今日图片
|
// 获取今日图片
|
||||||
const { image: todayImage, loading: todayLoading } = useTodayImage()
|
const { image: todayImage, loading: todayLoading } = useTodayImage()
|
||||||
|
|
||||||
// 获取图片列表(使用服务端分页和筛选)
|
// 获取图片列表(使用服务端分页和筛选,每页15张)
|
||||||
const { images, loading, hasMore, loadMore, filterByMonth } = useImageList(30)
|
const { images, loading, hasMore, loadMore, filterByMonth } = useImageList(15)
|
||||||
|
|
||||||
// 筛选相关状态
|
// 筛选相关状态
|
||||||
const selectedYear = ref('')
|
const selectedYear = ref('')
|
||||||
@@ -258,6 +263,10 @@ const imageRefs = ref<(HTMLElement | null)[]>([])
|
|||||||
const imageVisibility = ref<boolean[]>([])
|
const imageVisibility = ref<boolean[]>([])
|
||||||
let observer: IntersectionObserver | null = null
|
let observer: IntersectionObserver | null = null
|
||||||
|
|
||||||
|
// 无限滚动加载
|
||||||
|
const loadMoreTrigger = ref<HTMLElement | null>(null)
|
||||||
|
let loadMoreObserver: IntersectionObserver | null = null
|
||||||
|
|
||||||
// 计算可用的年份列表(基于当前日期生成,从2020年到当前年份)
|
// 计算可用的年份列表(基于当前日期生成,从2020年到当前年份)
|
||||||
const availableYears = computed(() => {
|
const availableYears = computed(() => {
|
||||||
const currentYear = new Date().getFullYear()
|
const currentYear = new Date().getFullYear()
|
||||||
@@ -360,16 +369,6 @@ const setupObserver = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化懒加载状态
|
|
||||||
onMounted(() => {
|
|
||||||
// 初始化时设置
|
|
||||||
if (images.value.length > 0) {
|
|
||||||
imageVisibility.value = new Array(images.value.length).fill(false)
|
|
||||||
setTimeout(() => {
|
|
||||||
setupObserver()
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 监听 images 变化,动态更新 imageVisibility
|
// 监听 images 变化,动态更新 imageVisibility
|
||||||
watch(() => images.value.length, (newLength, oldLength) => {
|
watch(() => images.value.length, (newLength, oldLength) => {
|
||||||
@@ -399,11 +398,51 @@ watch(() => images.value.length, (newLength, oldLength) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 设置无限滚动 Observer
|
||||||
|
const setupLoadMoreObserver = () => {
|
||||||
|
if (loadMoreObserver) {
|
||||||
|
loadMoreObserver.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
loadMoreObserver = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting && !loading.value && hasMore.value) {
|
||||||
|
loadMore()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: null,
|
||||||
|
rootMargin: '100px',
|
||||||
|
threshold: 0.1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (loadMoreTrigger.value) {
|
||||||
|
loadMoreObserver.observe(loadMoreTrigger.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化时设置无限滚动
|
||||||
|
onMounted(() => {
|
||||||
|
if (images.value.length > 0) {
|
||||||
|
imageVisibility.value = new Array(images.value.length).fill(false)
|
||||||
|
setTimeout(() => {
|
||||||
|
setupObserver()
|
||||||
|
setupLoadMoreObserver()
|
||||||
|
}, 100)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 清理
|
// 清理
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
if (observer) {
|
if (observer) {
|
||||||
observer.disconnect()
|
observer.disconnect()
|
||||||
}
|
}
|
||||||
|
if (loadMoreObserver) {
|
||||||
|
loadMoreObserver.disconnect()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 格式化日期
|
// 格式化日期
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<button
|
<button
|
||||||
@click="previousDay"
|
@click="previousDay"
|
||||||
:disabled="navigating"
|
:disabled="navigating || !hasPreviousDay"
|
||||||
class="flex items-center gap-2 px-4 py-2 bg-white/10 backdrop-blur-md text-white rounded-lg hover:bg-white/20 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
class="flex items-center gap-2 px-4 py-2 bg-white/10 backdrop-blur-md text-white rounded-lg hover:bg-white/20 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
@click="nextDay"
|
@click="nextDay"
|
||||||
:disabled="navigating || isToday"
|
:disabled="navigating || !hasNextDay"
|
||||||
class="flex items-center gap-2 px-4 py-2 bg-white/10 backdrop-blur-md text-white rounded-lg hover:bg-white/20 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
class="flex items-center gap-2 px-4 py-2 bg-white/10 backdrop-blur-md text-white rounded-lg hover:bg-white/20 transition-all disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
<span class="hidden sm:inline">后一天</span>
|
<span class="hidden sm:inline">后一天</span>
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { useImageByDate } from '@/composables/useImages'
|
import { useImageByDate } from '@/composables/useImages'
|
||||||
import { bingPaperApi } from '@/lib/api-service'
|
import { bingPaperApi } from '@/lib/api-service'
|
||||||
@@ -153,6 +153,11 @@ const currentDate = ref(route.params.date as string)
|
|||||||
const showInfo = ref(true)
|
const showInfo = ref(true)
|
||||||
const navigating = ref(false)
|
const navigating = ref(false)
|
||||||
|
|
||||||
|
// 前后日期可用性
|
||||||
|
const hasPreviousDay = ref(true)
|
||||||
|
const hasNextDay = ref(true)
|
||||||
|
const checkingDates = ref(false)
|
||||||
|
|
||||||
// 拖动相关状态
|
// 拖动相关状态
|
||||||
const infoPanel = ref<HTMLElement | null>(null)
|
const infoPanel = ref<HTMLElement | null>(null)
|
||||||
const infoPanelPos = ref({ x: 0, y: 0 })
|
const infoPanelPos = ref({ x: 0, y: 0 })
|
||||||
@@ -230,9 +235,50 @@ const stopDrag = () => {
|
|||||||
// 使用 composable 获取图片数据(传递 ref,自动响应日期变化)
|
// 使用 composable 获取图片数据(传递 ref,自动响应日期变化)
|
||||||
const { image, loading, error } = useImageByDate(currentDate)
|
const { image, loading, error } = useImageByDate(currentDate)
|
||||||
|
|
||||||
|
// 检测指定日期是否有数据
|
||||||
|
const checkDateAvailability = async (dateStr: string): Promise<boolean> => {
|
||||||
|
try {
|
||||||
|
await bingPaperApi.getImageMetaByDate(dateStr)
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测前后日期可用性
|
||||||
|
const checkAdjacentDates = async () => {
|
||||||
|
if (checkingDates.value) return
|
||||||
|
|
||||||
|
checkingDates.value = true
|
||||||
|
const date = new Date(currentDate.value)
|
||||||
|
|
||||||
|
// 检测前一天
|
||||||
|
const prevDate = new Date(date)
|
||||||
|
prevDate.setDate(prevDate.getDate() - 1)
|
||||||
|
hasPreviousDay.value = await checkDateAvailability(prevDate.toISOString().split('T')[0])
|
||||||
|
|
||||||
|
// 检测后一天(不能超过今天)
|
||||||
|
const nextDate = new Date(date)
|
||||||
|
nextDate.setDate(nextDate.getDate() + 1)
|
||||||
|
const today = new Date().toISOString().split('T')[0]
|
||||||
|
if (nextDate.toISOString().split('T')[0] > today) {
|
||||||
|
hasNextDay.value = false
|
||||||
|
} else {
|
||||||
|
hasNextDay.value = await checkDateAvailability(nextDate.toISOString().split('T')[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
checkingDates.value = false
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化位置
|
// 初始化位置
|
||||||
initPanelPosition()
|
initPanelPosition()
|
||||||
|
|
||||||
|
// 监听日期变化,检测前后日期可用性
|
||||||
|
import { watch } from 'vue'
|
||||||
|
watch(currentDate, () => {
|
||||||
|
checkAdjacentDates()
|
||||||
|
}, { immediate: true })
|
||||||
|
|
||||||
// 格式化日期
|
// 格式化日期
|
||||||
const formatDate = (dateStr?: string) => {
|
const formatDate = (dateStr?: string) => {
|
||||||
if (!dateStr) return ''
|
if (!dateStr) return ''
|
||||||
@@ -245,12 +291,6 @@ const formatDate = (dateStr?: string) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否是今天
|
|
||||||
const isToday = computed(() => {
|
|
||||||
const today = new Date().toISOString().split('T')[0]
|
|
||||||
return currentDate.value === today
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取完整图片 URL
|
// 获取完整图片 URL
|
||||||
const getFullImageUrl = () => {
|
const getFullImageUrl = () => {
|
||||||
return bingPaperApi.getImageUrlByDate(currentDate.value, 'UHD', 'jpg')
|
return bingPaperApi.getImageUrlByDate(currentDate.value, 'UHD', 'jpg')
|
||||||
@@ -265,7 +305,7 @@ const goBack = () => {
|
|||||||
|
|
||||||
// 前一天
|
// 前一天
|
||||||
const previousDay = () => {
|
const previousDay = () => {
|
||||||
if (navigating.value) return
|
if (navigating.value || !hasPreviousDay.value) return
|
||||||
|
|
||||||
navigating.value = true
|
navigating.value = true
|
||||||
const date = new Date(currentDate.value)
|
const date = new Date(currentDate.value)
|
||||||
@@ -282,7 +322,7 @@ const previousDay = () => {
|
|||||||
|
|
||||||
// 后一天
|
// 后一天
|
||||||
const nextDay = () => {
|
const nextDay = () => {
|
||||||
if (navigating.value || isToday.value) return
|
if (navigating.value || !hasNextDay.value) return
|
||||||
|
|
||||||
navigating.value = true
|
navigating.value = true
|
||||||
const date = new Date(currentDate.value)
|
const date = new Date(currentDate.value)
|
||||||
@@ -299,9 +339,9 @@ const nextDay = () => {
|
|||||||
|
|
||||||
// 键盘导航
|
// 键盘导航
|
||||||
const handleKeydown = (e: KeyboardEvent) => {
|
const handleKeydown = (e: KeyboardEvent) => {
|
||||||
if (e.key === 'ArrowLeft') {
|
if (e.key === 'ArrowLeft' && hasPreviousDay.value) {
|
||||||
previousDay()
|
previousDay()
|
||||||
} else if (e.key === 'ArrowRight' && !isToday.value) {
|
} else if (e.key === 'ArrowRight' && hasNextDay.value) {
|
||||||
nextDay()
|
nextDay()
|
||||||
} else if (e.key === 'Escape') {
|
} else if (e.key === 'Escape') {
|
||||||
goBack()
|
goBack()
|
||||||
|
|||||||
Reference in New Issue
Block a user