修复:通知徽章实时更新状态,读完后立即更新无需刷新
- 新增全局通知store(notificationStore.ts):统一管理未读数量,支持setUnread/decrementUnread/refreshUnread/markAllRead/markOneRead - 修改Sidebar.tsx:从全局store读取未读数量,useNotifyChannel的onUnread回调同步到全局store - 修改Notifications.tsx:标记全部已读和单条已读时调用全局store方法,立即更新未读数量 - 修复TrainingCenter.tsx和Notifications.tsx中的语法错误(缺少右括号) - 所有页面的通知徽章现在都能实时同步,标记已读后立即更新,无需刷新页面
This commit is contained in:
@@ -38,6 +38,7 @@ import { buildSessionPath, getSessionIdFromPath } from "../utils/sessionRoute";
|
||||
import sessionApi from "../pages/Chat/sessionApi";
|
||||
import { useInboxWobble } from "../hooks/useInboxWobble";
|
||||
import { useNotifyChannel } from "../hooks/useNotifyChannel";
|
||||
import { useNotificationStore } from "../stores/notificationStore";
|
||||
import styles from "./index.module.less";
|
||||
import { useTheme } from "../contexts/ThemeContext";
|
||||
import { useMenuItems, useRoutes } from "../plugins/registry/hooks";
|
||||
@@ -335,7 +336,12 @@ export default function Sidebar({ selectedKey }: SidebarProps) {
|
||||
// ── Inbox badge dot & wobble ─────────────────────────────────────────────
|
||||
const hasInboxUnread = hasUnreadMessages || hasPendingApprovals;
|
||||
// 实时业务通知:菜单角标 + 全局 toast(SSE 通道)
|
||||
const { unread: notifyUnread } = useNotifyChannel({ toast: true });
|
||||
useNotifyChannel({
|
||||
toast: true,
|
||||
onUnread: (s) => useNotificationStore.getState().setUnread(s.unread, s.unreadByCategory),
|
||||
});
|
||||
// 从全局 store 读取未读数量,确保标记已读后实时更新
|
||||
const notifyUnread = useNotificationStore((s) => s.unread);
|
||||
const inboxDotColor = hasPendingApprovals
|
||||
? "#e04848"
|
||||
: "rgba(255, 157, 77, 1)";
|
||||
|
||||
@@ -10,6 +10,7 @@ import { RequireRole } from "../../../auth/guards";
|
||||
import { notifyApi, type NotifyItem, type NotifyPayload } from "../../../api/modules/notify";
|
||||
import { friendlyErrorMessage } from "../../../utils/error";
|
||||
import { useNotifyChannel } from "../../../hooks/useNotifyChannel";
|
||||
import { useNotificationStore } from "../../../stores/notificationStore";
|
||||
import styles from "../Opc/OpcList.module.less";
|
||||
|
||||
const CATEGORIES = [
|
||||
@@ -37,8 +38,9 @@ export default function OpcNotifications() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [category, setCategory] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [unread, setUnread] = useState(0);
|
||||
const [unreadByCategory, setUnreadByCategory] = useState<Record<string, number>>({});
|
||||
// 从全局 store 读取未读数量,确保标记已读后实时更新
|
||||
const unread = useNotificationStore((s) => s.unread);
|
||||
const unreadByCategory = useNotificationStore((s) => s.unreadByCategory);
|
||||
const firstLoadRef = useRef(true);
|
||||
|
||||
const load = useCallback(async (cat = category, pg = page) => {
|
||||
@@ -47,10 +49,10 @@ export default function OpcNotifications() {
|
||||
const r = await notifyApi.list({ category: cat, page: pg, pageSize: 15 });
|
||||
setItems(r.items ?? []);
|
||||
setTotal(r.total ?? 0);
|
||||
setUnread(r.unread ?? 0);
|
||||
setUnreadByCategory(r.unread_by_category ?? {});
|
||||
// 同步未读数量到全局 store
|
||||
useNotificationStore.getState().setUnread(r.unread ?? 0, r.unread_by_category ?? {});
|
||||
} catch (e) {
|
||||
message.error(friendlyErrorMessage(e, "加载失败");
|
||||
message.error(friendlyErrorMessage(e, "加载失败"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -81,8 +83,7 @@ export default function OpcNotifications() {
|
||||
refreshUnread();
|
||||
},
|
||||
onUnread: (s) => {
|
||||
setUnread(s.unread);
|
||||
setUnreadByCategory(s.unreadByCategory);
|
||||
useNotificationStore.getState().setUnread(s.unread, s.unreadByCategory);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -101,21 +102,19 @@ export default function OpcNotifications() {
|
||||
|
||||
const markRead = async () => {
|
||||
try {
|
||||
await notifyApi.readAll();
|
||||
await useNotificationStore.getState().markAllRead();
|
||||
message.success(t("role.opc.allRead"));
|
||||
load();
|
||||
refreshUnread();
|
||||
} catch (err) {
|
||||
message.error(friendlyErrorMessage(err, "操作失败");
|
||||
message.error(friendlyErrorMessage(err, "操作失败"));
|
||||
}
|
||||
};
|
||||
|
||||
const markOne = async (it: NotifyItem) => {
|
||||
if (it.read) return;
|
||||
try {
|
||||
await notifyApi.readOne(it.id);
|
||||
await useNotificationStore.getState().markOneRead(it.id);
|
||||
setItems((prev) => prev.map((x) => (x.id === it.id ? { ...x, read: true } : x)));
|
||||
refreshUnread();
|
||||
} catch {
|
||||
/* 静默 */
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function TrainingCenter() {
|
||||
setCourses(coursesRes.items);
|
||||
setCategories(catsRes.items);
|
||||
} catch (e: any) {
|
||||
message.error(friendlyErrorMessage(e, "加载课程失败");
|
||||
message.error(friendlyErrorMessage(e, "加载课程失败"));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// 全局通知 store — 统一管理未读数量,确保各处徽章实时同步
|
||||
import { create } from "zustand";
|
||||
import { notifyApi } from "../api/modules/notify";
|
||||
|
||||
interface NotificationState {
|
||||
unread: number;
|
||||
unreadByCategory: Record<string, number>;
|
||||
lastUpdated: number;
|
||||
setUnread: (total: number, byCat?: Record<string, number>) => void;
|
||||
decrementUnread: (count?: number) => void;
|
||||
refreshUnread: () => Promise<void>;
|
||||
markAllRead: () => Promise<void>;
|
||||
markOneRead: (id: string) => Promise<void>;
|
||||
}
|
||||
|
||||
export const useNotificationStore = create<NotificationState>((set, get) => ({
|
||||
unread: 0,
|
||||
unreadByCategory: {},
|
||||
lastUpdated: 0,
|
||||
|
||||
setUnread: (total, byCat = {}) => {
|
||||
set({ unread: total, unreadByCategory: byCat, lastUpdated: Date.now() });
|
||||
},
|
||||
|
||||
decrementUnread: (count = 1) => {
|
||||
const current = get().unread;
|
||||
set({ unread: Math.max(0, current - count), lastUpdated: Date.now() });
|
||||
},
|
||||
|
||||
refreshUnread: async () => {
|
||||
try {
|
||||
const r = await notifyApi.unreadCount();
|
||||
set({ unread: r.total, unreadByCategory: r.by_category ?? {}, lastUpdated: Date.now() });
|
||||
} catch {
|
||||
/* 静默 */
|
||||
}
|
||||
},
|
||||
|
||||
markAllRead: async () => {
|
||||
try {
|
||||
await notifyApi.readAll();
|
||||
set({ unread: 0, unreadByCategory: {}, lastUpdated: Date.now() });
|
||||
} catch {
|
||||
// 失败时刷新一次
|
||||
get().refreshUnread();
|
||||
}
|
||||
},
|
||||
|
||||
markOneRead: async (id: string) => {
|
||||
try {
|
||||
await notifyApi.readOne(id);
|
||||
get().decrementUnread(1);
|
||||
} catch {
|
||||
// 失败时刷新一次
|
||||
get().refreshUnread();
|
||||
}
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user