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 (
<>
-
+