feat: auto select medals and make badges interactive

This commit is contained in:
Tim
2025-08-10 00:25:13 +08:00
parent 594068bd5e
commit 4516f77727
4 changed files with 60 additions and 4 deletions

View File

@@ -83,6 +83,17 @@ public class MedalService {
seedUserMedal.setSelected(selected == MedalType.SEED); seedUserMedal.setSelected(selected == MedalType.SEED);
medals.add(seedUserMedal); 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; return medals;
} }

View File

@@ -92,4 +92,24 @@ class MedalServiceTest {
MedalService service = new MedalService(commentRepo, postRepo, userRepo); MedalService service = new MedalService(commentRepo, postRepo, userRepo);
assertThrows(IllegalArgumentException.class, () -> service.selectMedal("user", MedalType.COMMENT)); 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<MedalDto> medals = service.getMedals(1L);
assertEquals(MedalType.COMMENT, user.getDisplayMedal());
assertTrue(medals.stream().anyMatch(m -> m.getType() == MedalType.COMMENT && m.isSelected()));
}
} }

View File

@@ -11,7 +11,11 @@
<div class="common-info-content-header"> <div class="common-info-content-header">
<div class="info-content-header-left"> <div class="info-content-header-left">
<span class="user-name">{{ comment.userName }}</span> <span class="user-name">{{ comment.userName }}</span>
<span v-if="comment.medal" class="medal-name">{{ getMedalTitle(comment.medal) }}</span> <span
v-if="comment.medal"
class="medal-name"
@click="gotoProfileMedals(comment.userId)"
>{{ getMedalTitle(comment.medal) }}</span>
<span v-if="level >= 2"> <span v-if="level >= 2">
<i class="fas fa-reply reply-icon"></i> <i class="fas fa-reply reply-icon"></i>
<span class="user-name reply-user-name">{{ comment.parentUserName }}</span> <span class="user-name reply-user-name">{{ comment.parentUserName }}</span>
@@ -115,6 +119,10 @@ const CommentItem = {
showEditor.value = !showEditor.value showEditor.value = !showEditor.value
} }
const gotoProfileMedals = (id) => {
router.push(`/users/${id}?tab=achievements`)
}
// 合并所有子回复为一个扁平数组 // 合并所有子回复为一个扁平数组
const flattenReplies = (list) => { const flattenReplies = (list) => {
let result = [] let result = []
@@ -234,7 +242,7 @@ const CommentItem = {
lightboxVisible.value = true 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; font-size: 12px;
margin-left: 4px; margin-left: 4px;
opacity: 0.6; opacity: 0.6;
cursor: pointer;
} }
@keyframes highlight { @keyframes highlight {

View File

@@ -43,7 +43,11 @@
<div v-if="isMobile" class="info-content-header"> <div v-if="isMobile" class="info-content-header">
<div class="user-name"> <div class="user-name">
{{ author.username }} {{ author.username }}
<span v-if="author.displayMedal" class="user-medal">{{ getMedalTitle(author.displayMedal) }}</span> <span
v-if="author.displayMedal"
class="user-medal"
@click="gotoProfileMedals"
>{{ getMedalTitle(author.displayMedal) }}</span>
</div> </div>
<div class="post-time">{{ postTime }}</div> <div class="post-time">{{ postTime }}</div>
</div> </div>
@@ -53,7 +57,11 @@
<div v-if="!isMobile" class="info-content-header"> <div v-if="!isMobile" class="info-content-header">
<div class="user-name"> <div class="user-name">
{{ author.username }} {{ author.username }}
<span v-if="author.displayMedal" class="user-medal">{{ getMedalTitle(author.displayMedal) }}</span> <span
v-if="author.displayMedal"
class="user-medal"
@click="gotoProfileMedals"
>{{ getMedalTitle(author.displayMedal) }}</span>
</div> </div>
<div class="post-time">{{ postTime }}</div> <div class="post-time">{{ postTime }}</div>
</div> </div>
@@ -235,6 +243,7 @@ export default {
const mapComment = (c, parentUserName = '', level = 0) => ({ const mapComment = (c, parentUserName = '', level = 0) => ({
id: c.id, id: c.id,
userName: c.author.username, userName: c.author.username,
userId: c.author.id,
medal: c.author.displayMedal, medal: c.author.displayMedal,
time: TimeManager.format(c.createdAt), time: TimeManager.format(c.createdAt),
avatar: c.author.avatar, avatar: c.author.avatar,
@@ -598,6 +607,10 @@ export default {
router.push(`/users/${author.value.id}`) router.push(`/users/${author.value.id}`)
} }
const gotoProfileMedals = () => {
router.push(`/users/${author.value.id}?tab=achievements`)
}
await fetchPost() await fetchPost()
onMounted(async () => { onMounted(async () => {
@@ -635,6 +648,7 @@ export default {
isWaitingFetchingPost, isWaitingFetchingPost,
isWaitingPostingComment, isWaitingPostingComment,
gotoProfile, gotoProfile,
gotoProfileMedals,
subscribed, subscribed,
loggedIn, loggedIn,
isAuthor, isAuthor,
@@ -940,6 +954,7 @@ export default {
font-size: 12px; font-size: 12px;
margin-left: 4px; margin-left: 4px;
opacity: 0.6; opacity: 0.6;
cursor: pointer;
} }
.post-time { .post-time {
@@ -1008,6 +1023,7 @@ export default {
.user-medal { .user-medal {
font-size: 12px; font-size: 12px;
cursor: pointer;
} }
.post-time { .post-time {