mirror of
https://github.com/nagisa77/OpenIsle.git
synced 2026-05-10 12:47:30 +08:00
feat: show unread message count
This commit is contained in:
@@ -9,6 +9,7 @@
|
|||||||
<router-link class="menu-item" exact-active-class="selected" to="/message">
|
<router-link class="menu-item" exact-active-class="selected" to="/message">
|
||||||
<i class="menu-item-icon fas fa-envelope"></i>
|
<i class="menu-item-icon fas fa-envelope"></i>
|
||||||
<span class="menu-item-text">我的消息</span>
|
<span class="menu-item-text">我的消息</span>
|
||||||
|
<span v-if="unreadCount > 0" class="unread-dot"></span>
|
||||||
</router-link>
|
</router-link>
|
||||||
<router-link class="menu-item" exact-active-class="selected" to="/about">
|
<router-link class="menu-item" exact-active-class="selected" to="/about">
|
||||||
<i class="menu-item-icon fas fa-info-circle"></i>
|
<i class="menu-item-icon fas fa-info-circle"></i>
|
||||||
@@ -31,6 +32,9 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { themeState, cycleTheme, ThemeMode } from '../utils/theme'
|
import { themeState, cycleTheme, ThemeMode } from '../utils/theme'
|
||||||
|
import { authState } from '../utils/auth'
|
||||||
|
import { fetchUnreadCount } from '../utils/notification'
|
||||||
|
import { watch } from 'vue'
|
||||||
export default {
|
export default {
|
||||||
name: 'MenuComponent',
|
name: 'MenuComponent',
|
||||||
props: {
|
props: {
|
||||||
@@ -39,6 +43,9 @@ export default {
|
|||||||
default: true
|
default: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return { unreadCount: 0 }
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
iconClass() {
|
iconClass() {
|
||||||
switch (themeState.mode) {
|
switch (themeState.mode) {
|
||||||
@@ -51,6 +58,19 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async mounted() {
|
||||||
|
const updateCount = async () => {
|
||||||
|
if (authState.loggedIn) {
|
||||||
|
this.unreadCount = await fetchUnreadCount()
|
||||||
|
} else {
|
||||||
|
this.unreadCount = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await updateCount()
|
||||||
|
watch(() => authState.loggedIn, async () => {
|
||||||
|
await updateCount()
|
||||||
|
})
|
||||||
|
},
|
||||||
methods: { cycleTheme }
|
methods: { cycleTheme }
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -94,6 +114,15 @@ export default {
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.unread-dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
margin-left: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: red;
|
||||||
|
}
|
||||||
|
|
||||||
.menu-footer {
|
.menu-footer {
|
||||||
height: 30px;
|
height: 30px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
17
open-isle-cli/src/utils/notification.js
Normal file
17
open-isle-cli/src/utils/notification.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { API_BASE_URL } from '../main'
|
||||||
|
import { getToken } from './auth'
|
||||||
|
|
||||||
|
export async function fetchUnreadCount() {
|
||||||
|
try {
|
||||||
|
const token = getToken()
|
||||||
|
if (!token) return 0
|
||||||
|
const res = await fetch(`${API_BASE_URL}/api/notifications/unread-count`, {
|
||||||
|
headers: { Authorization: `Bearer ${token}` }
|
||||||
|
})
|
||||||
|
if (!res.ok) return 0
|
||||||
|
const data = await res.json()
|
||||||
|
return data.count
|
||||||
|
} catch (e) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,14 @@ public class NotificationController {
|
|||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping("/unread-count")
|
||||||
|
public UnreadCount unreadCount(Authentication auth) {
|
||||||
|
long count = notificationService.countUnread(auth.getName());
|
||||||
|
UnreadCount uc = new UnreadCount();
|
||||||
|
uc.setCount(count);
|
||||||
|
return uc;
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/read")
|
@PostMapping("/read")
|
||||||
public void markRead(@RequestBody MarkReadRequest req, Authentication auth) {
|
public void markRead(@RequestBody MarkReadRequest req, Authentication auth) {
|
||||||
notificationService.markRead(auth.getName(), req.getIds());
|
notificationService.markRead(auth.getName(), req.getIds());
|
||||||
@@ -115,4 +123,9 @@ public class NotificationController {
|
|||||||
private String username;
|
private String username;
|
||||||
private String avatar;
|
private String avatar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class UnreadCount {
|
||||||
|
private long count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,4 +10,5 @@ import java.util.List;
|
|||||||
public interface NotificationRepository extends JpaRepository<Notification, Long> {
|
public interface NotificationRepository extends JpaRepository<Notification, Long> {
|
||||||
List<Notification> findByUserOrderByCreatedAtDesc(User user);
|
List<Notification> findByUserOrderByCreatedAtDesc(User user);
|
||||||
List<Notification> findByUserAndReadOrderByCreatedAtDesc(User user, boolean read);
|
List<Notification> findByUserAndReadOrderByCreatedAtDesc(User user, boolean read);
|
||||||
|
long countByUserAndRead(User user, boolean read);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,4 +45,10 @@ public class NotificationService {
|
|||||||
}
|
}
|
||||||
notificationRepository.saveAll(notifs);
|
notificationRepository.saveAll(notifs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long countUnread(String username) {
|
||||||
|
User user = userRepository.findByUsername(username)
|
||||||
|
.orElseThrow(() -> new IllegalArgumentException("User not found"));
|
||||||
|
return notificationRepository.countByUserAndRead(user, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,4 +60,14 @@ class NotificationControllerTest {
|
|||||||
|
|
||||||
verify(notificationService).markRead("alice", List.of(1L,2L));
|
verify(notificationService).markRead("alice", List.of(1L,2L));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void unreadCountEndpoint() throws Exception {
|
||||||
|
Mockito.when(notificationService.countUnread("alice")).thenReturn(3L);
|
||||||
|
|
||||||
|
mockMvc.perform(get("/api/notifications/unread-count")
|
||||||
|
.principal(new UsernamePasswordAuthenticationToken("alice","p")))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(jsonPath("$.count").value(3));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,4 +60,22 @@ class NotificationServiceTest {
|
|||||||
assertEquals(1, list.size());
|
assertEquals(1, list.size());
|
||||||
verify(nRepo).findByUserOrderByCreatedAtDesc(user);
|
verify(nRepo).findByUserOrderByCreatedAtDesc(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void countUnreadReturnsRepositoryValue() {
|
||||||
|
NotificationRepository nRepo = mock(NotificationRepository.class);
|
||||||
|
UserRepository uRepo = mock(UserRepository.class);
|
||||||
|
NotificationService service = new NotificationService(nRepo, uRepo);
|
||||||
|
|
||||||
|
User user = new User();
|
||||||
|
user.setId(3L);
|
||||||
|
user.setUsername("carl");
|
||||||
|
when(uRepo.findByUsername("carl")).thenReturn(Optional.of(user));
|
||||||
|
when(nRepo.countByUserAndRead(user, false)).thenReturn(5L);
|
||||||
|
|
||||||
|
long count = service.countUnread("carl");
|
||||||
|
|
||||||
|
assertEquals(5L, count);
|
||||||
|
verify(nRepo).countByUserAndRead(user, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user