From 7db0c6fc2681d705389b1967a514186c0da0b3f6 Mon Sep 17 00:00:00 2001 From: susie-laptop Date: Wed, 16 Apr 2025 09:55:20 -0400 Subject: [PATCH] [dev]notification center --- public/index.html | 12 -- public/vite.svg | 1 - src/components/NotificationCenter.jsx | 164 ++++++++++++------ src/layouts/HeaderWithNav.jsx | 7 +- src/services/mockApi.js | 115 +++++++++++- src/services/websocket.js | 10 +- .../notificationCenter.slice.js | 95 ++++------ .../notificationCenter.thunks.js | 50 ++++++ 8 files changed, 321 insertions(+), 133 deletions(-) delete mode 100644 public/index.html delete mode 100644 public/vite.svg create mode 100644 src/store/notificationCenter/notificationCenter.thunks.js diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 9da09c4..0000000 --- a/public/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - OOIN 智能知识库 - - - -
- - \ No newline at end of file diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index 40d10df..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/NotificationCenter.jsx b/src/components/NotificationCenter.jsx index dbf5372..fb04eff 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,9 +29,14 @@ 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 @@ -56,30 +68,66 @@ export default function NotificationCenter({ show, onClose }) { }; }, [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 +186,23 @@ export default function NotificationCenter({ show, onClose }) {
通知中心
{unreadCount > 0 && {unreadCount}} - {isConnected ? ( - 已连接 - ) : ( - 未连接 - )}
-
+
@@ -173,43 +220,46 @@ export default function NotificationCenter({ show, onClose }) {
-
-
- +
+
+
+ {notification.title} +
+ {formatDate(notification.created_at)}
-
-
-
- {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 1a923d8..2578e59 100644 --- a/src/layouts/HeaderWithNav.jsx +++ b/src/layouts/HeaderWithNav.jsx @@ -1,10 +1,11 @@ -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 SvgIcon from '../components/SvgIcon'; +import { fetchNotifications } from '../store/notificationCenter/notificationCenter.thunks'; export default function HeaderWithNav() { const dispatch = useDispatch(); @@ -32,6 +33,10 @@ export default function HeaderWithNav() { // 检查用户是否有管理权限(leader 或 admin) const hasManagePermission = user && (user.role === 'leader' || user.role === 'admin'); + useEffect(() => { + dispatch(fetchNotifications()); + }, [dispatch]); + return (