mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-02-14 02:50:58 +08:00
feat: add favorites tab to user profile
This commit is contained in:
@@ -105,6 +105,17 @@ public class UserController {
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("/{identifier}/subscribed-posts")
|
||||
public java.util.List<PostMetaDto> subscribedPosts(@PathVariable("identifier") String identifier,
|
||||
@RequestParam(value = "limit", required = false) Integer limit) {
|
||||
int l = limit != null ? limit : defaultPostsLimit;
|
||||
User user = userService.findByIdentifier(identifier).orElseThrow();
|
||||
return subscriptionService.getSubscribedPosts(user.getUsername()).stream()
|
||||
.limit(l)
|
||||
.map(userMapper::toMetaDto)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
@GetMapping("/{identifier}/replies")
|
||||
public java.util.List<CommentInfoDto> userReplies(@PathVariable("identifier") String identifier,
|
||||
@RequestParam(value = "limit", required = false) Integer limit) {
|
||||
|
||||
@@ -107,6 +107,11 @@ public class SubscriptionService {
|
||||
return commentSubRepo.findByComment(c).stream().map(CommentSubscription::getUser).toList();
|
||||
}
|
||||
|
||||
public List<Post> getSubscribedPosts(String username) {
|
||||
User user = userRepo.findByUsername(username).orElseThrow();
|
||||
return postSubRepo.findByUser(user).stream().map(PostSubscription::getPost).toList();
|
||||
}
|
||||
|
||||
|
||||
public long countSubscribers(String username) {
|
||||
User user = userRepo.findByUsername(username).orElseThrow();
|
||||
|
||||
@@ -136,6 +136,30 @@ class UserControllerTest {
|
||||
.andExpect(jsonPath("$[0].title").value("hello"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listSubscribedPosts() throws Exception {
|
||||
User user = new User();
|
||||
user.setUsername("bob");
|
||||
com.openisle.model.Category cat = new com.openisle.model.Category();
|
||||
cat.setName("tech");
|
||||
com.openisle.model.Post post = new com.openisle.model.Post();
|
||||
post.setId(6L);
|
||||
post.setTitle("fav");
|
||||
post.setCreatedAt(java.time.LocalDateTime.now());
|
||||
post.setCategory(cat);
|
||||
post.setAuthor(user);
|
||||
Mockito.when(userService.findByIdentifier("bob")).thenReturn(Optional.of(user));
|
||||
Mockito.when(subscriptionService.getSubscribedPosts("bob")).thenReturn(java.util.List.of(post));
|
||||
PostMetaDto meta = new PostMetaDto();
|
||||
meta.setId(6L);
|
||||
meta.setTitle("fav");
|
||||
Mockito.when(userMapper.toMetaDto(post)).thenReturn(meta);
|
||||
|
||||
mockMvc.perform(get("/api/users/bob/subscribed-posts"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$[0].title").value("fav"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listUserReplies() throws Exception {
|
||||
User user = new User();
|
||||
|
||||
@@ -94,6 +94,13 @@
|
||||
<i class="fas fa-user-plus"></i>
|
||||
<div class="profile-tabs-item-label">关注</div>
|
||||
</div>
|
||||
<div
|
||||
:class="['profile-tabs-item', { selected: selectedTab === 'favorites' }]"
|
||||
@click="selectedTab = 'favorites'"
|
||||
>
|
||||
<i class="fas fa-bookmark"></i>
|
||||
<div class="profile-tabs-item-label">收藏</div>
|
||||
</div>
|
||||
<div
|
||||
:class="['profile-tabs-item', { selected: selectedTab === 'achievements' }]"
|
||||
@click="selectedTab = 'achievements'"
|
||||
@@ -318,6 +325,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="selectedTab === 'favorites'" class="favorites-container">
|
||||
<div v-if="favoritePosts.length > 0">
|
||||
<BaseTimeline :items="favoritePosts">
|
||||
<template #item="{ item }">
|
||||
<NuxtLink :to="`/posts/${item.post.id}`" class="timeline-link">
|
||||
{{ item.post.title }}
|
||||
</NuxtLink>
|
||||
<div class="timeline-snippet">
|
||||
{{ stripMarkdown(item.post.snippet) }}
|
||||
</div>
|
||||
<div class="timeline-date">{{ formatDate(item.post.createdAt) }}</div>
|
||||
</template>
|
||||
</BaseTimeline>
|
||||
</div>
|
||||
<div v-else class="summary-empty">暂无收藏文章</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="selectedTab === 'achievements'" class="achievements-container">
|
||||
<AchievementList :medals="medals" :can-select="isMine" />
|
||||
</div>
|
||||
@@ -352,6 +376,7 @@ const user = ref({})
|
||||
const hotPosts = ref([])
|
||||
const hotReplies = ref([])
|
||||
const hotTags = ref([])
|
||||
const favoritePosts = ref([])
|
||||
const timelineItems = ref([])
|
||||
const timelineFilter = ref('all')
|
||||
const filteredTimelineItems = computed(() => {
|
||||
@@ -369,7 +394,7 @@ const subscribed = ref(false)
|
||||
const isLoading = ref(true)
|
||||
const tabLoading = ref(false)
|
||||
const selectedTab = ref(
|
||||
['summary', 'timeline', 'following', 'achievements'].includes(route.query.tab)
|
||||
['summary', 'timeline', 'following', 'favorites', 'achievements'].includes(route.query.tab)
|
||||
? route.query.tab
|
||||
: 'summary',
|
||||
)
|
||||
@@ -472,6 +497,16 @@ const fetchFollowUsers = async () => {
|
||||
followings.value = followingRes.ok ? await followingRes.json() : []
|
||||
}
|
||||
|
||||
const fetchFavorites = async () => {
|
||||
const res = await fetch(`${API_BASE_URL}/api/users/${username}/subscribed-posts`)
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
favoritePosts.value = data.map((p) => ({ icon: 'fas fa-bookmark', post: p }))
|
||||
} else {
|
||||
favoritePosts.value = []
|
||||
}
|
||||
}
|
||||
|
||||
const loadSummary = async () => {
|
||||
tabLoading.value = true
|
||||
await fetchSummary()
|
||||
@@ -490,6 +525,12 @@ const loadFollow = async () => {
|
||||
tabLoading.value = false
|
||||
}
|
||||
|
||||
const loadFavorites = async () => {
|
||||
tabLoading.value = true
|
||||
await fetchFavorites()
|
||||
tabLoading.value = false
|
||||
}
|
||||
|
||||
const fetchAchievements = async () => {
|
||||
const res = await fetch(`${API_BASE_URL}/api/medals?userId=${user.value.id}`)
|
||||
if (res.ok) {
|
||||
@@ -578,6 +619,8 @@ const init = async () => {
|
||||
await loadTimeline()
|
||||
} else if (selectedTab.value === 'following') {
|
||||
await loadFollow()
|
||||
} else if (selectedTab.value === 'favorites') {
|
||||
await loadFavorites()
|
||||
} else if (selectedTab.value === 'achievements') {
|
||||
await loadAchievements()
|
||||
}
|
||||
@@ -596,6 +639,8 @@ watch(selectedTab, async (val) => {
|
||||
await loadTimeline()
|
||||
} else if (val === 'following' && followers.value.length === 0 && followings.value.length === 0) {
|
||||
await loadFollow()
|
||||
} else if (val === 'favorites' && favoritePosts.value.length === 0) {
|
||||
await loadFavorites()
|
||||
} else if (val === 'achievements' && medals.value.length === 0) {
|
||||
await loadAchievements()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user