mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-03-04 02:50:59 +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());
|
.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")
|
@GetMapping("/{identifier}/replies")
|
||||||
public java.util.List<CommentInfoDto> userReplies(@PathVariable("identifier") String identifier,
|
public java.util.List<CommentInfoDto> userReplies(@PathVariable("identifier") String identifier,
|
||||||
@RequestParam(value = "limit", required = false) Integer limit) {
|
@RequestParam(value = "limit", required = false) Integer limit) {
|
||||||
|
|||||||
@@ -107,6 +107,11 @@ public class SubscriptionService {
|
|||||||
return commentSubRepo.findByComment(c).stream().map(CommentSubscription::getUser).toList();
|
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) {
|
public long countSubscribers(String username) {
|
||||||
User user = userRepo.findByUsername(username).orElseThrow();
|
User user = userRepo.findByUsername(username).orElseThrow();
|
||||||
|
|||||||
@@ -136,6 +136,30 @@ class UserControllerTest {
|
|||||||
.andExpect(jsonPath("$[0].title").value("hello"));
|
.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
|
@Test
|
||||||
void listUserReplies() throws Exception {
|
void listUserReplies() throws Exception {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
|
|||||||
@@ -94,6 +94,13 @@
|
|||||||
<i class="fas fa-user-plus"></i>
|
<i class="fas fa-user-plus"></i>
|
||||||
<div class="profile-tabs-item-label">关注</div>
|
<div class="profile-tabs-item-label">关注</div>
|
||||||
</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
|
<div
|
||||||
:class="['profile-tabs-item', { selected: selectedTab === 'achievements' }]"
|
:class="['profile-tabs-item', { selected: selectedTab === 'achievements' }]"
|
||||||
@click="selectedTab = 'achievements'"
|
@click="selectedTab = 'achievements'"
|
||||||
@@ -318,6 +325,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<div v-else-if="selectedTab === 'achievements'" class="achievements-container">
|
||||||
<AchievementList :medals="medals" :can-select="isMine" />
|
<AchievementList :medals="medals" :can-select="isMine" />
|
||||||
</div>
|
</div>
|
||||||
@@ -352,6 +376,7 @@ const user = ref({})
|
|||||||
const hotPosts = ref([])
|
const hotPosts = ref([])
|
||||||
const hotReplies = ref([])
|
const hotReplies = ref([])
|
||||||
const hotTags = ref([])
|
const hotTags = ref([])
|
||||||
|
const favoritePosts = ref([])
|
||||||
const timelineItems = ref([])
|
const timelineItems = ref([])
|
||||||
const timelineFilter = ref('all')
|
const timelineFilter = ref('all')
|
||||||
const filteredTimelineItems = computed(() => {
|
const filteredTimelineItems = computed(() => {
|
||||||
@@ -369,7 +394,7 @@ const subscribed = ref(false)
|
|||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const tabLoading = ref(false)
|
const tabLoading = ref(false)
|
||||||
const selectedTab = ref(
|
const selectedTab = ref(
|
||||||
['summary', 'timeline', 'following', 'achievements'].includes(route.query.tab)
|
['summary', 'timeline', 'following', 'favorites', 'achievements'].includes(route.query.tab)
|
||||||
? route.query.tab
|
? route.query.tab
|
||||||
: 'summary',
|
: 'summary',
|
||||||
)
|
)
|
||||||
@@ -472,6 +497,16 @@ const fetchFollowUsers = async () => {
|
|||||||
followings.value = followingRes.ok ? await followingRes.json() : []
|
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 () => {
|
const loadSummary = async () => {
|
||||||
tabLoading.value = true
|
tabLoading.value = true
|
||||||
await fetchSummary()
|
await fetchSummary()
|
||||||
@@ -490,6 +525,12 @@ const loadFollow = async () => {
|
|||||||
tabLoading.value = false
|
tabLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadFavorites = async () => {
|
||||||
|
tabLoading.value = true
|
||||||
|
await fetchFavorites()
|
||||||
|
tabLoading.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const fetchAchievements = async () => {
|
const fetchAchievements = async () => {
|
||||||
const res = await fetch(`${API_BASE_URL}/api/medals?userId=${user.value.id}`)
|
const res = await fetch(`${API_BASE_URL}/api/medals?userId=${user.value.id}`)
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
@@ -578,6 +619,8 @@ const init = async () => {
|
|||||||
await loadTimeline()
|
await loadTimeline()
|
||||||
} else if (selectedTab.value === 'following') {
|
} else if (selectedTab.value === 'following') {
|
||||||
await loadFollow()
|
await loadFollow()
|
||||||
|
} else if (selectedTab.value === 'favorites') {
|
||||||
|
await loadFavorites()
|
||||||
} else if (selectedTab.value === 'achievements') {
|
} else if (selectedTab.value === 'achievements') {
|
||||||
await loadAchievements()
|
await loadAchievements()
|
||||||
}
|
}
|
||||||
@@ -596,6 +639,8 @@ watch(selectedTab, async (val) => {
|
|||||||
await loadTimeline()
|
await loadTimeline()
|
||||||
} else if (val === 'following' && followers.value.length === 0 && followings.value.length === 0) {
|
} else if (val === 'following' && followers.value.length === 0 && followings.value.length === 0) {
|
||||||
await loadFollow()
|
await loadFollow()
|
||||||
|
} else if (val === 'favorites' && favoritePosts.value.length === 0) {
|
||||||
|
await loadFavorites()
|
||||||
} else if (val === 'achievements' && medals.value.length === 0) {
|
} else if (val === 'achievements' && medals.value.length === 0) {
|
||||||
await loadAchievements()
|
await loadAchievements()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user