From c6e8f701094ae6af8643d3f1427bbe2f8394a252 Mon Sep 17 00:00:00 2001 From: mtvpls Date: Thu, 18 Dec 2025 23:48:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=9F=AD=E5=89=A7=E6=8E=A8?= =?UTF-8?q?=E8=8D=90=E5=BA=8F=E5=88=97=E5=8C=96=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/UserMenu.tsx | 44 ++++++++++++++++++++++++++++++++----- src/lib/duanju.ts | 2 +- src/lib/upstash.db.ts | 8 +++++-- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/components/UserMenu.tsx b/src/components/UserMenu.tsx index b0af501..802fc81 100644 --- a/src/components/UserMenu.tsx +++ b/src/components/UserMenu.tsx @@ -18,7 +18,7 @@ import { X, } from 'lucide-react'; import { useRouter } from 'next/navigation'; -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { getAuthInfoFromBrowserCookie } from '@/lib/auth'; @@ -139,21 +139,55 @@ export const UserMenu: React.FC = () => { const response = await fetch('/api/notifications'); if (response.ok) { const data = await response.json(); - setUnreadCount(data.unreadCount || 0); + const count = data.unreadCount || 0; + setUnreadCount(count); + // 同步到全局,让其他 UserMenu 实例也能获取 + if (typeof window !== 'undefined') { + (window as any).__unreadNotificationCount = count; + } } } catch (error) { console.error('加载未读通知数量失败:', error); } }; - // 首次加载时检查未读通知数量 + // 首次加载时检查未读通知数量(使用全局标记避免多个实例重复请求) useEffect(() => { - loadUnreadCount(); + if (typeof window === 'undefined') return; + + // 检查是否已经有其他实例在加载 + const globalWindow = window as any; + if (globalWindow.__loadingNotifications) { + // 如果正在加载,等待加载完成后获取结果 + const checkInterval = setInterval(() => { + if (!globalWindow.__loadingNotifications && globalWindow.__unreadNotificationCount !== undefined) { + setUnreadCount(globalWindow.__unreadNotificationCount); + clearInterval(checkInterval); + } + }, 100); + return () => clearInterval(checkInterval); + } + + // 检查是否已经加载过 + if (globalWindow.__unreadNotificationCount !== undefined) { + setUnreadCount(globalWindow.__unreadNotificationCount); + return; + } + + // 标记正在加载 + globalWindow.__loadingNotifications = true; + loadUnreadCount().finally(() => { + globalWindow.__loadingNotifications = false; + }); }, []); // 监听通知更新事件 useEffect(() => { const handleNotificationsUpdated = () => { + // 清除缓存,强制重新加载 + if (typeof window !== 'undefined') { + delete (window as any).__unreadNotificationCount; + } loadUnreadCount(); }; @@ -1455,7 +1489,7 @@ export const UserMenu: React.FC = () => { isOpen={isNotificationPanelOpen} onClose={() => { setIsNotificationPanelOpen(false); - loadUnreadCount(); // 关闭时刷新未读数量 + // 不需要在这里刷新,NotificationPanel 内部会触发事件 }} />, document.body diff --git a/src/lib/duanju.ts b/src/lib/duanju.ts index 9c91de3..095101c 100644 --- a/src/lib/duanju.ts +++ b/src/lib/duanju.ts @@ -25,7 +25,7 @@ export async function getDuanjuSources(): Promise { const cachedData = await db.getGlobalValue('duanju'); if (cachedData !== null) { - // 有缓存,直接返回 + // 有缓存,直接返回(getGlobalValue 已经处理了序列化问题) return cachedData ? JSON.parse(cachedData) : []; } diff --git a/src/lib/upstash.db.ts b/src/lib/upstash.db.ts index a9ab356..a274f43 100644 --- a/src/lib/upstash.db.ts +++ b/src/lib/upstash.db.ts @@ -403,12 +403,16 @@ export class UpstashRedisStorage implements IStorage { const val = await withRetry(() => this.client.get(this.globalValueKey(key)) ); - return val ? ensureString(val) : null; + // Upstash 会自动反序列化 JSON,如果值是对象,需要重新序列化为字符串 + if (val === null) return null; + if (typeof val === 'string') return val; + // 如果是对象(Upstash 自动反序列化的结果),重新序列化 + return JSON.stringify(val); } async setGlobalValue(key: string, value: string): Promise { await withRetry(() => - this.client.set(this.globalValueKey(key), ensureString(value)) + this.client.set(this.globalValueKey(key), value) ); }