From 1a12bec7b11dd863f689d4a135f678f491431a63 Mon Sep 17 00:00:00 2001 From: tim Date: Tue, 19 Aug 2025 18:26:55 +0800 Subject: [PATCH] Revert "feat: add paginated notification APIs and frontend" This reverts commit 7dd1f1b3d02c764912d79fc0a963c4fa41dedbb2. --- .../controller/NotificationController.java | 12 +- .../repository/NotificationRepository.java | 4 - .../openisle/service/NotificationService.java | 7 +- frontend_nuxt/pages/message.vue | 58 +-- frontend_nuxt/utils/notification.js | 441 ++++++++---------- 5 files changed, 209 insertions(+), 313 deletions(-) diff --git a/backend/src/main/java/com/openisle/controller/NotificationController.java b/backend/src/main/java/com/openisle/controller/NotificationController.java index 1149bff37..d25d2a808 100644 --- a/backend/src/main/java/com/openisle/controller/NotificationController.java +++ b/backend/src/main/java/com/openisle/controller/NotificationController.java @@ -23,17 +23,9 @@ public class NotificationController { private final NotificationMapper notificationMapper; @GetMapping - public List list(@RequestParam(value = "page", defaultValue = "0") int page, + public List list(@RequestParam(value = "read", required = false) Boolean read, Authentication auth) { - return notificationService.listNotifications(auth.getName(), null, page).stream() - .map(notificationMapper::toDto) - .collect(Collectors.toList()); - } - - @GetMapping("/unread") - public List listUnread(@RequestParam(value = "page", defaultValue = "0") int page, - Authentication auth) { - return notificationService.listNotifications(auth.getName(), false, page).stream() + return notificationService.listNotifications(auth.getName(), read).stream() .map(notificationMapper::toDto) .collect(Collectors.toList()); } diff --git a/backend/src/main/java/com/openisle/repository/NotificationRepository.java b/backend/src/main/java/com/openisle/repository/NotificationRepository.java index 65fddca4c..1e897b3a0 100644 --- a/backend/src/main/java/com/openisle/repository/NotificationRepository.java +++ b/backend/src/main/java/com/openisle/repository/NotificationRepository.java @@ -5,8 +5,6 @@ import com.openisle.model.User; import com.openisle.model.Post; import com.openisle.model.Comment; import com.openisle.model.NotificationType; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; @@ -15,8 +13,6 @@ import java.util.List; public interface NotificationRepository extends JpaRepository { List findByUserOrderByCreatedAtDesc(User user); List findByUserAndReadOrderByCreatedAtDesc(User user, boolean read); - Page findByUserOrderByCreatedAtDesc(User user, Pageable pageable); - Page findByUserAndReadOrderByCreatedAtDesc(User user, boolean read, Pageable pageable); long countByUserAndRead(User user, boolean read); List findByPost(Post post); List findByComment(Comment comment); diff --git a/backend/src/main/java/com/openisle/service/NotificationService.java b/backend/src/main/java/com/openisle/service/NotificationService.java index 088d0f61a..6ecf571e8 100644 --- a/backend/src/main/java/com/openisle/service/NotificationService.java +++ b/backend/src/main/java/com/openisle/service/NotificationService.java @@ -180,16 +180,15 @@ public class NotificationService { userRepository.save(user); } - public List listNotifications(String username, Boolean read, int page) { + public List listNotifications(String username, Boolean read) { User user = userRepository.findByUsername(username) .orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found")); Set disabled = user.getDisabledNotificationTypes(); List list; - var pageable = org.springframework.data.domain.PageRequest.of(page, 50); if (read == null) { - list = notificationRepository.findByUserOrderByCreatedAtDesc(user, pageable).getContent(); + list = notificationRepository.findByUserOrderByCreatedAtDesc(user); } else { - list = notificationRepository.findByUserAndReadOrderByCreatedAtDesc(user, read, pageable).getContent(); + list = notificationRepository.findByUserAndReadOrderByCreatedAtDesc(user, read); } return list.stream().filter(n -> !disabled.contains(n.getType())).collect(Collectors.toList()); } diff --git a/frontend_nuxt/pages/message.vue b/frontend_nuxt/pages/message.vue index 30eed2239..061eedd37 100644 --- a/frontend_nuxt/pages/message.vue +++ b/frontend_nuxt/pages/message.vue @@ -53,13 +53,13 @@ -
- +
+
- -
diff --git a/frontend_nuxt/utils/notification.js b/frontend_nuxt/utils/notification.js index 28403331e..7c0245b94 100644 --- a/frontend_nuxt/utils/notification.js +++ b/frontend_nuxt/utils/notification.js @@ -4,7 +4,9 @@ import { toast } from '~/composables/useToast' import { authState, getToken } from '~/utils/auth' import { reactionEmojiMap } from '~/utils/reactions' -export const notificationState = reactive({ unreadCount: 0 }) +export const notificationState = reactive({ + unreadCount: 0, +}) const iconMap = { POST_VIEWED: 'fas fa-eye', @@ -55,6 +57,7 @@ export async function markNotificationsRead(ids) { try { const config = useRuntimeConfig() const API_BASE_URL = config.public.apiBaseUrl + const token = getToken() if (!token || !ids || ids.length === 0) return false const res = await fetch(`${API_BASE_URL}/api/notifications/read`, { @@ -75,6 +78,7 @@ export async function fetchNotificationPreferences() { try { const config = useRuntimeConfig() const API_BASE_URL = config.public.apiBaseUrl + const token = getToken() if (!token) return [] const res = await fetch(`${API_BASE_URL}/api/notifications/prefs`, { @@ -108,219 +112,193 @@ export async function updateNotificationPreference(type, enabled) { } /** - * 提供通知列表的分页获取与状态管理 + * 处理信息的高阶函数 + * @returns */ function createFetchNotifications() { - const allNotifications = ref([]) - const unreadNotifications = ref([]) - const pageAll = ref(0) - const pageUnread = ref(0) - const isLoadingAll = ref(false) - const isLoadingUnread = ref(false) + const notifications = ref([]) + const isLoadingMessage = ref(false) + const fetchNotifications = async () => { + const config = useRuntimeConfig() + const API_BASE_URL = config.public.apiBaseUrl + if (isLoadingMessage && notifications && markRead) { + try { + const token = getToken() + if (!token) { + toast.error('请先登录') + return + } + isLoadingMessage.value = true + notifications.value = [] + const res = await fetch(`${API_BASE_URL}/api/notifications`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }) + isLoadingMessage.value = false + if (!res.ok) { + toast.error('获取通知失败') + return + } + const data = await res.json() - const processAndPush = (data, target) => { - for (const n of data) { - if (n.type === 'COMMENT_REPLY') { - target.push({ - ...n, - src: n.comment.author.avatar, - iconClick: () => { - markRead(n.id) - navigateTo(`/users/${n.comment.author.id}`, { replace: true }) - }, - }) - } else if (n.type === 'REACTION') { - target.push({ - ...n, - emoji: reactionEmojiMap[n.reactionType], - iconClick: () => { - if (n.fromUser) { - markRead(n.id) - navigateTo(`/users/${n.fromUser.id}`, { replace: true }) - } - }, - }) - } else if (n.type === 'POST_VIEWED') { - target.push({ - ...n, - src: n.fromUser ? n.fromUser.avatar : null, - icon: n.fromUser ? undefined : iconMap[n.type], - iconClick: () => { - if (n.fromUser) { - markRead(n.id) - navigateTo(`/users/${n.fromUser.id}`, { replace: true }) - } - }, - }) - } else if (n.type === 'LOTTERY_WIN') { - target.push({ - ...n, - icon: iconMap[n.type], - iconClick: () => { - if (n.post) { - markRead(n.id) - navigateTo(`/posts/${n.post.id}`, { replace: true }) - } - }, - }) - } else if (n.type === 'LOTTERY_DRAW') { - target.push({ - ...n, - icon: iconMap[n.type], - iconClick: () => { - if (n.post) { - markRead(n.id) - navigateTo(`/posts/${n.post.id}`, { replace: true }) - } - }, - }) - } else if (n.type === 'POST_UPDATED') { - target.push({ - ...n, - src: n.comment.author.avatar, - iconClick: () => { - markRead(n.id) - navigateTo(`/users/${n.comment.author.id}`, { replace: true }) - }, - }) - } else if (n.type === 'USER_ACTIVITY') { - target.push({ - ...n, - src: n.comment.author.avatar, - iconClick: () => { - markRead(n.id) - navigateTo(`/users/${n.comment.author.id}`, { replace: true }) - }, - }) - } else if (n.type === 'MENTION') { - target.push({ - ...n, - icon: iconMap[n.type], - iconClick: () => { - if (n.fromUser) { - markRead(n.id) - navigateTo(`/users/${n.fromUser.id}`, { replace: true }) - } - }, - }) - } else if (n.type === 'USER_FOLLOWED' || n.type === 'USER_UNFOLLOWED') { - target.push({ - ...n, - icon: iconMap[n.type], - iconClick: () => { - if (n.fromUser) { - markRead(n.id) - navigateTo(`/users/${n.fromUser.id}`, { replace: true }) - } - }, - }) - } else if (n.type === 'FOLLOWED_POST') { - target.push({ - ...n, - icon: iconMap[n.type], - iconClick: () => { - if (n.post) { - markRead(n.id) - navigateTo(`/posts/${n.post.id}`, { replace: true }) - } - }, - }) - } else if (n.type === 'POST_SUBSCRIBED' || n.type === 'POST_UNSUBSCRIBED') { - target.push({ - ...n, - icon: iconMap[n.type], - iconClick: () => { - if (n.post) { - markRead(n.id) - navigateTo(`/posts/${n.post.id}`, { replace: true }) - } - }, - }) - } else if (n.type === 'POST_REVIEW_REQUEST') { - target.push({ - ...n, - src: n.fromUser ? n.fromUser.avatar : null, - icon: n.fromUser ? undefined : iconMap[n.type], - iconClick: () => { - if (n.post) { - markRead(n.id) - navigateTo(`/posts/${n.post.id}`, { replace: true }) - } - }, - }) - } else if (n.type === 'REGISTER_REQUEST') { - target.push({ - ...n, - icon: iconMap[n.type], - iconClick: () => {}, - }) - } else { - target.push({ - ...n, - icon: iconMap[n.type], - }) + for (const n of data) { + if (n.type === 'COMMENT_REPLY') { + notifications.value.push({ + ...n, + src: n.comment.author.avatar, + iconClick: () => { + markRead(n.id) + navigateTo(`/users/${n.comment.author.id}`, { replace: true }) + }, + }) + } else if (n.type === 'REACTION') { + notifications.value.push({ + ...n, + emoji: reactionEmojiMap[n.reactionType], + iconClick: () => { + if (n.fromUser) { + markRead(n.id) + navigateTo(`/users/${n.fromUser.id}`, { replace: true }) + } + }, + }) + } else if (n.type === 'POST_VIEWED') { + notifications.value.push({ + ...n, + src: n.fromUser ? n.fromUser.avatar : null, + icon: n.fromUser ? undefined : iconMap[n.type], + iconClick: () => { + if (n.fromUser) { + markRead(n.id) + navigateTo(`/users/${n.fromUser.id}`, { replace: true }) + } + }, + }) + } else if (n.type === 'LOTTERY_WIN') { + notifications.value.push({ + ...n, + icon: iconMap[n.type], + iconClick: () => { + if (n.post) { + markRead(n.id) + router.push(`/posts/${n.post.id}`) + } + }, + }) + } else if (n.type === 'LOTTERY_DRAW') { + notifications.value.push({ + ...n, + icon: iconMap[n.type], + iconClick: () => { + if (n.post) { + markRead(n.id) + router.push(`/posts/${n.post.id}`) + } + }, + }) + } else if (n.type === 'POST_UPDATED') { + notifications.value.push({ + ...n, + src: n.comment.author.avatar, + iconClick: () => { + markRead(n.id) + navigateTo(`/users/${n.comment.author.id}`, { replace: true }) + }, + }) + } else if (n.type === 'USER_ACTIVITY') { + notifications.value.push({ + ...n, + src: n.comment.author.avatar, + iconClick: () => { + markRead(n.id) + navigateTo(`/users/${n.comment.author.id}`, { replace: true }) + }, + }) + } else if (n.type === 'MENTION') { + notifications.value.push({ + ...n, + icon: iconMap[n.type], + iconClick: () => { + if (n.fromUser) { + markRead(n.id) + navigateTo(`/users/${n.fromUser.id}`, { replace: true }) + } + }, + }) + } else if (n.type === 'USER_FOLLOWED' || n.type === 'USER_UNFOLLOWED') { + notifications.value.push({ + ...n, + icon: iconMap[n.type], + iconClick: () => { + if (n.fromUser) { + markRead(n.id) + navigateTo(`/users/${n.fromUser.id}`, { replace: true }) + } + }, + }) + } else if (n.type === 'FOLLOWED_POST') { + notifications.value.push({ + ...n, + icon: iconMap[n.type], + iconClick: () => { + if (n.post) { + markRead(n.id) + navigateTo(`/posts/${n.post.id}`, { replace: true }) + } + }, + }) + } else if (n.type === 'POST_SUBSCRIBED' || n.type === 'POST_UNSUBSCRIBED') { + notifications.value.push({ + ...n, + icon: iconMap[n.type], + iconClick: () => { + if (n.post) { + markRead(n.id) + navigateTo(`/posts/${n.post.id}`, { replace: true }) + } + }, + }) + } else if (n.type === 'POST_REVIEW_REQUEST') { + notifications.value.push({ + ...n, + src: n.fromUser ? n.fromUser.avatar : null, + icon: n.fromUser ? undefined : iconMap[n.type], + iconClick: () => { + if (n.post) { + markRead(n.id) + navigateTo(`/posts/${n.post.id}`, { replace: true }) + } + }, + }) + } else if (n.type === 'REGISTER_REQUEST') { + notifications.value.push({ + ...n, + icon: iconMap[n.type], + iconClick: () => {}, + }) + } else { + notifications.value.push({ + ...n, + icon: iconMap[n.type], + }) + } + } + } catch (e) { + console.error(e) } } } - const fetchAllNotifications = async () => { - const config = useRuntimeConfig() - const API_BASE_URL = config.public.apiBaseUrl - const token = getToken() - if (!token) { - toast.error('请先登录') - return { done: true } - } - isLoadingAll.value = true - const res = await fetch(`${API_BASE_URL}/api/notifications?page=${pageAll.value}`, { - headers: { Authorization: `Bearer ${token}` }, - }) - isLoadingAll.value = false - if (!res.ok) { - toast.error('获取通知失败') - return { done: true } - } - const data = await res.json() - processAndPush(data, allNotifications.value) - pageAll.value++ - return { done: data.length < 50 } - } - - const fetchUnreadNotifications = async () => { - const config = useRuntimeConfig() - const API_BASE_URL = config.public.apiBaseUrl - const token = getToken() - if (!token) { - toast.error('请先登录') - return { done: true } - } - isLoadingUnread.value = true - const res = await fetch(`${API_BASE_URL}/api/notifications/unread?page=${pageUnread.value}`, { - headers: { Authorization: `Bearer ${token}` }, - }) - isLoadingUnread.value = false - if (!res.ok) { - toast.error('获取通知失败') - return { done: true } - } - const data = await res.json() - processAndPush(data, unreadNotifications.value) - pageUnread.value++ - return { done: data.length < 50 } - } - const markRead = async (id) => { if (!id) return - const nAll = allNotifications.value.find((n) => n.id === id) - const idxUnread = unreadNotifications.value.findIndex((n) => n.id === id) - const unreadItem = idxUnread !== -1 ? unreadNotifications.value[idxUnread] : null - if (nAll) nAll.read = true - if (idxUnread !== -1) unreadNotifications.value.splice(idxUnread, 1) + const n = notifications.value.find((n) => n.id === id) + if (!n || n.read) return + n.read = true if (notificationState.unreadCount > 0) notificationState.unreadCount-- const ok = await markNotificationsRead([id]) if (!ok) { - if (nAll) nAll.read = false - if (idxUnread !== -1 && unreadItem) unreadNotifications.value.splice(idxUnread, 0, unreadItem) + n.read = false notificationState.unreadCount++ } else { fetchUnreadCount() @@ -328,28 +306,20 @@ function createFetchNotifications() { } const markAllRead = async () => { - const idsToMark = [ - ...new Set( - [...allNotifications.value, ...unreadNotifications.value] - .filter((n) => n.type !== 'REGISTER_REQUEST' && !n.read) - .map((n) => n.id), - ), - ] + // 除了 REGISTER_REQUEST 类型消息 + const idsToMark = notifications.value + .filter((n) => n.type !== 'REGISTER_REQUEST' && !n.read) + .map((n) => n.id) if (idsToMark.length === 0) return - allNotifications.value.forEach((n) => { + notifications.value.forEach((n) => { if (n.type !== 'REGISTER_REQUEST') n.read = true }) - const prevUnread = [...unreadNotifications.value] - unreadNotifications.value = unreadNotifications.value.filter( - (n) => n.type === 'REGISTER_REQUEST', - ) - notificationState.unreadCount = unreadNotifications.value.length + notificationState.unreadCount = notifications.value.filter((n) => !n.read).length const ok = await markNotificationsRead(idsToMark) if (!ok) { - allNotifications.value.forEach((n) => { + notifications.value.forEach((n) => { if (idsToMark.includes(n.id)) n.read = false }) - unreadNotifications.value = prevUnread await fetchUnreadCount() return } @@ -360,40 +330,15 @@ function createFetchNotifications() { toast.success('已读所有消息') } } - - const resetAll = () => { - pageAll.value = 0 - allNotifications.value = [] - } - - const resetUnread = () => { - pageUnread.value = 0 - unreadNotifications.value = [] - } - return { - fetchAllNotifications, - fetchUnreadNotifications, + fetchNotifications, + markRead, + notifications, + isLoadingMessage, markRead, markAllRead, - allNotifications, - unreadNotifications, - isLoadingAll, - isLoadingUnread, - resetAll, - resetUnread, } } -export const { - fetchAllNotifications, - fetchUnreadNotifications, - markRead, - markAllRead, - allNotifications, - unreadNotifications, - isLoadingAll, - isLoadingUnread, - resetAll, - resetUnread, -} = createFetchNotifications() +export const { fetchNotifications, markRead, notifications, isLoadingMessage, markAllRead } = + createFetchNotifications()