父页面 - HomePage
<template><div id="home-page" ref="contentRef"><PictureList ref="pictureListRef" /></div>
</template><script setup lang="ts">
import PictureList from '@/components/PictureList.vue'
import { onMounted, onUnmounted, ref } from 'vue'const pictureListRef = ref()
const contentRef = ref()let lastScrollTop = 0// 找到真正有滚动条的元素
const findScrollableElement = () => {let current = contentRef.valuewhile (current && current !== document.body) {current = current.parentElementif (current && current.scrollHeight > current.clientHeight) {// console.log('找到可滚动的父元素:', current.tagName)return current}}return document.documentElement.scrollHeight > document.body.scrollHeight? document.documentElement: document.body
}const checkScroll = () => {// 直接使用 PictureList 组件的状态来判断if (!pictureListRef.value) returnconst { moreLoading, noMore } = pictureListRef.value// 如果正在加载或没有更多数据,不再触发if (moreLoading || noMore) {return}const scrollElement = findScrollableElement()const scrollTop = scrollElement.scrollTopconst clientHeight = scrollElement.clientHeightconst scrollHeight = scrollElement.scrollHeight// 判断滚动方向(只有向下滚动才触发)const isScrollingDown = scrollTop > lastScrollToplastScrollTop = scrollTop// 计算距离底部的距离const distanceFromBottom = scrollHeight - (scrollTop + clientHeight)// 设置触发阈值,当距离底部 200px 时触发const threshold = 200// console.log('滚动检测:', {// distanceFromBottom,// isScrollingDown,// moreLoading,// noMore,// })// 只有向下滚动且接近底部时才触发加载if (isScrollingDown && distanceFromBottom <= threshold) {// console.log('触发加载更多')pictureListRef.value.loadMore()}
}// 防抖处理
let scrollTimer: number | null = null
const handleScroll = () => {if (scrollTimer) clearTimeout(scrollTimer)scrollTimer = setTimeout(checkScroll, 150)
}onMounted(() => {setTimeout(() => {const scrollElement = findScrollableElement()// console.log('最终绑定的滚动元素:', scrollElement)scrollElement.addEventListener('scroll', handleScroll)}, 100)
})onUnmounted(() => {const scrollElement = findScrollableElement()if (scrollElement && scrollElement.removeEventListener) {scrollElement.removeEventListener('scroll', handleScroll)}if (scrollTimer) clearTimeout(scrollTimer)
})
</script><style scoped></style>
子组件 - PictureList
<template><div class="picture-list"><a-list:grid="{ gutter: 16, xs: 1, sm: 2, md: 4, lg: 4, xl: 4, xxl: 6 }":data-source="dataList":loading="initLoading"><template #renderItem="{ item }"><a-list-item style="padding: 0"><div class="item"><img :src="item.url" :alt="item.name" loading="lazy" /><div class="tags-container"><a-tag class="tag" v-for="(tag, index) in item.tags" :key="index" color="#69B74D">{{ tag }}</a-tag></div></div></a-list-item></template></a-list><!-- 加载状态 --><div style="text-align: center"><a-spin :spinning="moreLoading" /></div><!-- 没有更多数据提示 --><div v-if="noMore" style="text-align: center; padding: 20px; color: #999">没有更多图片了</div></div>
</template>
<script setup lang="ts">
import { pagePictureVoUsingPost } from '@/api/tupianjiekou.ts'
import { onMounted, reactive, ref } from 'vue'
import { message } from 'ant-design-vue'// 数据列表
const dataList = ref<API.PictureVO[] | undefined>([])
// 初始化加载状态
const initLoading = ref<boolean>(false)
// 加载更多加载状态
const moreLoading = ref<boolean>(false)
// 是否还有更多数据
const noMore = ref<boolean>(false)// 搜索条件
const searchParams = reactive<API.PictureQueryRequest>({current: 1,size: 12,
})// 获取图片列表
const fetchData = async (isLoadMore: boolean = false) => {if (isLoadMore) {moreLoading.value = true} else {initLoading.value = true}try {const res = await pagePictureVoUsingPost({...searchParams,})const { records, current, pages } = res.data.dataif (res.data.code == 0 && res.data.data) {if (isLoadMore) {dataList.value = [...dataList.value, ...records]moreLoading.value = false} else {dataList.value = recordsinitLoading.value = false}noMore.value = current >= pages || records.length === 0} else {message.error(res.data.message || '获取数据失败')}} catch (e) {console.error('获取数据失败:', e)message.error('获取数据失败')} finally {initLoading.value = falsemoreLoading.value = false}
}// 加载更多 - 暴露给父组件
const loadMore = async () => {if (moreLoading.value || noMore.value) returnif (searchParams.current) {searchParams.current++}await fetchData(true)
}// 重置搜索
const resetSearch = () => {searchParams.current = 1noMore.value = falsedataList.value = []fetchData(false)
}// 组件挂载时初始化
onMounted(() => {fetchData()
})// 暴露方法给父组件
defineExpose({loadMore,resetSearch,fetchData,
})
</script><style scoped>
.picture-list {.item {position: relative;height: 30vh;border-radius: 10px;overflow: hidden;transition: all 0.5s ease;&:hover {box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);}img {width: 100%;height: 100%;display: block;object-fit: cover; /* 关键属性:填充整个容器,可能会裁剪图片 */object-position: center; /* 图片居中显示 */}.tags-container {position: absolute;bottom: 10px;left: 10px;display: flex;gap: 6px; /* 标签间距 */flex-wrap: wrap; /* 超出换行 */.tag {bottom: 20px;left: 20px;}}}
}
</style>