From b3cf2e366b29ef8b40b7c802ece9a450c8884234 Mon Sep 17 00:00:00 2001 From: susie-laptop Date: Thu, 17 Apr 2025 10:03:36 -0400 Subject: [PATCH] [dev]update notificationcenter --- .env | 2 +- src/components/NotificationCenter.jsx | 169 +++++++++++------- src/layouts/HeaderWithNav.jsx | 23 ++- src/pages/auth/Login.jsx | 4 +- src/services/websocket.js | 62 +++++-- .../notificationCenter.slice.js | 95 ++++------ .../notificationCenter.thunks.js | 135 ++++++++++++++ 7 files changed, 350 insertions(+), 140 deletions(-) create mode 100644 src/store/notificationCenter/notificationCenter.thunks.js diff --git a/.env b/.env index 9e9ffb3..e1ff3b1 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ VITE_PORT = 8080 VITE_PROD = false -VITE_API_URL = "http://81.69.223.133:3000" +VITE_API_URL = "http://81.69.223.133:8008" VITE_SECRETKEY = "ooin-knowledge-base-key" \ No newline at end of file diff --git a/src/components/NotificationCenter.jsx b/src/components/NotificationCenter.jsx index dbf5372..f5b62c6 100644 --- a/src/components/NotificationCenter.jsx +++ b/src/components/NotificationCenter.jsx @@ -6,15 +6,22 @@ import { markNotificationAsRead, setWebSocketConnected, } from '../store/notificationCenter/notificationCenter.slice'; +import { + fetchNotifications, + markNotificationRead, + markAllNotificationsRead, +} from '../store/notificationCenter/notificationCenter.thunks'; import RequestDetailSlideOver from '../pages/Permissions/components/RequestDetailSlideOver'; import { approvePermissionThunk, rejectPermissionThunk } from '../store/permissions/permissions.thunks'; import { showNotification } from '../store/notification.slice'; import { initWebSocket, acknowledgeNotification, closeWebSocket } from '../services/websocket'; +import { formatDate } from '../utils/dateUtils'; +import { useNavigate } from 'react-router-dom'; export default function NotificationCenter({ show, onClose }) { const [showAll, setShowAll] = useState(false); const dispatch = useDispatch(); - const { notifications, unreadCount, isConnected } = useSelector((state) => state.notificationCenter); + const { notifications, unreadCount, isConnected, loading } = useSelector((state) => state.notificationCenter); const [selectedRequest, setSelectedRequest] = useState(null); const [showSlideOver, setShowSlideOver] = useState(false); const [showResponseInput, setShowResponseInput] = useState(false); @@ -22,21 +29,24 @@ export default function NotificationCenter({ show, onClose }) { const [isApproving, setIsApproving] = useState(false); const [responseMessage, setResponseMessage] = useState(''); const { isAuthenticated } = useSelector((state) => state.auth); + const [loadingNotificationId, setLoadingNotificationId] = useState(null); + const [isMarkingAllAsRead, setIsMarkingAllAsRead] = useState(false); + const [isClearingAll, setIsClearingAll] = useState(false); const displayedNotifications = showAll ? notifications : notifications.slice(0, 5); + const navigate = useNavigate(); + // 初始化WebSocket连接 useEffect(() => { - // 只有在用户已登录的情况下才连接WebSocket + // 只有在用户已登录且WebSocket未连接的情况下才连接WebSocket if (isAuthenticated && !isConnected) { initWebSocket() .then(() => { - dispatch(setWebSocketConnected(true)); console.log('Successfully connected to notification WebSocket'); }) .catch((error) => { console.error('Failed to connect to notification WebSocket:', error); - dispatch(setWebSocketConnected(false)); // 可以在这里显示连接失败的通知 dispatch( showNotification({ @@ -51,35 +61,70 @@ export default function NotificationCenter({ show, onClose }) { return () => { if (isConnected) { closeWebSocket(); - dispatch(setWebSocketConnected(false)); } }; }, [isAuthenticated, isConnected, dispatch]); + // 当通知中心显示时,获取最新通知 + useEffect(() => { + if (show && isAuthenticated) { + dispatch(fetchNotifications()); + } + }, [show, isAuthenticated, dispatch]); + const handleClearAll = () => { - dispatch(clearNotifications()); + setIsClearingAll(true); + // 假设这个操作可能需要一点时间 + setTimeout(() => { + dispatch(clearNotifications()); + setIsClearingAll(false); + }, 300); }; const handleMarkAllAsRead = () => { - dispatch(markAllNotificationsAsRead()); + // 设置正在处理标志 + setIsMarkingAllAsRead(true); + + // 通过API将所有通知标记为已读,成功后更新本地状态 + dispatch(markAllNotificationsRead()) + .unwrap() + .then(() => { + dispatch(markAllNotificationsAsRead()); + }) + .catch((error) => { + console.error('Failed to mark all notifications as read:', error); + }) + .finally(() => { + setIsMarkingAllAsRead(false); + }); }; const handleMarkAsRead = (notificationId) => { - dispatch(markNotificationAsRead(notificationId)); - // 同时发送确认消息到服务器 - acknowledgeNotification(notificationId); + // 设置正在加载的通知ID + setLoadingNotificationId(notificationId); + + // 通过API更新已读状态,成功后再更新本地状态 + dispatch(markNotificationRead(notificationId)) + .unwrap() + .then(() => { + // API调用成功后,更新本地状态 + dispatch(markNotificationAsRead(notificationId)); + // 同时发送确认消息到服务器 + acknowledgeNotification(notificationId); + }) + .catch((error) => { + console.error('Failed to mark notification as read:', error); + }) + .finally(() => { + // 清除加载状态 + setLoadingNotificationId(null); + }); }; const handleViewDetail = (notification) => { - // 标记为已读 - if (!notification.isRead) { - handleMarkAsRead(notification.id); - } - - if (notification.type === 'permission') { - setSelectedRequest(notification); - setShowSlideOver(true); - } + navigate('/permissions'); + // setSelectedRequest(notification); + // setShowSlideOver(true); }; const handleCloseSlideOver = () => { @@ -138,24 +183,23 @@ export default function NotificationCenter({ show, onClose }) {
通知中心
{unreadCount > 0 && {unreadCount}} - {isConnected ? ( - 已连接 - ) : ( - 未连接 - )}
-
+
@@ -173,43 +217,46 @@ export default function NotificationCenter({ show, onClose }) {
-
-
- +
+
+
+ {notification.title} +
+ {formatDate(notification.time)}
-
-
-
- {notification.title} -
- {notification.time} -
-

{notification.content}

-
- {notification.hasDetail && ( - - )} - {!notification.isRead && ( - - )} -
+

{notification.content}

+
+ {notification.type === 'permission_request' && ( + + )} + {!notification.is_read && ( + + )}
diff --git a/src/layouts/HeaderWithNav.jsx b/src/layouts/HeaderWithNav.jsx index b4fba0f..81c0959 100644 --- a/src/layouts/HeaderWithNav.jsx +++ b/src/layouts/HeaderWithNav.jsx @@ -1,10 +1,12 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Link, useNavigate, useLocation } from 'react-router-dom'; import { logoutThunk } from '../store/auth/auth.thunk'; import UserSettingsModal from '../components/UserSettingsModal'; import NotificationCenter from '../components/NotificationCenter'; import FullscreenLoading from '../components/FullscreenLoading'; +import SvgIcon from '../components/SvgIcon'; +import { fetchNotifications } from '../store/notificationCenter/notificationCenter.thunks'; export default function HeaderWithNav() { const dispatch = useDispatch(); @@ -23,7 +25,7 @@ export default function HeaderWithNav() { sessionStorage.removeItem('token'); navigate('/login'); } catch (error) { - console.error("Logout failed:", error); + console.error('Logout failed:', error); } finally { setIsLoggingOut(false); } @@ -38,9 +40,13 @@ export default function HeaderWithNav() { // 检查用户是否有管理权限(leader 或 admin) const hasManagePermission = user && (user.role === 'leader' || user.role === 'admin'); + useEffect(() => { + dispatch(fetchNotifications()); + }, [dispatch]); + return ( <> - +