Add ranking endpoint and UI

This commit is contained in:
Tim
2025-07-09 20:48:58 +08:00
parent db58af31c9
commit befe0ff470
4 changed files with 88 additions and 11 deletions

View File

@@ -24,7 +24,7 @@
</div> </div>
<div class="article-container"> <div class="article-container">
<template v-if="selectedTopic === '最新'"> <template v-if="selectedTopic === '最新' || selectedTopic === '排行榜'">
<div class="header-container"> <div class="header-container">
<div class="header-item main-item"> <div class="header-item main-item">
<div class="header-item-text">话题</div> <div class="header-item-text">话题</div>
@@ -81,9 +81,6 @@
</div> </div>
</div> </div>
</template> </template>
<div v-else-if="selectedTopic === '排行榜'" class="placeholder-container">
排行榜功能开发中敬请期待
</div>
<div v-else-if="selectedTopic === '热门'" class="placeholder-container"> <div v-else-if="selectedTopic === '热门'" class="placeholder-container">
热门帖子功能开发中敬请期待 热门帖子功能开发中敬请期待
</div> </div>
@@ -144,6 +141,10 @@ export default {
return url return url
} }
const buildRankUrl = () => {
return `${API_BASE_URL}/api/posts/ranking?page=${page.value}&pageSize=${pageSize}`
}
const fetchPosts = async (reset = false) => { const fetchPosts = async (reset = false) => {
if (reset) { if (reset) {
page.value = 0 page.value = 0
@@ -180,17 +181,73 @@ export default {
} }
} }
const handleScroll = (e) => { const fetchRanking = async (reset = false) => {
const el = e.target if (reset) {
if (el.scrollHeight - el.scrollTop <= el.clientHeight + 50) { page.value = 0
fetchPosts() allLoaded.value = false
articles.value = []
}
if (isLoadingPosts.value || allLoaded.value) return
try {
isLoadingPosts.value = true
const res = await fetch(buildRankUrl())
isLoadingPosts.value = false
if (!res.ok) return
const data = await res.json()
articles.value.push(
...data.map(p => ({
id: p.id,
title: p.title,
description: p.content,
category: p.category,
tags: p.tags || [],
members: [],
comments: (p.comments || []).length,
views: p.views,
time: TimeManager.format(p.createdAt)
}))
)
if (data.length < pageSize) {
allLoaded.value = true
} else {
page.value += 1
}
} catch (e) {
console.error(e)
} }
} }
onMounted(fetchPosts) const handleScroll = (e) => {
const el = e.target
if (el.scrollHeight - el.scrollTop <= el.clientHeight + 50) {
if (selectedTopic.value === '排行榜') {
fetchRanking()
} else {
fetchPosts()
}
}
}
onMounted(() => {
if (selectedTopic.value === '排行榜') {
fetchRanking()
} else {
fetchPosts()
}
})
watch([selectedCategory, selectedTags], () => { watch([selectedCategory, selectedTags], () => {
fetchPosts(true) if (selectedTopic.value === '最新') {
fetchPosts(true)
}
})
watch(selectedTopic, () => {
if (selectedTopic.value === '排行榜') {
fetchRanking(true)
} else {
fetchPosts(true)
}
}) })
const sanitizeDescription = (text) => stripMarkdown(text) const sanitizeDescription = (text) => stripMarkdown(text)

View File

@@ -78,13 +78,20 @@ public class PostController {
} }
if (hasTags) { if (hasTags) {
return postService.listPostsByTags(tids, page, pageSize) return postService.listPostsByTags(tids, page, pageSize)
.stream().map(this::toDto).collect(Collectors.toList()); .stream().map(this::toDto).collect(Collectors.toList());
} }
return postService.listPostsByCategories(ids, page, pageSize) return postService.listPostsByCategories(ids, page, pageSize)
.stream().map(this::toDto).collect(Collectors.toList()); .stream().map(this::toDto).collect(Collectors.toList());
} }
@GetMapping("/ranking")
public List<PostDto> rankingPosts(@RequestParam(value = "page", required = false) Integer page,
@RequestParam(value = "pageSize", required = false) Integer pageSize) {
return postService.listPostsByViews(page, pageSize)
.stream().map(this::toDto).collect(Collectors.toList());
}
private PostDto toDto(Post post) { private PostDto toDto(Post post) {
PostDto dto = new PostDto(); PostDto dto = new PostDto();
dto.setId(post.getId()); dto.setId(post.getId());

View File

@@ -16,6 +16,8 @@ import org.springframework.data.repository.query.Param;
public interface PostRepository extends JpaRepository<Post, Long> { public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByStatus(PostStatus status); List<Post> findByStatus(PostStatus status);
List<Post> findByStatus(PostStatus status, Pageable pageable); List<Post> findByStatus(PostStatus status, Pageable pageable);
List<Post> findByStatusOrderByViewsDesc(PostStatus status);
List<Post> findByStatusOrderByViewsDesc(PostStatus status, Pageable pageable);
List<Post> findByAuthorAndStatusOrderByCreatedAtDesc(User author, PostStatus status, Pageable pageable); List<Post> findByAuthorAndStatusOrderByCreatedAtDesc(User author, PostStatus status, Pageable pageable);
List<Post> findByCategoryInAndStatus(List<Category> categories, PostStatus status); List<Post> findByCategoryInAndStatus(List<Category> categories, PostStatus status);
List<Post> findByCategoryInAndStatus(List<Category> categories, PostStatus status, Pageable pageable); List<Post> findByCategoryInAndStatus(List<Category> categories, PostStatus status, Pageable pageable);

View File

@@ -107,6 +107,17 @@ public class PostService {
return listPostsByCategories(null, null, null); return listPostsByCategories(null, null, null);
} }
public List<Post> listPostsByViews(Integer page, Integer pageSize) {
Pageable pageable = null;
if (page != null && pageSize != null) {
pageable = PageRequest.of(page, pageSize);
}
if (pageable != null) {
return postRepository.findByStatusOrderByViewsDesc(PostStatus.PUBLISHED, pageable);
}
return postRepository.findByStatusOrderByViewsDesc(PostStatus.PUBLISHED);
}
public List<Post> listPostsByCategories(java.util.List<Long> categoryIds, public List<Post> listPostsByCategories(java.util.List<Long> categoryIds,
Integer page, Integer page,
Integer pageSize) { Integer pageSize) {