From 4516f77727ac3c9d2fd323a5e476df799fd2bf96 Mon Sep 17 00:00:00 2001 From: Tim <135014430+nagisa77@users.noreply.github.com> Date: Sun, 10 Aug 2025 00:25:13 +0800 Subject: [PATCH] feat: auto select medals and make badges interactive --- .../com/openisle/service/MedalService.java | 11 ++++++++++ .../openisle/service/MedalServiceTest.java | 20 +++++++++++++++++++ frontend_nuxt/components/CommentItem.vue | 13 ++++++++++-- frontend_nuxt/pages/posts/[id]/index.vue | 20 +++++++++++++++++-- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/openisle/service/MedalService.java b/backend/src/main/java/com/openisle/service/MedalService.java index f40ceeb00..74aa8e308 100644 --- a/backend/src/main/java/com/openisle/service/MedalService.java +++ b/backend/src/main/java/com/openisle/service/MedalService.java @@ -83,6 +83,17 @@ public class MedalService { seedUserMedal.setSelected(selected == MedalType.SEED); medals.add(seedUserMedal); + if (user != null && medals.stream().noneMatch(MedalDto::isSelected)) { + medals.stream() + .filter(MedalDto::isCompleted) + .findFirst() + .ifPresent(m -> { + m.setSelected(true); + user.setDisplayMedal(m.getType()); + userRepository.save(user); + }); + } + return medals; } diff --git a/backend/src/test/java/com/openisle/service/MedalServiceTest.java b/backend/src/test/java/com/openisle/service/MedalServiceTest.java index bcc8659c3..dd6208de9 100644 --- a/backend/src/test/java/com/openisle/service/MedalServiceTest.java +++ b/backend/src/test/java/com/openisle/service/MedalServiceTest.java @@ -92,4 +92,24 @@ class MedalServiceTest { MedalService service = new MedalService(commentRepo, postRepo, userRepo); assertThrows(IllegalArgumentException.class, () -> service.selectMedal("user", MedalType.COMMENT)); } + + @Test + void autoSelectFirstCompletedMedal() { + CommentRepository commentRepo = mock(CommentRepository.class); + PostRepository postRepo = mock(PostRepository.class); + UserRepository userRepo = mock(UserRepository.class); + + when(commentRepo.countByAuthor_Id(1L)).thenReturn(120L); + when(postRepo.countByAuthor_Id(1L)).thenReturn(0L); + User user = new User(); + user.setId(1L); + user.setCreatedAt(LocalDateTime.of(2025, 9, 15, 0, 0)); + when(userRepo.findById(1L)).thenReturn(Optional.of(user)); + + MedalService service = new MedalService(commentRepo, postRepo, userRepo); + List medals = service.getMedals(1L); + + assertEquals(MedalType.COMMENT, user.getDisplayMedal()); + assertTrue(medals.stream().anyMatch(m -> m.getType() == MedalType.COMMENT && m.isSelected())); + } } diff --git a/frontend_nuxt/components/CommentItem.vue b/frontend_nuxt/components/CommentItem.vue index 54d1ceb28..d3c73c32d 100644 --- a/frontend_nuxt/components/CommentItem.vue +++ b/frontend_nuxt/components/CommentItem.vue @@ -11,7 +11,11 @@
{{ comment.userName }} - {{ getMedalTitle(comment.medal) }} + {{ getMedalTitle(comment.medal) }} {{ comment.parentUserName }} @@ -115,6 +119,10 @@ const CommentItem = { showEditor.value = !showEditor.value } + const gotoProfileMedals = (id) => { + router.push(`/users/${id}?tab=achievements`) + } + // 合并所有子回复为一个扁平数组 const flattenReplies = (list) => { let result = [] @@ -234,7 +242,7 @@ const CommentItem = { lightboxVisible.value = true } } - return { showReplies, toggleReplies, showEditor, toggleEditor, submitReply, copyCommentLink, renderMarkdown, isWaitingForReply, commentMenuItems, deleteComment, lightboxVisible, lightboxIndex, lightboxImgs, handleContentClick, loggedIn, replyCount, replyList, getMedalTitle } + return { showReplies, toggleReplies, showEditor, toggleEditor, gotoProfileMedals, submitReply, copyCommentLink, renderMarkdown, isWaitingForReply, commentMenuItems, deleteComment, lightboxVisible, lightboxIndex, lightboxImgs, handleContentClick, loggedIn, replyCount, replyList, getMedalTitle } } } @@ -289,6 +297,7 @@ export default CommentItem font-size: 12px; margin-left: 4px; opacity: 0.6; + cursor: pointer; } @keyframes highlight { diff --git a/frontend_nuxt/pages/posts/[id]/index.vue b/frontend_nuxt/pages/posts/[id]/index.vue index 67060b041..26253aae9 100644 --- a/frontend_nuxt/pages/posts/[id]/index.vue +++ b/frontend_nuxt/pages/posts/[id]/index.vue @@ -43,7 +43,11 @@
{{ author.username }} - {{ getMedalTitle(author.displayMedal) }} + {{ getMedalTitle(author.displayMedal) }}
{{ postTime }}
@@ -53,7 +57,11 @@
{{ author.username }} - {{ getMedalTitle(author.displayMedal) }} + {{ getMedalTitle(author.displayMedal) }}
{{ postTime }}
@@ -235,6 +243,7 @@ export default { const mapComment = (c, parentUserName = '', level = 0) => ({ id: c.id, userName: c.author.username, + userId: c.author.id, medal: c.author.displayMedal, time: TimeManager.format(c.createdAt), avatar: c.author.avatar, @@ -598,6 +607,10 @@ export default { router.push(`/users/${author.value.id}`) } + const gotoProfileMedals = () => { + router.push(`/users/${author.value.id}?tab=achievements`) + } + await fetchPost() onMounted(async () => { @@ -635,6 +648,7 @@ export default { isWaitingFetchingPost, isWaitingPostingComment, gotoProfile, + gotoProfileMedals, subscribed, loggedIn, isAuthor, @@ -940,6 +954,7 @@ export default { font-size: 12px; margin-left: 4px; opacity: 0.6; + cursor: pointer; } .post-time { @@ -1008,6 +1023,7 @@ export default { .user-medal { font-size: 12px; + cursor: pointer; } .post-time {