feat(frontend): add infinite scroll for posts

This commit is contained in:
Tim
2025-07-09 18:27:01 +08:00
parent 9fdf068cd6
commit 2a58a3cb40

View File

@@ -1,5 +1,5 @@
<template> <template>
<div class="home-page"> <div class="home-page" @scroll="handleScroll">
<div class="search-container"> <div class="search-container">
<div class="search-title">Where possible begins</div> <div class="search-title">Where possible begins</div>
<div class="search-subtitle">希望你喜欢这里有问题请提问或搜索现有帖子</div> <div class="search-subtitle">希望你喜欢这里有问题请提问或搜索现有帖子</div>
@@ -36,7 +36,7 @@
</div> </div>
</div> </div>
<div v-if="isLoadingPosts" class="loading-container"> <div v-if="isLoadingPosts && articles.length === 0" class="loading-container">
<l-hatch size="28" stroke="4" speed="3.5" color="var(--primary-color)"></l-hatch> <l-hatch size="28" stroke="4" speed="3.5" color="var(--primary-color)"></l-hatch>
</div> </div>
@@ -73,6 +73,9 @@
{{ article.time }} {{ article.time }}
</div> </div>
</div> </div>
<div v-if="isLoadingPosts && articles.length > 0" class="loading-container bottom-loading">
<l-hatch size="28" stroke="4" speed="3.5" color="var(--primary-color)"></l-hatch>
</div>
</div> </div>
</div> </div>
@@ -110,35 +113,53 @@ export default {
const selectedTopic = ref('最新') const selectedTopic = ref('最新')
const articles = ref([]) const articles = ref([])
const page = ref(0)
const pageSize = 5
const allLoaded = ref(false)
const fetchPosts = async () => { const fetchPosts = async () => {
if (isLoadingPosts.value || allLoaded.value) return
try { try {
isLoadingPosts.value = true isLoadingPosts.value = true
const res = await fetch(`${API_BASE_URL}/api/posts`) const res = await fetch(`${API_BASE_URL}/api/posts?page=${page.value}&pageSize=${pageSize}`)
isLoadingPosts.value = false isLoadingPosts.value = false
if (!res.ok) return if (!res.ok) return
const data = await res.json() const data = await res.json()
articles.value = data.map(p => ({ articles.value.push(
id: p.id, ...data.map(p => ({
title: p.title, id: p.id,
description: p.content, title: p.title,
category: p.category, description: p.content,
tags: p.tags || [], category: p.category,
members: [], tags: p.tags || [],
comments: (p.comments || []).length, members: [],
views: p.views, comments: (p.comments || []).length,
time: new Date(p.createdAt).toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' }) views: p.views,
})) time: new Date(p.createdAt).toLocaleDateString('zh-CN', { month: 'numeric', day: 'numeric' })
}))
)
if (data.length < pageSize) {
allLoaded.value = true
} else {
page.value += 1
}
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
} }
const handleScroll = (e) => {
const el = e.target
if (el.scrollHeight - el.scrollTop <= el.clientHeight + 50) {
fetchPosts()
}
}
onMounted(fetchPosts) onMounted(fetchPosts)
const sanitizeDescription = (text) => stripMarkdown(text) const sanitizeDescription = (text) => stripMarkdown(text)
return { topics, selectedTopic, articles, sanitizeDescription, isLoadingPosts } return { topics, selectedTopic, articles, sanitizeDescription, isLoadingPosts, handleScroll }
} }
} }
</script> </script>
@@ -185,6 +206,10 @@ export default {
height: 200px; height: 200px;
} }
.bottom-loading {
height: 100px;
}
.no-posts-container { .no-posts-container {
display: flex; display: flex;
justify-content: center; justify-content: center;