Merge pull request #454 from nagisa77/codex/update-medal-selection-logic-in-backend

feat: auto select medals and improve navigation
This commit is contained in:
Tim
2025-08-10 00:59:51 +08:00
committed by GitHub
5 changed files with 32 additions and 6 deletions

View File

@@ -82,6 +82,16 @@ public class MedalService {
} }
seedUserMedal.setSelected(selected == MedalType.SEED); seedUserMedal.setSelected(selected == MedalType.SEED);
medals.add(seedUserMedal); medals.add(seedUserMedal);
if (user != null && selected == null) {
for (MedalDto medal : medals) {
if (medal.isCompleted()) {
medal.setSelected(true);
user.setDisplayMedal(medal.getType());
userRepository.save(user);
break;
}
}
}
return medals; return medals;
} }

View File

@@ -41,19 +41,20 @@ class MedalServiceTest {
User user = new User(); User user = new User();
user.setId(1L); user.setId(1L);
user.setCreatedAt(LocalDateTime.of(2025, 9, 15, 0, 0)); user.setCreatedAt(LocalDateTime.of(2025, 9, 15, 0, 0));
user.setDisplayMedal(MedalType.COMMENT);
when(userRepo.findById(1L)).thenReturn(Optional.of(user)); when(userRepo.findById(1L)).thenReturn(Optional.of(user));
when(userRepo.findByUsername("user")).thenReturn(Optional.of(user)); when(userRepo.findByUsername("user")).thenReturn(Optional.of(user));
MedalService service = new MedalService(commentRepo, postRepo, userRepo); MedalService service = new MedalService(commentRepo, postRepo, userRepo);
List<MedalDto> medals = service.getMedals(1L); List<MedalDto> medals = service.getMedals(1L);
assertEquals(MedalType.COMMENT, user.getDisplayMedal());
assertTrue(medals.stream().filter(m -> m.getType() == MedalType.COMMENT).findFirst().orElseThrow().isCompleted()); assertTrue(medals.stream().filter(m -> m.getType() == MedalType.COMMENT).findFirst().orElseThrow().isCompleted());
assertTrue(medals.stream().filter(m -> m.getType() == MedalType.COMMENT).findFirst().orElseThrow().isSelected()); assertTrue(medals.stream().filter(m -> m.getType() == MedalType.COMMENT).findFirst().orElseThrow().isSelected());
assertFalse(medals.stream().filter(m -> m.getType() == MedalType.POST).findFirst().orElseThrow().isCompleted()); assertFalse(medals.stream().filter(m -> m.getType() == MedalType.POST).findFirst().orElseThrow().isCompleted());
assertFalse(medals.stream().filter(m -> m.getType() == MedalType.POST).findFirst().orElseThrow().isSelected()); assertFalse(medals.stream().filter(m -> m.getType() == MedalType.POST).findFirst().orElseThrow().isSelected());
assertTrue(medals.stream().filter(m -> m.getType() == MedalType.SEED).findFirst().orElseThrow().isCompleted()); assertTrue(medals.stream().filter(m -> m.getType() == MedalType.SEED).findFirst().orElseThrow().isCompleted());
assertFalse(medals.stream().filter(m -> m.getType() == MedalType.SEED).findFirst().orElseThrow().isSelected()); assertFalse(medals.stream().filter(m -> m.getType() == MedalType.SEED).findFirst().orElseThrow().isSelected());
verify(userRepo).save(user);
} }
@Test @Test

View File

@@ -3,7 +3,7 @@
<div <div
v-for="medal in sortedMedals" v-for="medal in sortedMedals"
:key="medal.type" :key="medal.type"
:class="['achievements-list-item', { select: medal.selected, clickable: canSelect }]" :class="['achievements-list-item', { select: medal.selected && canSelect, clickable: canSelect }]"
@click="selectMedal(medal)" @click="selectMedal(medal)"
> >
<img <img
@@ -11,7 +11,7 @@
:alt="medal.title" :alt="medal.title"
:class="['achievements-list-item-icon', { not_completed: !medal.completed }]" :class="['achievements-list-item-icon', { not_completed: !medal.completed }]"
/> />
<div v-if="medal.selected" class="achievements-list-item-top-right-label">展示</div> <div v-if="medal.selected && canSelect" class="achievements-list-item-top-right-label">展示</div>
<div class="achievements-list-item-title">{{ medal.title }}</div> <div class="achievements-list-item-title">{{ medal.title }}</div>
<div class="achievements-list-item-description"> <div class="achievements-list-item-description">
{{ medal.description }} {{ medal.description }}

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> <router-link
v-if="comment.medal"
class="medal-name"
:to="`/users/${comment.userId}?tab=achievements`"
>{{ getMedalTitle(comment.medal) }}</router-link>
<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>
@@ -289,6 +293,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> <router-link
v-if="author.displayMedal"
class="user-medal"
:to="`/users/${author.id}?tab=achievements`"
>{{ getMedalTitle(author.displayMedal) }}</router-link>
</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> <router-link
v-if="author.displayMedal"
class="user-medal"
:to="`/users/${author.id}?tab=achievements`"
>{{ getMedalTitle(author.displayMedal) }}</router-link>
</div> </div>
<div class="post-time">{{ postTime }}</div> <div class="post-time">{{ postTime }}</div>
</div> </div>
@@ -236,6 +244,7 @@ export default {
id: c.id, id: c.id,
userName: c.author.username, userName: c.author.username,
medal: c.author.displayMedal, medal: c.author.displayMedal,
userId: c.author.id,
time: TimeManager.format(c.createdAt), time: TimeManager.format(c.createdAt),
avatar: c.author.avatar, avatar: c.author.avatar,
text: c.content, text: c.content,
@@ -940,6 +949,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 {