refactor(message): replace SubscriptionManager with Zustand selectors
Remove custom SubscriptionManager class and adopt Zustand's built-in selector mechanism for reactive state updates. This simplifies the architecture by eliminating manual subscription management and relying on Zustand's automatic dependency tracking for component re-renders. Key changes: - Remove SubscriptionManager class from store.ts - Replace all notifySubscribers calls with direct store updates - Update hooks to use Zustand selectors with useShallow for complex data - Remove subscribe/unsubscribe patterns from MessageManager - Simplify EmbeddedChat to use selector instead of local state - Rename requestConversationListRefresh to refreshConversations
This commit is contained in:
@@ -153,10 +153,7 @@ function prefetchConversations(): void {
|
|||||||
key: 'conversations:list',
|
key: 'conversations:list',
|
||||||
executor: async () => {
|
executor: async () => {
|
||||||
await messageManager.initialize();
|
await messageManager.initialize();
|
||||||
await messageManager.requestConversationListRefresh('prefetch', {
|
await messageManager.refreshConversations(true, 'prefetch');
|
||||||
force: true,
|
|
||||||
allowDefer: true,
|
|
||||||
});
|
|
||||||
return messageManager.getConversations();
|
return messageManager.getConversations();
|
||||||
},
|
},
|
||||||
priority: Priority.HIGH,
|
priority: Priority.HIGH,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
* - useChatScreen.ts: 自定义Hook,管理所有状态和逻辑
|
* - useChatScreen.ts: 自定义Hook,管理所有状态和逻辑
|
||||||
* - EmojiPanel.tsx: 表情面板组件
|
* - EmojiPanel.tsx: 表情面板组件
|
||||||
* - MorePanel.tsx: 更多功能面板组件
|
* - MorePanel.tsx: 更多功能面板组件
|
||||||
* - MentionPanel.tsx: @成员选择面板组件
|
* - MentionPanel.tsx: @成员选择 面板组件
|
||||||
* - LongPressMenu.tsx: 长按菜单组件
|
* - LongPressMenu.tsx: 长按菜单组件
|
||||||
* - ChatHeader.tsx: 聊天头部组件
|
* - ChatHeader.tsx: 聊天头部组件
|
||||||
* - MessageBubble.tsx: 消息气泡组件
|
* - MessageBubble.tsx: 消息气泡组件
|
||||||
@@ -258,10 +258,7 @@ export const ChatScreen: React.FC = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = navigation.addListener('beforeRemove', () => {
|
const unsubscribe = navigation.addListener('beforeRemove', () => {
|
||||||
// 刷新会话列表,确保已读状态正确显示
|
// 刷新会话列表,确保已读状态正确显示
|
||||||
messageManager.requestConversationListRefresh('chat-before-remove', {
|
messageManager.refreshConversations(true, 'chat-before-remove');
|
||||||
force: true,
|
|
||||||
allowDefer: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, [navigation]);
|
}, [navigation]);
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import { Image as ExpoImage } from 'expo-image';
|
|||||||
import { spacing, fontSizes, shadows, useAppColors, type AppColors } from '../../../theme';
|
import { spacing, fontSizes, shadows, useAppColors, type AppColors } from '../../../theme';
|
||||||
import { ConversationResponse, MessageResponse, MessageSegment, GroupMemberResponse, ImageSegmentData } from '../../../types/dto';
|
import { ConversationResponse, MessageResponse, MessageSegment, GroupMemberResponse, ImageSegmentData } from '../../../types/dto';
|
||||||
import { Avatar, Text, ImageGallery, AppBackButton } from '../../../components/common';
|
import { Avatar, Text, ImageGallery, AppBackButton } from '../../../components/common';
|
||||||
import { useAuthStore, messageManager, MessageEvent, MessageSubscriber } from '../../../stores';
|
import { useAuthStore, messageManager, useMessageStore } from '../../../stores';
|
||||||
import { formatDistanceToNow } from 'date-fns';
|
import { formatDistanceToNow } from 'date-fns';
|
||||||
import { zhCN } from 'date-fns/locale';
|
import { zhCN } from 'date-fns/locale';
|
||||||
import { extractTextFromSegments } from '../../../types/dto';
|
import { extractTextFromSegments } from '../../../types/dto';
|
||||||
@@ -302,8 +302,8 @@ export const EmbeddedChat: React.FC<EmbeddedChatProps> = ({ conversation, onBack
|
|||||||
// 使用 markAsRead hook
|
// 使用 markAsRead hook
|
||||||
const { markAsRead } = useMarkAsRead(String(conversation.id));
|
const { markAsRead } = useMarkAsRead(String(conversation.id));
|
||||||
|
|
||||||
// 状态
|
// 状态 - 使用 Zustand selector 直接订阅消息
|
||||||
const [messages, setMessages] = useState<MessageResponse[]>([]);
|
const messages = useMessageStore(state => state.messagesMap.get(String(conversation.id)) || []);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
const [inputText, setInputText] = useState('');
|
const [inputText, setInputText] = useState('');
|
||||||
@@ -490,7 +490,6 @@ export const EmbeddedChat: React.FC<EmbeddedChatProps> = ({ conversation, onBack
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await messageManager.fetchMessages(String(conversation.id));
|
await messageManager.fetchMessages(String(conversation.id));
|
||||||
setMessages(messageManager.getMessages(String(conversation.id)));
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// 检查是否是权限错误或会话不存在错误
|
// 检查是否是权限错误或会话不存在错误
|
||||||
const errorMessage = error?.message || String(error);
|
const errorMessage = error?.message || String(error);
|
||||||
@@ -501,8 +500,6 @@ export const EmbeddedChat: React.FC<EmbeddedChatProps> = ({ conversation, onBack
|
|||||||
error?.code === 'FORBIDDEN'
|
error?.code === 'FORBIDDEN'
|
||||||
) {
|
) {
|
||||||
console.warn('[EmbeddedChat] 会话不存在或无权限访问:', conversation.id);
|
console.warn('[EmbeddedChat] 会话不存在或无权限访问:', conversation.id);
|
||||||
// 设置空消息列表,让 UI 显示"暂无消息"而不是崩溃
|
|
||||||
setMessages([]);
|
|
||||||
} else {
|
} else {
|
||||||
console.error('[EmbeddedChat] 加载消息失败:', error);
|
console.error('[EmbeddedChat] 加载消息失败:', error);
|
||||||
}
|
}
|
||||||
@@ -514,25 +511,6 @@ export const EmbeddedChat: React.FC<EmbeddedChatProps> = ({ conversation, onBack
|
|||||||
// 初始加载
|
// 初始加载
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadMessages();
|
loadMessages();
|
||||||
|
|
||||||
// 订阅消息事件
|
|
||||||
const subscriber: MessageSubscriber = (event: MessageEvent) => {
|
|
||||||
if (
|
|
||||||
event.type === 'messages_updated' &&
|
|
||||||
String(event.payload?.conversationId) === String(conversation.id)
|
|
||||||
) {
|
|
||||||
setMessages(event.payload.messages || []);
|
|
||||||
setTimeout(() => {
|
|
||||||
flatListRef.current?.scrollToEnd({ animated: true });
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const unsubscribe = messageManager.subscribe(subscriber);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unsubscribe();
|
|
||||||
};
|
|
||||||
}, [conversation.id, loadMessages]);
|
}, [conversation.id, loadMessages]);
|
||||||
|
|
||||||
// 自动标记已读 - 当有新消息时
|
// 自动标记已读 - 当有新消息时
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export {
|
|||||||
|
|
||||||
// MessageManager 新架构导出(已替换 conversationStore)
|
// MessageManager 新架构导出(已替换 conversationStore)
|
||||||
// 重构:从 ./message 目录导入,移除了 messageManager.ts 重导出文件
|
// 重构:从 ./message 目录导入,移除了 messageManager.ts 重导出文件
|
||||||
export { messageManager, MessageManager } from './message';
|
export { messageManager, MessageManager, useMessageStore } from './message';
|
||||||
export type {
|
export type {
|
||||||
MessageEvent,
|
MessageEvent,
|
||||||
MessageEventType,
|
MessageEventType,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
* - 移除了 MessageStateManager 包装层
|
* - 移除了 MessageStateManager 包装层
|
||||||
* - 直接使用 zustand store
|
* - 直接使用 zustand store
|
||||||
* - 服务层移至 services/ 目录
|
* - 服务层移至 services/ 目录
|
||||||
|
* - 移除了 SubscriptionManager,改用 Zustand selector 进行响应式订阅
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ConversationResponse, MessageResponse, MessageSegment, UserDTO } from '../../types/dto';
|
import type { ConversationResponse, MessageResponse, MessageSegment, UserDTO } from '../../types/dto';
|
||||||
@@ -24,7 +25,7 @@ import { MessageSyncService } from './services/MessageSyncService';
|
|||||||
import { WSMessageHandler } from './services/WSMessageHandler';
|
import { WSMessageHandler } from './services/WSMessageHandler';
|
||||||
|
|
||||||
// 导入 store
|
// 导入 store
|
||||||
import { useMessageStore, subscriptionManager, normalizeConversationId } from './store';
|
import { useMessageStore, normalizeConversationId } from './store';
|
||||||
|
|
||||||
// 重新导出类型,保持兼容性
|
// 重新导出类型,保持兼容性
|
||||||
export type {
|
export type {
|
||||||
@@ -164,23 +165,12 @@ class MessageManager {
|
|||||||
|
|
||||||
this.wsHandler.setBootstrapping(false);
|
this.wsHandler.setBootstrapping(false);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: this.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (useMessageStore.getState().currentConversationId) {
|
if (useMessageStore.getState().currentConversationId) {
|
||||||
await this.fetchMessages(useMessageStore.getState().currentConversationId!);
|
await this.fetchMessages(useMessageStore.getState().currentConversationId!);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[MessageManager] 初始化失败:', error);
|
console.error('[MessageManager] 初始化失败:', error);
|
||||||
this.wsHandler.setBootstrapping(false);
|
this.wsHandler.setBootstrapping(false);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'error',
|
|
||||||
payload: { error, context: 'initialize' },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -194,7 +184,6 @@ class MessageManager {
|
|||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.wsHandler.disconnect();
|
this.wsHandler.disconnect();
|
||||||
useMessageStore.getState().reset();
|
useMessageStore.getState().reset();
|
||||||
subscriptionManager.clear();
|
|
||||||
this.syncService.restartSources();
|
this.syncService.restartSources();
|
||||||
this.readReceiptManager.reset();
|
this.readReceiptManager.reset();
|
||||||
this.deduplication.reset();
|
this.deduplication.reset();
|
||||||
@@ -339,18 +328,6 @@ class MessageManager {
|
|||||||
await this.initialize();
|
await this.initialize();
|
||||||
this.setActiveConversation(normalizedId);
|
this.setActiveConversation(normalizedId);
|
||||||
|
|
||||||
// 先下发当前内存快照
|
|
||||||
const snapshot = this.getMessages(normalizedId);
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId: normalizedId,
|
|
||||||
messages: [...snapshot],
|
|
||||||
source: 'activation_snapshot',
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const existingTask = this.activatingConversationTasks.get(normalizedId);
|
const existingTask = this.activatingConversationTasks.get(normalizedId);
|
||||||
if (existingTask && !options?.forceSync) {
|
if (existingTask && !options?.forceSync) {
|
||||||
await existingTask;
|
await existingTask;
|
||||||
@@ -371,81 +348,14 @@ class MessageManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 订阅机制 ====================
|
|
||||||
|
|
||||||
subscribe(subscriber: import('./types').MessageSubscriber): () => void {
|
|
||||||
const unsubscribe = subscriptionManager.subscribe(subscriber);
|
|
||||||
|
|
||||||
// 立即发送当前状态给新订阅者(与原始版本保持一致)
|
|
||||||
subscriber({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: this.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const unreadCount = this.getUnreadCount();
|
|
||||||
subscriber({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: unreadCount.total,
|
|
||||||
systemUnreadCount: unreadCount.system,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
subscriber({
|
|
||||||
type: 'connection_changed',
|
|
||||||
payload: { connected: this.isConnected() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 如果当前有活动会话,立即发送该会话的消息给新订阅者
|
|
||||||
const activeConversationId = this.getActiveConversation();
|
|
||||||
if (activeConversationId) {
|
|
||||||
const currentMessages = this.getMessages(activeConversationId);
|
|
||||||
if (currentMessages.length > 0) {
|
|
||||||
subscriber({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId: activeConversationId,
|
|
||||||
messages: [...currentMessages],
|
|
||||||
source: 'initial_sync',
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribeToMessages(conversationId: string, callback: (messages: MessageResponse[]) => void): () => void {
|
|
||||||
return subscriptionManager.subscribe((event) => {
|
|
||||||
if (event.type === 'messages_updated' && event.payload.conversationId === conversationId) {
|
|
||||||
callback(event.payload.messages);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 其他方法 ====================
|
// ==================== 其他方法 ====================
|
||||||
|
|
||||||
canLoadMoreConversations(): boolean {
|
canLoadMoreConversations(): boolean {
|
||||||
return this.syncService.canLoadMoreConversations();
|
return this.syncService.canLoadMoreConversations();
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateCache(_type?: 'all' | 'list' | 'unread'): void {
|
async refreshConversations(forceRefresh: boolean, source: string): Promise<void> {
|
||||||
// 兼容性方法,新架构不需要外部缓存
|
if (this.shouldDeferConversationRefresh(source, forceRefresh)) {
|
||||||
}
|
|
||||||
|
|
||||||
async requestConversationListRefresh(
|
|
||||||
source: string,
|
|
||||||
options?: { force?: boolean; allowDefer?: boolean }
|
|
||||||
): Promise<void> {
|
|
||||||
const forceRefresh = options?.force ?? true;
|
|
||||||
const allowDefer = options?.allowDefer ?? true;
|
|
||||||
|
|
||||||
if (allowDefer && this.shouldDeferConversationRefresh(source, forceRefresh)) {
|
|
||||||
// 延迟刷新逻辑在 syncService 中处理
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,19 @@
|
|||||||
* 消息模块 React Hooks
|
* 消息模块 React Hooks
|
||||||
*
|
*
|
||||||
* 提供React组件与消息状态管理的集成
|
* 提供React组件与消息状态管理的集成
|
||||||
* 所有hooks都基于zustand store和订阅机制
|
* 所有hooks都基于zustand store的selector机制
|
||||||
* 确保组件能实时获取状态更新
|
* 确保组件能实时获取状态更新
|
||||||
*
|
*
|
||||||
* 重构说明:直接使用 zustand store,移除了对 messageManager 的直接依赖
|
* 重构说明:
|
||||||
|
* - 移除了 SubscriptionManager,改用 Zustand selector
|
||||||
|
* - Zustand 会自动处理依赖追踪和组件重渲染
|
||||||
|
* - 不需要手动管理订阅/取消订阅
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto';
|
import { ConversationResponse, MessageResponse, MessageSegment } from '../../types/dto';
|
||||||
import { useMessageStore, subscriptionManager, normalizeConversationId } from './store';
|
import { useMessageStore, normalizeConversationId } from './store';
|
||||||
import type { MessageEvent, MessageSubscriber } from './types';
|
|
||||||
|
|
||||||
// ==================== useConversations - 获取会话列表 ====================
|
// ==================== useConversations - 获取会话列表 ====================
|
||||||
|
|
||||||
@@ -26,6 +29,7 @@ interface UseConversationsReturn {
|
|||||||
/**
|
/**
|
||||||
* 获取会话列表
|
* 获取会话列表
|
||||||
* 用于 MessageListScreen 等需要显示会话列表的组件
|
* 用于 MessageListScreen 等需要显示会话列表的组件
|
||||||
|
* 使用 Zustand selector 自动订阅状态变化
|
||||||
*/
|
*/
|
||||||
export function useConversations(
|
export function useConversations(
|
||||||
fetchConversations: (forceRefresh: boolean, source: string) => Promise<void>,
|
fetchConversations: (forceRefresh: boolean, source: string) => Promise<void>,
|
||||||
@@ -33,28 +37,12 @@ export function useConversations(
|
|||||||
canLoadMore: () => boolean,
|
canLoadMore: () => boolean,
|
||||||
initialize: () => Promise<void>
|
initialize: () => Promise<void>
|
||||||
): UseConversationsReturn {
|
): UseConversationsReturn {
|
||||||
const store = useMessageStore();
|
// 使用 Zustand selector 直接订阅状态
|
||||||
const [conversations, setConversations] = useState<ConversationResponse[]>(() => store.getConversations());
|
const conversations = useMessageStore(state => state.conversationList);
|
||||||
const [isLoading, setIsLoading] = useState(() => store.isLoading());
|
const isLoading = useMessageStore(state => state.isLoadingConversations);
|
||||||
const [hasMore, setHasMore] = useState(() => canLoadMore());
|
const [hasMore, setHasMore] = useState(() => canLoadMore());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 订阅事件
|
|
||||||
const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => {
|
|
||||||
switch (event.type) {
|
|
||||||
case 'conversations_updated':
|
|
||||||
setConversations(store.getConversations());
|
|
||||||
setHasMore(canLoadMore());
|
|
||||||
break;
|
|
||||||
case 'conversations_loading':
|
|
||||||
setIsLoading(!!event.payload?.loading);
|
|
||||||
break;
|
|
||||||
case 'connection_changed':
|
|
||||||
// 连接状态变化时可能需要刷新
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
initialize().catch(error => {
|
initialize().catch(error => {
|
||||||
console.error('[useConversations] 初始化失败:', error);
|
console.error('[useConversations] 初始化失败:', error);
|
||||||
@@ -69,10 +57,14 @@ export function useConversations(
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(coldStartSyncTimer);
|
clearTimeout(coldStartSyncTimer);
|
||||||
unsubscribe();
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 监听 hasMore 变化
|
||||||
|
useEffect(() => {
|
||||||
|
setHasMore(canLoadMore());
|
||||||
|
}, [conversations, canLoadMore]);
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
await fetchConversations(true, 'hooks-manual-refresh');
|
await fetchConversations(true, 'hooks-manual-refresh');
|
||||||
}, [fetchConversations]);
|
}, [fetchConversations]);
|
||||||
@@ -102,30 +94,24 @@ interface UseMessagesReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定会话的消息
|
* 获取指定会话的消息
|
||||||
|
* 使用 Zustand selector 自动订阅消息变化
|
||||||
*/
|
*/
|
||||||
export function useMessages(
|
export function useMessages(
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
fetchMessages: (conversationId: string) => Promise<void>,
|
fetchMessages: (conversationId: string) => Promise<void>,
|
||||||
loadMoreMessages: (conversationId: string, beforeSeq: number, limit?: number) => Promise<MessageResponse[]>
|
loadMoreMessages: (conversationId: string, beforeSeq: number, limit?: number) => Promise<MessageResponse[]>
|
||||||
): UseMessagesReturn {
|
): UseMessagesReturn {
|
||||||
const store = useMessageStore();
|
|
||||||
const normalizedConversationId = normalizeConversationId(conversationId);
|
const normalizedConversationId = normalizeConversationId(conversationId);
|
||||||
|
const store = useMessageStore();
|
||||||
|
|
||||||
const [messages, setMessages] = useState<MessageResponse[]>(() =>
|
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
||||||
store.getMessages(normalizedConversationId)
|
const messages = useMessageStore(
|
||||||
);
|
useShallow(state => state.messagesMap.get(normalizedConversationId) || [])
|
||||||
const [isLoading, setIsLoading] = useState(() =>
|
|
||||||
store.isLoadingMessages(normalizedConversationId)
|
|
||||||
);
|
);
|
||||||
|
const isLoadingMessages = useMessageStore(state => state.loadingMessagesSet.has(normalizedConversationId));
|
||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'messages_updated' && event.payload.conversationId === normalizedConversationId) {
|
|
||||||
setMessages(event.payload.messages);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 激活会话
|
// 激活会话
|
||||||
fetchMessages(normalizedConversationId).catch(error => {
|
fetchMessages(normalizedConversationId).catch(error => {
|
||||||
console.error('[useMessages] 激活会话失败:', error);
|
console.error('[useMessages] 激活会话失败:', error);
|
||||||
@@ -136,12 +122,11 @@ export function useMessages(
|
|||||||
if (store.getActiveConversation() === normalizedConversationId) {
|
if (store.getActiveConversation() === normalizedConversationId) {
|
||||||
store.setCurrentConversation(null);
|
store.setCurrentConversation(null);
|
||||||
}
|
}
|
||||||
unsubscribe();
|
|
||||||
};
|
};
|
||||||
}, [normalizedConversationId]);
|
}, [normalizedConversationId]);
|
||||||
|
|
||||||
const loadMore = useCallback(async () => {
|
const loadMore = useCallback(async () => {
|
||||||
const currentMessages = store.getMessages(conversationId);
|
const currentMessages = messages;
|
||||||
if (currentMessages.length === 0) return;
|
if (currentMessages.length === 0) return;
|
||||||
|
|
||||||
// 消息数组在 store 内保持 seq 升序,首项即最早消息
|
// 消息数组在 store 内保持 seq 升序,首项即最早消息
|
||||||
@@ -150,16 +135,15 @@ export function useMessages(
|
|||||||
|
|
||||||
const loadedMessages = await loadMoreMessages(conversationId, minSeq, 20);
|
const loadedMessages = await loadMoreMessages(conversationId, minSeq, 20);
|
||||||
setHasMore(loadedMessages.length > 0);
|
setHasMore(loadedMessages.length > 0);
|
||||||
}, [conversationId, loadMoreMessages]);
|
}, [conversationId, messages, loadMoreMessages]);
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
await fetchMessages(conversationId);
|
await fetchMessages(conversationId);
|
||||||
setMessages(store.getMessages(conversationId));
|
|
||||||
}, [conversationId, fetchMessages]);
|
}, [conversationId, fetchMessages]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
isLoading,
|
isLoading: isLoadingMessages,
|
||||||
hasMore,
|
hasMore,
|
||||||
loadMore,
|
loadMore,
|
||||||
refresh,
|
refresh,
|
||||||
@@ -240,22 +224,16 @@ interface UseUnreadCountReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取未读数
|
* 获取未读数
|
||||||
|
* 使用 Zustand selector 直接订阅未读数变化
|
||||||
*/
|
*/
|
||||||
export function useUnreadCount(fetchUnreadCount: () => Promise<void>): UseUnreadCountReturn {
|
export function useUnreadCount(fetchUnreadCount: () => Promise<void>): UseUnreadCountReturn {
|
||||||
const store = useMessageStore();
|
// 使用 Zustand selector 直接订阅未读数
|
||||||
const [counts, setCounts] = useState(() => store.getUnreadCount());
|
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
|
||||||
|
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'unread_count_updated') {
|
|
||||||
setCounts(store.getUnreadCount());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始获取
|
// 初始获取
|
||||||
fetchUnreadCount();
|
fetchUnreadCount();
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
@@ -263,8 +241,8 @@ export function useUnreadCount(fetchUnreadCount: () => Promise<void>): UseUnread
|
|||||||
}, [fetchUnreadCount]);
|
}, [fetchUnreadCount]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalUnreadCount: counts.total,
|
totalUnreadCount,
|
||||||
systemUnreadCount: counts.system,
|
systemUnreadCount,
|
||||||
refresh,
|
refresh,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -280,26 +258,15 @@ interface UseSystemUnreadCountReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取系统消息未读数
|
* 获取系统消息未读数
|
||||||
|
* 使用 Zustand selector 直接订阅
|
||||||
*/
|
*/
|
||||||
export function useSystemUnreadCount(
|
export function useSystemUnreadCount(
|
||||||
setSystemUnreadCountService: (count: number) => void,
|
setSystemUnreadCountService: (count: number) => void,
|
||||||
incrementSystemUnreadCountService: () => void,
|
incrementSystemUnreadCountService: () => void,
|
||||||
decrementSystemUnreadCountService: (count?: number) => void
|
decrementSystemUnreadCountService: (count?: number) => void
|
||||||
): UseSystemUnreadCountReturn {
|
): UseSystemUnreadCountReturn {
|
||||||
const store = useMessageStore();
|
// 使用 Zustand selector 直接订阅
|
||||||
const [systemUnreadCount, setSystemUnreadCountState] = useState(() =>
|
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
||||||
store.getUnreadCount().system
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'unread_count_updated') {
|
|
||||||
setSystemUnreadCountState(store.getUnreadCount().system);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const setSystemUnreadCount = useCallback((count: number) => {
|
const setSystemUnreadCount = useCallback((count: number) => {
|
||||||
setSystemUnreadCountService(count);
|
setSystemUnreadCountService(count);
|
||||||
@@ -330,32 +297,19 @@ interface UseConversationReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取单个会话
|
* 获取单个会话
|
||||||
|
* 使用 Zustand selector 直接订阅会话变化
|
||||||
*/
|
*/
|
||||||
export function useConversation(
|
export function useConversation(
|
||||||
conversationId: string,
|
conversationId: string,
|
||||||
fetchConversationDetail: (conversationId: string) => Promise<ConversationResponse | null>
|
fetchConversationDetail: (conversationId: string) => Promise<ConversationResponse | null>
|
||||||
): UseConversationReturn {
|
): UseConversationReturn {
|
||||||
const store = useMessageStore();
|
// 使用 useShallow 避免每次返回新对象引用导致的无限循环
|
||||||
const [conversation, setConversation] = useState<ConversationResponse | null>(() =>
|
const conversation = useMessageStore(
|
||||||
store.getConversation(conversationId)
|
useShallow(state => state.conversations.get(conversationId) || null)
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setConversation(store.getConversation(conversationId));
|
|
||||||
|
|
||||||
const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'conversations_updated') {
|
|
||||||
const updated = store.getConversation(conversationId);
|
|
||||||
setConversation(updated);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, [conversationId]);
|
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
await fetchConversationDetail(conversationId);
|
await fetchConversationDetail(conversationId);
|
||||||
setConversation(store.getConversation(conversationId));
|
|
||||||
}, [conversationId, fetchConversationDetail]);
|
}, [conversationId, fetchConversationDetail]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -364,43 +318,28 @@ export function useConversation(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== useMessageManager - 通用订阅 ====================
|
// ==================== useMessageManager - 通用状态 ====================
|
||||||
|
|
||||||
interface UseMessageManagerReturn {
|
interface UseMessageManagerReturn {
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
subscribe: (subscriber: MessageSubscriber) => () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用消息订阅
|
* 通用消息管理状态
|
||||||
|
* 使用 Zustand selector 直接订阅连接状态
|
||||||
*/
|
*/
|
||||||
export function useMessageManager(initialize: () => Promise<void>): UseMessageManagerReturn {
|
export function useMessageManager(initialize: () => Promise<void>): UseMessageManagerReturn {
|
||||||
const store = useMessageStore();
|
// 使用 Zustand selector 直接订阅连接状态
|
||||||
const [isConnected, setIsConnected] = useState(() => store.isConnected());
|
const isConnected = useMessageStore(state => state.isWSConnected);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => {
|
initialize().catch(error => {
|
||||||
if (event.type === 'connection_changed') {
|
|
||||||
setIsConnected(event.payload.connected);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
initialize().then(() => {
|
|
||||||
setIsConnected(store.isConnected());
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('[useMessageManager] 初始化失败:', error);
|
console.error('[useMessageManager] 初始化失败:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const subscribe = useCallback((subscriber: MessageSubscriber) => {
|
|
||||||
return subscriptionManager.subscribe(subscriber);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isConnected,
|
isConnected,
|
||||||
subscribe,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,25 +406,14 @@ interface UseGroupTypingReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取群聊输入状态
|
* 获取群聊输入状态
|
||||||
|
* 使用 Zustand selector 直接订阅
|
||||||
*/
|
*/
|
||||||
export function useGroupTyping(groupId: string): UseGroupTypingReturn {
|
export function useGroupTyping(groupId: string): UseGroupTypingReturn {
|
||||||
const store = useMessageStore();
|
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
||||||
const [typingUsers, setTypingUsers] = useState<string[]>(() =>
|
const typingUsers = useMessageStore(
|
||||||
store.getTypingUsers(groupId)
|
useShallow(state => state.typingUsersMap.get(groupId) || [])
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setTypingUsers(store.getTypingUsers(groupId));
|
|
||||||
|
|
||||||
const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'typing_status' && event.payload.groupId === groupId) {
|
|
||||||
setTypingUsers(event.payload.typingUsers);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, [groupId]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
typingUsers,
|
typingUsers,
|
||||||
};
|
};
|
||||||
@@ -500,28 +428,16 @@ interface UseGroupMutedReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取群聊禁言状态
|
* 获取群聊禁言状态
|
||||||
|
* 使用 Zustand selector 直接订阅
|
||||||
*/
|
*/
|
||||||
export function useGroupMuted(groupId: string): UseGroupMutedReturn {
|
export function useGroupMuted(groupId: string): UseGroupMutedReturn {
|
||||||
|
// 使用 Zustand selector 直接订阅禁言状态
|
||||||
|
const isMuted = useMessageStore(state => state.mutedStatusMap.get(groupId) || false);
|
||||||
const store = useMessageStore();
|
const store = useMessageStore();
|
||||||
const [isMuted, setIsMuted] = useState(() => store.isMuted(groupId));
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsMuted(store.isMuted(groupId));
|
|
||||||
|
|
||||||
const unsubscribe = subscriptionManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'group_notice' && event.payload.groupId === groupId) {
|
|
||||||
if (event.payload.noticeType === 'muted' || event.payload.noticeType === 'unmuted') {
|
|
||||||
setIsMuted(event.payload.noticeType === 'muted');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, [groupId]);
|
|
||||||
|
|
||||||
const setMutedStatus = useCallback((muted: boolean) => {
|
const setMutedStatus = useCallback((muted: boolean) => {
|
||||||
store.setMutedStatus(groupId, muted);
|
store.setMutedStatus(groupId, muted);
|
||||||
}, [groupId]);
|
}, [groupId, store]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isMuted,
|
isMuted,
|
||||||
@@ -529,29 +445,38 @@ export function useGroupMuted(groupId: string): UseGroupMutedReturn {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== useActiveConversation - 活动会话 ====================
|
// ==================== useConnectionStatus - 连接状态 ====================
|
||||||
|
|
||||||
interface UseActiveConversationReturn {
|
interface UseConnectionStatusReturn {
|
||||||
activeConversationId: string | null;
|
isConnected: boolean;
|
||||||
setActiveConversation: (conversationId: string | null) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取/设置活动会话
|
* 获取连接状态
|
||||||
|
* 使用 Zustand selector 直接订阅
|
||||||
*/
|
*/
|
||||||
export function useActiveConversation(): UseActiveConversationReturn {
|
export function useConnectionStatus(): UseConnectionStatusReturn {
|
||||||
const store = useMessageStore();
|
const isConnected = useMessageStore(state => state.isWSConnected);
|
||||||
const [activeConversationId, setActiveConversationId] = useState<string | null>(() =>
|
|
||||||
store.getActiveConversation()
|
|
||||||
);
|
|
||||||
|
|
||||||
const setActiveConversation = useCallback((conversationId: string | null) => {
|
|
||||||
store.setCurrentConversation(conversationId);
|
|
||||||
setActiveConversationId(conversationId);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
activeConversationId,
|
isConnected,
|
||||||
setActiveConversation,
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== useIsInitialized - 初始化状态 ====================
|
||||||
|
|
||||||
|
interface UseIsInitializedReturn {
|
||||||
|
isInitialized: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取初始化状态
|
||||||
|
* 使用 Zustand selector 直接订阅
|
||||||
|
*/
|
||||||
|
export function useIsInitialized(): UseIsInitializedReturn {
|
||||||
|
const isInitialized = useMessageStore(state => state.isInitialized);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isInitialized,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
* - 直接使用 zustand store 进行状态管理
|
* - 直接使用 zustand store 进行状态管理
|
||||||
* - 服务层移至 services/ 目录
|
* - 服务层移至 services/ 目录
|
||||||
* - 新增 hooks.ts 封装常用逻辑
|
* - 新增 hooks.ts 封装常用逻辑
|
||||||
|
* - 移除了 SubscriptionManager,改用 Zustand selector 进行响应式订阅
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// ==================== 主管理器 ====================
|
// ==================== 主管理器 ====================
|
||||||
@@ -14,11 +15,8 @@ export { messageManager, MessageManager } from './MessageManager';
|
|||||||
// ==================== 状态管理 ====================
|
// ==================== 状态管理 ====================
|
||||||
export {
|
export {
|
||||||
useMessageStore,
|
useMessageStore,
|
||||||
subscriptionManager,
|
|
||||||
normalizeConversationId,
|
normalizeConversationId,
|
||||||
mergeMessagesById,
|
mergeMessagesById,
|
||||||
notifySubscribers,
|
|
||||||
subscribeToMessages,
|
|
||||||
} from './store';
|
} from './store';
|
||||||
export type { MessageState, MessageActions, MessageStore } from './store';
|
export type { MessageState, MessageActions, MessageStore } from './store';
|
||||||
|
|
||||||
@@ -86,7 +84,6 @@ export {
|
|||||||
useConversation,
|
useConversation,
|
||||||
useCreateConversation,
|
useCreateConversation,
|
||||||
useUpdateConversation,
|
useUpdateConversation,
|
||||||
useActiveConversation,
|
|
||||||
|
|
||||||
// 消息相关
|
// 消息相关
|
||||||
useMessages,
|
useMessages,
|
||||||
@@ -103,6 +100,8 @@ export {
|
|||||||
|
|
||||||
// 通用
|
// 通用
|
||||||
useMessageManager,
|
useMessageManager,
|
||||||
|
useConnectionStatus,
|
||||||
|
useIsInitialized,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
|
|
||||||
// ==================== 默认导出 ====================
|
// ==================== 默认导出 ====================
|
||||||
|
|||||||
@@ -2,14 +2,16 @@
|
|||||||
* 会话操作服务
|
* 会话操作服务
|
||||||
* 处理会话的 CRUD 操作
|
* 处理会话的 CRUD 操作
|
||||||
*
|
*
|
||||||
* 重构说明:直接使用 zustand store 替代 IMessageStateManager
|
* 重构说明:
|
||||||
|
* - 直接使用 zustand store 替代 IMessageStateManager
|
||||||
|
* - 移除了 SubscriptionManager 调用,Zustand 的 set() 会自动触发组件重渲染
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ConversationResponse } from '../../../types/dto';
|
import type { ConversationResponse } from '../../../types/dto';
|
||||||
import { messageService } from '../../../services/messageService';
|
import { messageService } from '../../../services/messageService';
|
||||||
import { deleteConversation } from '../../../services/database';
|
import { deleteConversation } from '../../../services/database';
|
||||||
import type { IConversationOperations } from '../types';
|
import type { IConversationOperations } from '../types';
|
||||||
import { useMessageStore, subscriptionManager, normalizeConversationId } from '../store';
|
import { useMessageStore, normalizeConversationId } from '../store';
|
||||||
|
|
||||||
export class ConversationOperations implements IConversationOperations {
|
export class ConversationOperations implements IConversationOperations {
|
||||||
/**
|
/**
|
||||||
@@ -24,21 +26,9 @@ export class ConversationOperations implements IConversationOperations {
|
|||||||
// 添加到会话列表
|
// 添加到会话列表
|
||||||
store.updateConversation(conversation);
|
store.updateConversation(conversation);
|
||||||
|
|
||||||
// 通知更新
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: store.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return conversation;
|
return conversation;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ConversationOperations] 创建会话失败:', error);
|
console.error('[ConversationOperations] 创建会话失败:', error);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'error',
|
|
||||||
payload: { error, context: 'createConversation' },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -60,13 +50,6 @@ export class ConversationOperations implements IConversationOperations {
|
|||||||
};
|
};
|
||||||
|
|
||||||
store.updateConversation(updatedConv);
|
store.updateConversation(updatedConv);
|
||||||
|
|
||||||
// 通知更新
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: store.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,23 +59,6 @@ export class ConversationOperations implements IConversationOperations {
|
|||||||
const store = useMessageStore.getState();
|
const store = useMessageStore.getState();
|
||||||
store.removeConversation(conversationId);
|
store.removeConversation(conversationId);
|
||||||
|
|
||||||
// 通知更新
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: store.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const unreadCount = store.getUnreadCount();
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: unreadCount.total,
|
|
||||||
systemUnreadCount: unreadCount.system,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 删除本地数据库中的会话
|
// 删除本地数据库中的会话
|
||||||
deleteConversation(conversationId).catch(error => {
|
deleteConversation(conversationId).catch(error => {
|
||||||
console.error('[ConversationOperations] 删除本地会话失败:', error);
|
console.error('[ConversationOperations] 删除本地会话失败:', error);
|
||||||
@@ -105,21 +71,6 @@ export class ConversationOperations implements IConversationOperations {
|
|||||||
clearConversations(): void {
|
clearConversations(): void {
|
||||||
const store = useMessageStore.getState();
|
const store = useMessageStore.getState();
|
||||||
store.reset();
|
store.reset();
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: [] },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: 0,
|
|
||||||
systemUnreadCount: 0,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -149,22 +100,6 @@ export class ConversationOperations implements IConversationOperations {
|
|||||||
Math.max(0, currentUnread.total + diff),
|
Math.max(0, currentUnread.total + diff),
|
||||||
currentUnread.system
|
currentUnread.system
|
||||||
);
|
);
|
||||||
|
|
||||||
// 通知更新
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: store.getUnreadCount().total,
|
|
||||||
systemUnreadCount: store.getUnreadCount().system,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: store.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,22 +126,6 @@ export class ConversationOperations implements IConversationOperations {
|
|||||||
currentUnread.total + 1,
|
currentUnread.total + 1,
|
||||||
currentUnread.system
|
currentUnread.system
|
||||||
);
|
);
|
||||||
|
|
||||||
// 通知更新
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: store.getUnreadCount().total,
|
|
||||||
systemUnreadCount: store.getUnreadCount().system,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: store.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -216,15 +135,6 @@ export class ConversationOperations implements IConversationOperations {
|
|||||||
const store = useMessageStore.getState();
|
const store = useMessageStore.getState();
|
||||||
const currentUnread = store.getUnreadCount();
|
const currentUnread = store.getUnreadCount();
|
||||||
store.setUnreadCount(currentUnread.total, count);
|
store.setUnreadCount(currentUnread.total, count);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: currentUnread.total,
|
|
||||||
systemUnreadCount: count,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -234,15 +144,6 @@ export class ConversationOperations implements IConversationOperations {
|
|||||||
const store = useMessageStore.getState();
|
const store = useMessageStore.getState();
|
||||||
const currentUnread = store.getUnreadCount();
|
const currentUnread = store.getUnreadCount();
|
||||||
store.setUnreadCount(currentUnread.total, currentUnread.system + 1);
|
store.setUnreadCount(currentUnread.total, currentUnread.system + 1);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: currentUnread.total,
|
|
||||||
systemUnreadCount: currentUnread.system + 1,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -253,14 +154,5 @@ export class ConversationOperations implements IConversationOperations {
|
|||||||
const currentUnread = store.getUnreadCount();
|
const currentUnread = store.getUnreadCount();
|
||||||
const newSystemCount = Math.max(0, currentUnread.system - count);
|
const newSystemCount = Math.max(0, currentUnread.system - count);
|
||||||
store.setUnreadCount(currentUnread.total, newSystemCount);
|
store.setUnreadCount(currentUnread.total, newSystemCount);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: currentUnread.total,
|
|
||||||
systemUnreadCount: newSystemCount,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,14 +2,16 @@
|
|||||||
* 消息发送服务
|
* 消息发送服务
|
||||||
* 处理消息发送相关逻辑
|
* 处理消息发送相关逻辑
|
||||||
*
|
*
|
||||||
* 重构说明:直接使用 zustand store 替代 IMessageStateManager
|
* 重构说明:
|
||||||
|
* - 直接使用 zustand store 替代 IMessageStateManager
|
||||||
|
* - 移除了 SubscriptionManager 调用,Zustand 的 set() 会自动触发组件重渲染
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { MessageResponse, MessageSegment, ConversationResponse } from '../../../types/dto';
|
import type { MessageResponse, MessageSegment, ConversationResponse } from '../../../types/dto';
|
||||||
import { messageService } from '../../../services/messageService';
|
import { messageService } from '../../../services/messageService';
|
||||||
import { saveMessage } from '../../../services/database';
|
import { saveMessage } from '../../../services/database';
|
||||||
import type { IMessageSendService } from '../types';
|
import type { IMessageSendService } from '../types';
|
||||||
import { useMessageStore, subscriptionManager, mergeMessagesById } from '../store';
|
import { useMessageStore, mergeMessagesById } from '../store';
|
||||||
|
|
||||||
export class MessageSendService implements IMessageSendService {
|
export class MessageSendService implements IMessageSendService {
|
||||||
private getCurrentUserId: () => string | null;
|
private getCurrentUserId: () => string | null;
|
||||||
@@ -53,28 +55,9 @@ export class MessageSendService implements IMessageSendService {
|
|||||||
const updatedMessages = mergeMessagesById(existingMessages, [newMessage]);
|
const updatedMessages = mergeMessagesById(existingMessages, [newMessage]);
|
||||||
store.setMessages(conversationId, updatedMessages);
|
store.setMessages(conversationId, updatedMessages);
|
||||||
|
|
||||||
// 关键:立即广播消息列表更新
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId,
|
|
||||||
messages: updatedMessages,
|
|
||||||
newMessage,
|
|
||||||
source: 'send',
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 更新会话最后消息
|
// 更新会话最后消息
|
||||||
this.updateConversationWithNewMessage(conversationId, newMessage);
|
this.updateConversationWithNewMessage(conversationId, newMessage);
|
||||||
|
|
||||||
// 通知消息已发送
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'message_sent',
|
|
||||||
payload: { conversationId, message: newMessage },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 保存到本地数据库
|
// 保存到本地数据库
|
||||||
const textContent = segments
|
const textContent = segments
|
||||||
?.filter((s: any) => s.type === 'text')
|
?.filter((s: any) => s.type === 'text')
|
||||||
@@ -100,11 +83,6 @@ export class MessageSendService implements IMessageSendService {
|
|||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[MessageSendService] 发送消息失败:', error);
|
console.error('[MessageSendService] 发送消息失败:', error);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'error',
|
|
||||||
payload: { error, context: 'sendMessage' },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
* 消息同步服务
|
* 消息同步服务
|
||||||
* 处理消息和会话的服务器同步逻辑
|
* 处理消息和会话的服务器同步逻辑
|
||||||
*
|
*
|
||||||
* 重构说明:直接使用 zustand store 替代 IMessageStateManager
|
* 重构说明:
|
||||||
|
* - 直接使用 zustand store 替代 IMessageStateManager
|
||||||
|
* - 移除了 SubscriptionManager 调用,Zustand 的 set() 会自动触发组件重渲染
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ConversationResponse, MessageResponse } from '../../../types/dto';
|
import type { ConversationResponse, MessageResponse } from '../../../types/dto';
|
||||||
@@ -21,7 +23,7 @@ import {
|
|||||||
CONVERSATION_LIST_PAGE_SIZE,
|
CONVERSATION_LIST_PAGE_SIZE,
|
||||||
} from '../../conversationListSources';
|
} from '../../conversationListSources';
|
||||||
import type { IMessageSyncService, MessageManagerConversationListDeps, IUserCacheService } from '../types';
|
import type { IMessageSyncService, MessageManagerConversationListDeps, IUserCacheService } from '../types';
|
||||||
import { useMessageStore, subscriptionManager, normalizeConversationId, mergeMessagesById } from '../store';
|
import { useMessageStore, normalizeConversationId, mergeMessagesById } from '../store';
|
||||||
import { ReadReceiptManager } from './ReadReceiptManager';
|
import { ReadReceiptManager } from './ReadReceiptManager';
|
||||||
|
|
||||||
export class MessageSyncService implements IMessageSyncService {
|
export class MessageSyncService implements IMessageSyncService {
|
||||||
@@ -94,19 +96,11 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
if (!forceRefresh && currentState.conversations.size === 0) {
|
if (!forceRefresh && currentState.conversations.size === 0) {
|
||||||
const warmed = await this.hydrateConversationsFromLocalSource();
|
const warmed = await this.hydrateConversationsFromLocalSource();
|
||||||
if (warmed) {
|
if (warmed) {
|
||||||
this.emitConversationListAndUnreadUpdates();
|
this.persistConversationListCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
store.setLoading(true);
|
store.setLoading(true);
|
||||||
const emitLoadingToUi = store.getConversations().length === 0;
|
|
||||||
if (emitLoadingToUi) {
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_loading',
|
|
||||||
payload: { loading: true },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.remoteConversationListSource.restart();
|
this.remoteConversationListSource.restart();
|
||||||
@@ -129,29 +123,17 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
);
|
);
|
||||||
store.setUnreadCount(totalUnread, store.getUnreadCount().system);
|
store.setUnreadCount(totalUnread, store.getUnreadCount().system);
|
||||||
|
|
||||||
this.emitConversationListAndUnreadUpdates();
|
this.persistConversationListCache();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[MessageSyncService] 获取会话列表失败:', error);
|
console.error('[MessageSyncService] 获取会话列表失败:', error);
|
||||||
if (store.getConversations().length === 0) {
|
if (store.getConversations().length === 0) {
|
||||||
const recovered = await this.hydrateConversationsFromLocalSource();
|
const recovered = await this.hydrateConversationsFromLocalSource();
|
||||||
if (recovered) {
|
if (recovered) {
|
||||||
this.emitConversationListAndUnreadUpdates();
|
this.persistConversationListCache();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'error',
|
|
||||||
payload: { error, context: 'fetchConversations' },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
store.setLoading(false);
|
store.setLoading(false);
|
||||||
if (emitLoadingToUi) {
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_loading',
|
|
||||||
payload: { loading: false },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,14 +160,9 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.recomputeConversationTotalUnread();
|
this.recomputeConversationTotalUnread();
|
||||||
this.emitConversationListAndUnreadUpdates();
|
this.persistConversationListCache();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[MessageSyncService] 加载更多会话失败:', error);
|
console.error('[MessageSyncService] 加载更多会话失败:', error);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'error',
|
|
||||||
payload: { error, context: 'loadMoreConversations' },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
this.loadingMoreConversations = false;
|
this.loadingMoreConversations = false;
|
||||||
}
|
}
|
||||||
@@ -252,27 +229,9 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
store.setMessages(conversationId, formattedMessages);
|
store.setMessages(conversationId, formattedMessages);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId,
|
|
||||||
messages: formattedMessages,
|
|
||||||
source: 'local',
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
// 冷启动兜底:先下发空列表事件
|
// 冷启动兜底:先设置空列表
|
||||||
store.setMessages(conversationId, []);
|
store.setMessages(conversationId, []);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId,
|
|
||||||
messages: [],
|
|
||||||
source: 'local_empty',
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('[MessageSyncService] 读取本地消息失败:', error);
|
console.warn('[MessageSyncService] 读取本地消息失败:', error);
|
||||||
@@ -289,17 +248,6 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
const mergedSnapshot = mergeMessagesById(existingMessages, snapshotMessages);
|
const mergedSnapshot = mergeMessagesById(existingMessages, snapshotMessages);
|
||||||
store.setMessages(conversationId, mergedSnapshot);
|
store.setMessages(conversationId, mergedSnapshot);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId,
|
|
||||||
messages: mergedSnapshot,
|
|
||||||
newMessages: snapshotMessages,
|
|
||||||
source: 'server_snapshot',
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 持久化到本地
|
// 持久化到本地
|
||||||
saveMessagesBatch(snapshotMessages.map((m: any) => ({
|
saveMessagesBatch(snapshotMessages.map((m: any) => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
@@ -328,17 +276,6 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
const mergedMessages = mergeMessagesById(existingMessages, newMessages);
|
const mergedMessages = mergeMessagesById(existingMessages, newMessages);
|
||||||
store.setMessages(conversationId, mergedMessages);
|
store.setMessages(conversationId, mergedMessages);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId,
|
|
||||||
messages: mergedMessages,
|
|
||||||
newMessages,
|
|
||||||
source: 'server_incremental',
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
saveMessagesBatch(newMessages.map((m: any) => ({
|
saveMessagesBatch(newMessages.map((m: any) => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
conversationId: m.conversation_id || conversationId,
|
conversationId: m.conversation_id || conversationId,
|
||||||
@@ -368,16 +305,6 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
const mergedMessages = mergeMessagesById(existingMessages, newMessages);
|
const mergedMessages = mergeMessagesById(existingMessages, newMessages);
|
||||||
|
|
||||||
store.setMessages(conversationId, mergedMessages);
|
store.setMessages(conversationId, mergedMessages);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId,
|
|
||||||
messages: mergedMessages,
|
|
||||||
newMessages,
|
|
||||||
source: 'server',
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
saveMessagesBatch(newMessages.map((m: any) => ({
|
saveMessagesBatch(newMessages.map((m: any) => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
@@ -397,11 +324,6 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[MessageSyncService] 获取消息失败:', error);
|
console.error('[MessageSyncService] 获取消息失败:', error);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'error',
|
|
||||||
payload: { error, context: 'fetchMessages', conversationId },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
// 异步填充用户信息(不阻塞消息显示)
|
// 异步填充用户信息(不阻塞消息显示)
|
||||||
const currentMessages = store.getMessages(conversationId);
|
const currentMessages = store.getMessages(conversationId);
|
||||||
@@ -411,15 +333,6 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
currentMessages,
|
currentMessages,
|
||||||
(convId, enrichedMessages) => {
|
(convId, enrichedMessages) => {
|
||||||
store.setMessages(convId, enrichedMessages);
|
store.setMessages(convId, enrichedMessages);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId: convId,
|
|
||||||
messages: enrichedMessages,
|
|
||||||
source: 'sender_enriched',
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -452,12 +365,6 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
const mergedMessages = this.mergeOlderMessages(existingMessages, formattedMessages);
|
const mergedMessages = this.mergeOlderMessages(existingMessages, formattedMessages);
|
||||||
store.setMessages(conversationId, mergedMessages);
|
store.setMessages(conversationId, mergedMessages);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: { conversationId, messages: mergedMessages, source: 'local_history' },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return formattedMessages;
|
return formattedMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,12 +391,6 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
const mergedMessages = this.mergeOlderMessages(existingMessages, serverMessages);
|
const mergedMessages = this.mergeOlderMessages(existingMessages, serverMessages);
|
||||||
store.setMessages(conversationId, mergedMessages);
|
store.setMessages(conversationId, mergedMessages);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: { conversationId, messages: mergedMessages, source: 'server_history' },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return serverMessages;
|
return serverMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,22 +433,8 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
// 使用 zustand set 函数正确更新状态
|
// 使用 zustand set 函数正确更新状态
|
||||||
store.setConversations(newConversations);
|
store.setConversations(newConversations);
|
||||||
this.persistConversationListCache();
|
this.persistConversationListCache();
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: store.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: totalUnread,
|
|
||||||
systemUnreadCount: systemUnread,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[MessageSyncService] 获取未读数失败:', error);
|
console.error('[MessageSyncService] 获取未读数失败:', error);
|
||||||
}
|
}
|
||||||
@@ -598,26 +485,6 @@ export class MessageSyncService implements IMessageSyncService {
|
|||||||
store.setUnreadCount(totalUnread, store.getUnreadCount().system);
|
store.setUnreadCount(totalUnread, store.getUnreadCount().system);
|
||||||
}
|
}
|
||||||
|
|
||||||
private emitConversationListAndUnreadUpdates(): void {
|
|
||||||
const store = useMessageStore.getState();
|
|
||||||
this.persistConversationListCache();
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: store.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const unreadCount = store.getUnreadCount();
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: unreadCount.total,
|
|
||||||
systemUnreadCount: unreadCount.system,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private persistConversationListCache(): void {
|
private persistConversationListCache(): void {
|
||||||
const store = useMessageStore.getState();
|
const store = useMessageStore.getState();
|
||||||
const currentState = store.getState();
|
const currentState = store.getState();
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
* 已读回执管理器
|
* 已读回执管理器
|
||||||
* 管理消息已读状态的同步和保护
|
* 管理消息已读状态的同步和保护
|
||||||
*
|
*
|
||||||
* 重构说明:直接使用 zustand store 替代 IMessageStateManager
|
* 重构说明:
|
||||||
|
* - 直接使用 zustand store 替代 IMessageStateManager
|
||||||
|
* - 移除了 SubscriptionManager 调用,Zustand 的 set() 会自动触发组件重渲染
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { ConversationResponse } from '../../../types/dto';
|
import type { ConversationResponse } from '../../../types/dto';
|
||||||
@@ -10,7 +12,7 @@ import { messageService } from '../../../services/messageService';
|
|||||||
import { markConversationAsRead, updateConversationCacheUnreadCount } from '../../../services/database';
|
import { markConversationAsRead, updateConversationCacheUnreadCount } from '../../../services/database';
|
||||||
import type { ReadStateRecord, IReadReceiptManager } from '../types';
|
import type { ReadStateRecord, IReadReceiptManager } from '../types';
|
||||||
import { READ_STATE_PROTECTION_DELAY } from '../constants';
|
import { READ_STATE_PROTECTION_DELAY } from '../constants';
|
||||||
import { useMessageStore, subscriptionManager, normalizeConversationId } from '../store';
|
import { useMessageStore, normalizeConversationId } from '../store';
|
||||||
|
|
||||||
export class ReadReceiptManager implements IReadReceiptManager {
|
export class ReadReceiptManager implements IReadReceiptManager {
|
||||||
/**
|
/**
|
||||||
@@ -78,27 +80,7 @@ export class ReadReceiptManager implements IReadReceiptManager {
|
|||||||
// 3. 更新本地数据库
|
// 3. 更新本地数据库
|
||||||
markConversationAsRead(normalizedId).catch(console.error);
|
markConversationAsRead(normalizedId).catch(console.error);
|
||||||
|
|
||||||
// 4. 立即通知所有订阅者
|
// 4. 调用 API,完成后设置延迟清除保护
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'message_read',
|
|
||||||
payload: {
|
|
||||||
conversationId: normalizedId,
|
|
||||||
unreadCount: 0,
|
|
||||||
totalUnreadCount: newTotalUnread,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: newTotalUnread,
|
|
||||||
systemUnreadCount: currentUnread.system,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 5. 调用 API,完成后设置延迟清除保护
|
|
||||||
try {
|
try {
|
||||||
await messageService.markAsRead(normalizedId, seq);
|
await messageService.markAsRead(normalizedId, seq);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -108,16 +90,6 @@ export class ReadReceiptManager implements IReadReceiptManager {
|
|||||||
store.updateConversation(conversation);
|
store.updateConversation(conversation);
|
||||||
store.setUnreadCount(currentUnread.total, currentUnread.system);
|
store.setUnreadCount(currentUnread.total, currentUnread.system);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'message_read',
|
|
||||||
payload: {
|
|
||||||
conversationId: normalizedId,
|
|
||||||
unreadCount: prevUnreadCount,
|
|
||||||
totalUnreadCount: currentUnread.total,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// API 失败时立即清除保护
|
// API 失败时立即清除保护
|
||||||
this.pendingReadMap.delete(normalizedId);
|
this.pendingReadMap.delete(normalizedId);
|
||||||
return;
|
return;
|
||||||
@@ -156,15 +128,6 @@ export class ReadReceiptManager implements IReadReceiptManager {
|
|||||||
|
|
||||||
store.setUnreadCount(0, currentSystem);
|
store.setUnreadCount(0, currentSystem);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: 0,
|
|
||||||
systemUnreadCount: currentSystem,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 标记系统消息已读
|
// 标记系统消息已读
|
||||||
await messageService.markAllSystemMessagesRead();
|
await messageService.markAllSystemMessagesRead();
|
||||||
@@ -182,15 +145,6 @@ export class ReadReceiptManager implements IReadReceiptManager {
|
|||||||
console.error('[ReadReceiptManager] 标记所有已读失败:', error);
|
console.error('[ReadReceiptManager] 标记所有已读失败:', error);
|
||||||
// 回滚
|
// 回滚
|
||||||
store.setUnreadCount(prevTotalUnread, currentSystem);
|
store.setUnreadCount(prevTotalUnread, currentSystem);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: prevTotalUnread,
|
|
||||||
systemUnreadCount: currentSystem,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
* WebSocket 消息处理器
|
* WebSocket 消息处理器
|
||||||
* 处理所有来自 WebSocket 的消息事件
|
* 处理所有来自 WebSocket 的消息事件
|
||||||
*
|
*
|
||||||
* 重构说明:直接使用 zustand store 替代 IMessageStateManager
|
* 重构说明:
|
||||||
|
* - 直接使用 zustand store 替代 IMessageStateManager
|
||||||
|
* - 移除了 SubscriptionManager 调用,Zustand 的 set() 会自动触发组件重渲染
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@@ -26,7 +28,7 @@ import type {
|
|||||||
HandleNewMessageOptions,
|
HandleNewMessageOptions,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
import { MAX_FLUSH_ITERATIONS } from '../constants';
|
import { MAX_FLUSH_ITERATIONS } from '../constants';
|
||||||
import { useMessageStore, subscriptionManager, normalizeConversationId, mergeMessagesById } from '../store';
|
import { useMessageStore, normalizeConversationId, mergeMessagesById } from '../store';
|
||||||
|
|
||||||
export class WSMessageHandler implements IWSMessageHandler {
|
export class WSMessageHandler implements IWSMessageHandler {
|
||||||
private deduplication: IMessageDeduplication;
|
private deduplication: IMessageDeduplication;
|
||||||
@@ -134,11 +136,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
// 监听连接状态
|
// 监听连接状态
|
||||||
wsService.onConnect(() => {
|
wsService.onConnect(() => {
|
||||||
store.setSSEConnected(true);
|
store.setSSEConnected(true);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'connection_changed',
|
|
||||||
payload: { connected: true },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
// 冷启动/重连兜底
|
// 冷启动/重连兜底
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -166,11 +163,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
|
|
||||||
wsService.onDisconnect(() => {
|
wsService.onDisconnect(() => {
|
||||||
store.setSSEConnected(false);
|
store.setSSEConnected(false);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'connection_changed',
|
|
||||||
payload: { connected: false },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,14 +260,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
m.id === id ? { ...m, sender: user } : m
|
m.id === id ? { ...m, sender: user } : m
|
||||||
);
|
);
|
||||||
store.setMessages(normalizedConversationId, updatedMessages);
|
store.setMessages(normalizedConversationId, updatedMessages);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId: normalizedConversationId,
|
|
||||||
messages: [...updatedMessages],
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
@@ -301,16 +285,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
if (!messageExists) {
|
if (!messageExists) {
|
||||||
const updatedMessages = mergeMessagesById(existingMessages, [newMessage]);
|
const updatedMessages = mergeMessagesById(existingMessages, [newMessage]);
|
||||||
store.setMessages(normalizedConversationId, updatedMessages);
|
store.setMessages(normalizedConversationId, updatedMessages);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId: normalizedConversationId,
|
|
||||||
messages: updatedMessages,
|
|
||||||
newMessage,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新会话信息
|
// 更新会话信息
|
||||||
@@ -358,17 +332,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error('[WSMessageHandler] 保存消息到本地失败:', error);
|
console.error('[WSMessageHandler] 保存消息到本地失败:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 通知收到新消息
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'message_received',
|
|
||||||
payload: {
|
|
||||||
conversationId: conversation_id,
|
|
||||||
message: newMessage,
|
|
||||||
isCurrentUser: isCurrentUserMessage,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -395,12 +358,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
this.markMessageAsRecalled(normalizedConversationId, message_id);
|
this.markMessageAsRecalled(normalizedConversationId, message_id);
|
||||||
this.syncConversationLastMessageOnRecall(normalizedConversationId, message_id);
|
this.syncConversationLastMessageOnRecall(normalizedConversationId, message_id);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'message_recalled',
|
|
||||||
payload: { conversationId: normalizedConversationId, messageId: message_id },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
updateMessageStatus(message_id, 'recalled', true).catch(error => {
|
updateMessageStatus(message_id, 'recalled', true).catch(error => {
|
||||||
console.error('[WSMessageHandler] 更新本地消息撤回状态失败:', error);
|
console.error('[WSMessageHandler] 更新本地消息撤回状态失败:', error);
|
||||||
});
|
});
|
||||||
@@ -416,12 +373,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
this.markMessageAsRecalled(normalizedConversationId, message_id);
|
this.markMessageAsRecalled(normalizedConversationId, message_id);
|
||||||
this.syncConversationLastMessageOnRecall(normalizedConversationId, message_id);
|
this.syncConversationLastMessageOnRecall(normalizedConversationId, message_id);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'message_recalled',
|
|
||||||
payload: { conversationId: normalizedConversationId, messageId: message_id, isGroup: true },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
updateMessageStatus(message_id, 'recalled', true).catch(error => {
|
updateMessageStatus(message_id, 'recalled', true).catch(error => {
|
||||||
console.error('[WSMessageHandler] 更新本地消息撤回状态失败:', error);
|
console.error('[WSMessageHandler] 更新本地消息撤回状态失败:', error);
|
||||||
});
|
});
|
||||||
@@ -450,11 +401,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
|
|
||||||
if (updatedTypingUsers.length !== currentTypingUsers.length) {
|
if (updatedTypingUsers.length !== currentTypingUsers.length) {
|
||||||
store.setTypingUsers(groupIdStr, updatedTypingUsers);
|
store.setTypingUsers(groupIdStr, updatedTypingUsers);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'typing_status',
|
|
||||||
payload: { groupId: groupIdStr, typingUsers: updatedTypingUsers },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,34 +446,9 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
|
|
||||||
const updatedMessages = [...existingMessages, systemNoticeMessage].sort((a, b) => a.seq - b.seq);
|
const updatedMessages = [...existingMessages, systemNoticeMessage].sort((a, b) => a.seq - b.seq);
|
||||||
store.setMessages(conversationId, updatedMessages);
|
store.setMessages(conversationId, updatedMessages);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: {
|
|
||||||
conversationId,
|
|
||||||
messages: updatedMessages,
|
|
||||||
newMessage: systemNoticeMessage,
|
|
||||||
source: 'group_notice',
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 通知订阅者群通知
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'group_notice',
|
|
||||||
payload: {
|
|
||||||
groupId: groupIdStr,
|
|
||||||
noticeType: notice_type,
|
|
||||||
data,
|
|
||||||
timestamp,
|
|
||||||
messageId: message_id,
|
|
||||||
seq,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 私有工具方法 ====================
|
// ==================== 私有工具方法 ====================
|
||||||
@@ -550,12 +471,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
};
|
};
|
||||||
store.updateConversation(updatedConv);
|
store.updateConversation(updatedConv);
|
||||||
}
|
}
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: store.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private incrementUnreadCount(conversationId: string): void {
|
private incrementUnreadCount(conversationId: string): void {
|
||||||
@@ -571,21 +486,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
|
|
||||||
const currentUnread = store.getUnreadCount();
|
const currentUnread = store.getUnreadCount();
|
||||||
store.setUnreadCount(currentUnread.total + 1, currentUnread.system);
|
store.setUnreadCount(currentUnread.total + 1, currentUnread.system);
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'unread_count_updated',
|
|
||||||
payload: {
|
|
||||||
totalUnreadCount: store.getUnreadCount().total,
|
|
||||||
systemUnreadCount: store.getUnreadCount().system,
|
|
||||||
},
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
|
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'conversations_updated',
|
|
||||||
payload: { conversations: store.getConversations() },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,11 +510,6 @@ export class WSMessageHandler implements IWSMessageHandler {
|
|||||||
if (!changed) return;
|
if (!changed) return;
|
||||||
|
|
||||||
store.setMessages(conversationId, updatedMessages);
|
store.setMessages(conversationId, updatedMessages);
|
||||||
subscriptionManager.notifySubscribers({
|
|
||||||
type: 'messages_updated',
|
|
||||||
payload: { conversationId, messages: updatedMessages },
|
|
||||||
timestamp: Date.now(),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private syncConversationLastMessageOnRecall(conversationId: string, messageId: string): void {
|
private syncConversationLastMessageOnRecall(conversationId: string, messageId: string): void {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
* 重构说明:
|
* 重构说明:
|
||||||
* - 移除了 MessageStateManager 包装层
|
* - 移除了 MessageStateManager 包装层
|
||||||
* - 直接使用 zustand store 进行状态管理
|
* - 直接使用 zustand store 进行状态管理
|
||||||
* - 订阅管理器集成到 store 中
|
* - 移除了 SubscriptionManager,改用 Zustand selector 进行响应式订阅
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
@@ -13,10 +13,6 @@ import type {
|
|||||||
ConversationResponse,
|
ConversationResponse,
|
||||||
MessageResponse,
|
MessageResponse,
|
||||||
} from '../../types/dto';
|
} from '../../types/dto';
|
||||||
import type {
|
|
||||||
MessageSubscriber,
|
|
||||||
MessageEvent,
|
|
||||||
} from './types';
|
|
||||||
|
|
||||||
// ==================== 状态接口 ====================
|
// ==================== 状态接口 ====================
|
||||||
|
|
||||||
@@ -377,52 +373,3 @@ export const useMessageStore = create<MessageStore>((set, get) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// ==================== 订阅管理器 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 订阅管理器类
|
|
||||||
* 管理消息事件的订阅和通知
|
|
||||||
*/
|
|
||||||
export class SubscriptionManager {
|
|
||||||
private subscribers: Set<MessageSubscriber> = new Set();
|
|
||||||
|
|
||||||
subscribe(subscriber: MessageSubscriber): () => void {
|
|
||||||
this.subscribers.add(subscriber);
|
|
||||||
return () => {
|
|
||||||
this.subscribers.delete(subscriber);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
notifySubscribers(event: MessageEvent): void {
|
|
||||||
this.subscribers.forEach(subscriber => {
|
|
||||||
try {
|
|
||||||
subscriber(event);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[SubscriptionManager] 订阅者执行失败:', error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(): void {
|
|
||||||
this.subscribers.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建全局订阅管理器实例
|
|
||||||
export const subscriptionManager = new SubscriptionManager();
|
|
||||||
|
|
||||||
// ==================== 便捷方法 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 通知所有订阅者
|
|
||||||
*/
|
|
||||||
export function notifySubscribers(event: MessageEvent): void {
|
|
||||||
subscriptionManager.notifySubscribers(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 订阅消息事件
|
|
||||||
*/
|
|
||||||
export function subscribeToMessages(subscriber: MessageSubscriber): () => void {
|
|
||||||
return subscriptionManager.subscribe(subscriber);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,13 +2,20 @@
|
|||||||
* MessageManager React Hooks
|
* MessageManager React Hooks
|
||||||
*
|
*
|
||||||
* 提供React组件与MessageManager的集成
|
* 提供React组件与MessageManager的集成
|
||||||
* 所有hooks都基于MessageManager的订阅机制
|
* 所有hooks都基于Zustand selector机制
|
||||||
* 确保组件能实时获取状态更新
|
* 确保组件能实时获取状态更新
|
||||||
|
*
|
||||||
|
* 重构说明:
|
||||||
|
* - 移除了手动订阅机制,改用 Zustand selector
|
||||||
|
* - Zustand 会自动处理依赖追踪和组件重渲染
|
||||||
|
* - 不需要手动管理订阅/取消订阅
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||||
|
import { useShallow } from 'zustand/react/shallow';
|
||||||
import { ConversationResponse, MessageResponse, MessageSegment } from '../types/dto';
|
import { ConversationResponse, MessageResponse, MessageSegment } from '../types/dto';
|
||||||
import { messageManager, MessageEvent } from './messageManager';
|
import { messageManager } from './messageManager';
|
||||||
|
import { useMessageStore } from './message';
|
||||||
|
|
||||||
// ==================== useConversations - 获取会话列表 ====================
|
// ==================== useConversations - 获取会话列表 ====================
|
||||||
|
|
||||||
@@ -21,33 +28,16 @@ interface UseConversationsReturn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取会话列表(仅消费 MessageManager;网络游标与 SQLite 回退均在 Manager 内完成,调用方无感)
|
* 获取会话列表
|
||||||
* 用于 MessageListScreen 等需要显示会话列表的组件
|
* 使用 Zustand selector 自动订阅状态变化
|
||||||
*/
|
*/
|
||||||
export function useConversations(): UseConversationsReturn {
|
export function useConversations(): UseConversationsReturn {
|
||||||
const [conversations, setConversations] = useState<ConversationResponse[]>(
|
// 使用 Zustand selector 直接订阅状态
|
||||||
() => messageManager.getConversations()
|
const conversations = useMessageStore(state => state.conversationList);
|
||||||
);
|
const isLoading = useMessageStore(state => state.isLoadingConversations);
|
||||||
const [isLoading, setIsLoading] = useState(() => messageManager.isLoading());
|
|
||||||
const [hasMore, setHasMore] = useState(() => messageManager.canLoadMoreConversations());
|
const [hasMore, setHasMore] = useState(() => messageManager.canLoadMoreConversations());
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 订阅MessageManager的事件
|
|
||||||
const unsubscribe = messageManager.subscribe((event: MessageEvent) => {
|
|
||||||
switch (event.type) {
|
|
||||||
case 'conversations_updated':
|
|
||||||
setConversations(messageManager.getConversations());
|
|
||||||
setHasMore(messageManager.canLoadMoreConversations());
|
|
||||||
break;
|
|
||||||
case 'conversations_loading':
|
|
||||||
setIsLoading(!!event.payload?.loading);
|
|
||||||
break;
|
|
||||||
case 'connection_changed':
|
|
||||||
// 连接状态变化时可能需要刷新
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化时确保 MessageManager 已初始化
|
// 初始化时确保 MessageManager 已初始化
|
||||||
messageManager.initialize().catch(error => {
|
messageManager.initialize().catch(error => {
|
||||||
console.error('[useConversations] 初始化失败:', error);
|
console.error('[useConversations] 初始化失败:', error);
|
||||||
@@ -55,25 +45,23 @@ export function useConversations(): UseConversationsReturn {
|
|||||||
|
|
||||||
// 冷启动兜底:延迟做一次强制刷新,避免首次因时序问题拿到空列表
|
// 冷启动兜底:延迟做一次强制刷新,避免首次因时序问题拿到空列表
|
||||||
const coldStartSyncTimer = setTimeout(() => {
|
const coldStartSyncTimer = setTimeout(() => {
|
||||||
messageManager.requestConversationListRefresh('hooks-initial-refresh', {
|
messageManager.refreshConversations(true, 'hooks-initial-refresh').catch(error => {
|
||||||
force: true,
|
|
||||||
allowDefer: true,
|
|
||||||
}).catch(error => {
|
|
||||||
console.error('[useConversations] 冷启动强制刷新失败:', error);
|
console.error('[useConversations] 冷启动强制刷新失败:', error);
|
||||||
});
|
});
|
||||||
}, 1200);
|
}, 1200);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
clearTimeout(coldStartSyncTimer);
|
clearTimeout(coldStartSyncTimer);
|
||||||
unsubscribe();
|
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 监听 hasMore 变化
|
||||||
|
useEffect(() => {
|
||||||
|
setHasMore(messageManager.canLoadMoreConversations());
|
||||||
|
}, [conversations]);
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
await messageManager.requestConversationListRefresh('hooks-manual-refresh', {
|
await messageManager.refreshConversations(true, 'hooks-manual-refresh');
|
||||||
force: true,
|
|
||||||
allowDefer: false,
|
|
||||||
});
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const loadMore = useCallback(async () => {
|
const loadMore = useCallback(async () => {
|
||||||
@@ -101,66 +89,40 @@ interface UseMessagesReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定会话的消息
|
* 获取指定会话的消息
|
||||||
* 用于ChatScreen等需要显示消息的组件
|
* 使用 Zustand selector 自动订阅消息变化
|
||||||
*
|
|
||||||
* @param conversationId 会话ID
|
|
||||||
*/
|
*/
|
||||||
export function useMessages(conversationId: string | null): UseMessagesReturn {
|
export function useMessages(conversationId: string | null): UseMessagesReturn {
|
||||||
const [messages, setMessages] = useState<MessageResponse[]>([]);
|
const normalizedConversationId = conversationId ? String(conversationId) : null;
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
|
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
||||||
|
const messages = useMessageStore(
|
||||||
|
useShallow(state =>
|
||||||
|
normalizedConversationId ? (state.messagesMap.get(normalizedConversationId) || []) : []
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const isLoadingMessages = useMessageStore(state =>
|
||||||
|
normalizedConversationId ? state.loadingMessagesSet.has(normalizedConversationId) : false
|
||||||
|
);
|
||||||
const [hasMore, setHasMore] = useState(true);
|
const [hasMore, setHasMore] = useState(true);
|
||||||
const loadMoreInFlightRef = useRef(false);
|
const loadMoreInFlightRef = useRef(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!conversationId) {
|
if (!normalizedConversationId) {
|
||||||
setMessages([]);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizedConversationId = String(conversationId);
|
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
setHasMore(true);
|
|
||||||
let isUnmounted = false;
|
|
||||||
|
|
||||||
const unsubscribe = messageManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (
|
|
||||||
event.type === 'messages_updated' &&
|
|
||||||
String(event.payload.conversationId) === normalizedConversationId
|
|
||||||
) {
|
|
||||||
setMessages(event.payload.messages);
|
|
||||||
if (!isUnmounted) {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 先读取内存快照,确保页面切换时无闪烁
|
|
||||||
const cachedMessages = messageManager.getMessages(normalizedConversationId);
|
|
||||||
if (cachedMessages.length > 0) {
|
|
||||||
setMessages(cachedMessages);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 架构入口:激活会话并完成初始化/同步
|
// 架构入口:激活会话并完成初始化/同步
|
||||||
messageManager.activateConversation(normalizedConversationId).catch(error => {
|
messageManager.activateConversation(normalizedConversationId).catch(error => {
|
||||||
console.error('[useMessages] 激活会话失败:', error);
|
console.error('[useMessages] 激活会话失败:', error);
|
||||||
if (!isUnmounted) {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
}).finally(() => {
|
|
||||||
if (!isUnmounted) {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isUnmounted = true;
|
// 清理活动会话
|
||||||
unsubscribe();
|
if (messageManager.getActiveConversation() === normalizedConversationId) {
|
||||||
if (String(messageManager.getActiveConversation()) === normalizedConversationId) {
|
|
||||||
messageManager.setActiveConversation(null);
|
messageManager.setActiveConversation(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [conversationId]);
|
}, [normalizedConversationId]);
|
||||||
|
|
||||||
const loadMore = useCallback(async () => {
|
const loadMore = useCallback(async () => {
|
||||||
if (!conversationId || !hasMore || loadMoreInFlightRef.current) return;
|
if (!conversationId || !hasMore || loadMoreInFlightRef.current) return;
|
||||||
@@ -184,16 +146,13 @@ export function useMessages(conversationId: string | null): UseMessagesReturn {
|
|||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
if (!conversationId) return;
|
if (!conversationId) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
await messageManager.fetchMessages(conversationId);
|
await messageManager.fetchMessages(conversationId);
|
||||||
setMessages(messageManager.getMessages(conversationId));
|
|
||||||
setIsLoading(false);
|
|
||||||
setHasMore(true);
|
setHasMore(true);
|
||||||
}, [conversationId]);
|
}, [conversationId]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messages,
|
messages,
|
||||||
isLoading,
|
isLoading: isLoadingMessages,
|
||||||
hasMore,
|
hasMore,
|
||||||
loadMore,
|
loadMore,
|
||||||
refresh,
|
refresh,
|
||||||
@@ -209,7 +168,6 @@ interface UseSendMessageReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送消息
|
* 发送消息
|
||||||
* 返回发送函数和发送状态
|
|
||||||
*/
|
*/
|
||||||
export function useSendMessage(conversationId: string | null): UseSendMessageReturn {
|
export function useSendMessage(conversationId: string | null): UseSendMessageReturn {
|
||||||
const [isSending, setIsSending] = useState(false);
|
const [isSending, setIsSending] = useState(false);
|
||||||
@@ -245,7 +203,6 @@ interface UseMarkAsReadReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 标记已读
|
* 标记已读
|
||||||
* 用于ChatScreen标记当前会话已读
|
|
||||||
*/
|
*/
|
||||||
export function useMarkAsRead(conversationId: string | null): UseMarkAsReadReturn {
|
export function useMarkAsRead(conversationId: string | null): UseMarkAsReadReturn {
|
||||||
const [isMarking, setIsMarking] = useState(false);
|
const [isMarking, setIsMarking] = useState(false);
|
||||||
@@ -289,27 +246,21 @@ interface UseUnreadCountReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取未读消息数
|
* 获取未读消息数
|
||||||
* 用于TabBar角标等
|
* 使用 Zustand selector 直接订阅未读数变化
|
||||||
*/
|
*/
|
||||||
export function useUnreadCount(): UseUnreadCountReturn {
|
export function useUnreadCount(): UseUnreadCountReturn {
|
||||||
const [counts, setCounts] = useState(() => messageManager.getUnreadCount());
|
// 使用 Zustand selector 直接订阅未读数
|
||||||
|
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
|
||||||
|
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = messageManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'unread_count_updated') {
|
|
||||||
setCounts(messageManager.getUnreadCount());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化时获取最新未读数
|
// 初始化时获取最新未读数
|
||||||
messageManager.fetchUnreadCount();
|
messageManager.fetchUnreadCount();
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
totalUnreadCount: counts.total,
|
totalUnreadCount,
|
||||||
systemUnreadCount: counts.system,
|
systemUnreadCount,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,29 +268,18 @@ export function useUnreadCount(): UseUnreadCountReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取总未读消息数(会话未读 + 系统消息未读)
|
* 获取总未读消息数(会话未读 + 系统消息未读)
|
||||||
* 专用于TabBar徽章显示
|
* 使用 Zustand selector 直接订阅
|
||||||
*/
|
*/
|
||||||
export function useTotalUnreadCount(): number {
|
export function useTotalUnreadCount(): number {
|
||||||
const [totalCount, setTotalCount] = useState(() => {
|
const totalUnreadCount = useMessageStore(state => state.totalUnreadCount);
|
||||||
const counts = messageManager.getUnreadCount();
|
const systemUnreadCount = useMessageStore(state => state.systemUnreadCount);
|
||||||
return counts.total + counts.system;
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = messageManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'unread_count_updated') {
|
|
||||||
const counts = messageManager.getUnreadCount();
|
|
||||||
setTotalCount(counts.total + counts.system);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化时获取最新未读数
|
// 初始化时获取最新未读数
|
||||||
messageManager.fetchUnreadCount();
|
messageManager.fetchUnreadCount();
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return totalCount;
|
return totalUnreadCount + systemUnreadCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== useConversation - 获取单个会话信息 ====================
|
// ==================== useConversation - 获取单个会话信息 ====================
|
||||||
@@ -352,37 +292,22 @@ interface UseConversationReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取单个会话信息
|
* 获取单个会话信息
|
||||||
|
* 使用 Zustand selector 直接订阅会话变化
|
||||||
*/
|
*/
|
||||||
export function useConversation(conversationId: string | null): UseConversationReturn {
|
export function useConversation(conversationId: string | null): UseConversationReturn {
|
||||||
const [conversation, setConversation] = useState<ConversationResponse | null>(null);
|
// 使用 useShallow 避免每次返回新对象引用导致的无限循环
|
||||||
|
const conversation = useMessageStore(
|
||||||
|
useShallow(state =>
|
||||||
|
conversationId ? (state.conversations.get(conversationId) || null) : null
|
||||||
|
)
|
||||||
|
);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!conversationId) {
|
|
||||||
setConversation(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前会话信息
|
|
||||||
setConversation(messageManager.getConversation(conversationId));
|
|
||||||
|
|
||||||
// 订阅更新
|
|
||||||
const unsubscribe = messageManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'conversations_updated') {
|
|
||||||
const updated = messageManager.getConversation(conversationId);
|
|
||||||
setConversation(updated);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, [conversationId]);
|
|
||||||
|
|
||||||
const refresh = useCallback(async () => {
|
const refresh = useCallback(async () => {
|
||||||
if (!conversationId) return;
|
if (!conversationId) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
await messageManager.fetchConversationDetail(conversationId);
|
await messageManager.fetchConversationDetail(conversationId);
|
||||||
setConversation(messageManager.getConversation(conversationId));
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}, [conversationId]);
|
}, [conversationId]);
|
||||||
|
|
||||||
@@ -393,7 +318,7 @@ export function useConversation(conversationId: string | null): UseConversationR
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== useMessageManager - 通用订阅 ====================
|
// ==================== useMessageManager - 通用状态 ====================
|
||||||
|
|
||||||
interface UseMessageManagerReturn {
|
interface UseMessageManagerReturn {
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
@@ -401,26 +326,19 @@ interface UseMessageManagerReturn {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通用MessageManager订阅
|
* 通用MessageManager状态
|
||||||
* 用于获取连接状态等全局信息
|
* 使用 Zustand selector 直接订阅连接状态
|
||||||
*/
|
*/
|
||||||
export function useMessageManager(): UseMessageManagerReturn {
|
export function useMessageManager(): UseMessageManagerReturn {
|
||||||
const [isConnected, setIsConnected] = useState(() => messageManager.isConnected());
|
// 使用 Zustand selector 直接订阅状态
|
||||||
const [isInitialized, setIsInitialized] = useState(false);
|
const isConnected = useMessageStore(state => state.isWSConnected);
|
||||||
|
const isInitialized = useMessageStore(state => state.isInitialized);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unsubscribe = messageManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'connection_changed') {
|
|
||||||
setIsConnected(event.payload.connected);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 初始化
|
// 初始化
|
||||||
messageManager.initialize().then(() => {
|
messageManager.initialize().catch(error => {
|
||||||
setIsInitialized(true);
|
console.error('[useMessageManager] 初始化失败:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -517,10 +435,6 @@ interface UseMessageListRefreshReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* MessageListScreen焦点刷新Hook
|
* MessageListScreen焦点刷新Hook
|
||||||
* 从 ChatScreen 返回时,MessageManager 内存状态已经是最新的(markAsRead 乐观更新),
|
|
||||||
* 无需再请求服务器(避免时序竞争覆盖已读状态)。
|
|
||||||
* 仅在用户主动下拉刷新时才触发服务器请求。
|
|
||||||
* 注意:isFocused 参数已移除,不再触发焦点刷新
|
|
||||||
*/
|
*/
|
||||||
export function useMessageListRefresh(): UseMessageListRefreshReturn {
|
export function useMessageListRefresh(): UseMessageListRefreshReturn {
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
@@ -536,10 +450,7 @@ export function useMessageListRefresh(): UseMessageListRefreshReturn {
|
|||||||
|
|
||||||
setIsRefreshing(true);
|
setIsRefreshing(true);
|
||||||
try {
|
try {
|
||||||
await messageManager.requestConversationListRefresh('hooks-load-more-fallback', {
|
await messageManager.refreshConversations(true, 'hooks-load-more-fallback');
|
||||||
force: true,
|
|
||||||
allowDefer: false,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[useMessageListRefresh] 刷新失败:', error);
|
console.error('[useMessageListRefresh] 刷新失败:', error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -547,9 +458,6 @@ export function useMessageListRefresh(): UseMessageListRefreshReturn {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// 获得焦点时不主动拉服务器——内存状态已是最新,避免时序竞争
|
|
||||||
// refresh 仅供下拉刷新手动调用
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
refresh,
|
refresh,
|
||||||
isRefreshing,
|
isRefreshing,
|
||||||
@@ -560,7 +468,6 @@ export function useMessageListRefresh(): UseMessageListRefreshReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* ChatScreen专用Hook组合
|
* ChatScreen专用Hook组合
|
||||||
* 整合所有ChatScreen需要的功能
|
|
||||||
*/
|
*/
|
||||||
interface UseChatReturn {
|
interface UseChatReturn {
|
||||||
// 消息相关
|
// 消息相关
|
||||||
@@ -608,29 +515,15 @@ interface UseGroupTypingReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取群聊输入状态
|
* 获取群聊输入状态
|
||||||
* @param groupId 群组ID
|
* 使用 Zustand selector 直接订阅
|
||||||
*/
|
*/
|
||||||
export function useGroupTyping(groupId: string | null): UseGroupTypingReturn {
|
export function useGroupTyping(groupId: string | null): UseGroupTypingReturn {
|
||||||
const [typingUsers, setTypingUsers] = useState<string[]>([]);
|
// 使用 useShallow 避免每次返回新数组引用导致的无限循环
|
||||||
|
const typingUsers = useMessageStore(
|
||||||
useEffect(() => {
|
useShallow(state =>
|
||||||
if (!groupId) {
|
groupId ? (state.typingUsersMap.get(groupId) || []) : []
|
||||||
setTypingUsers([]);
|
)
|
||||||
return;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// 初始值
|
|
||||||
setTypingUsers(messageManager.getTypingUsers(groupId));
|
|
||||||
|
|
||||||
// 订阅输入状态变化
|
|
||||||
const unsubscribe = messageManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'typing_status' && event.payload.groupId === groupId) {
|
|
||||||
setTypingUsers(event.payload.typingUsers);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, [groupId]);
|
|
||||||
|
|
||||||
return { typingUsers };
|
return { typingUsers };
|
||||||
}
|
}
|
||||||
@@ -644,42 +537,17 @@ interface UseGroupMutedReturn {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取群聊禁言状态
|
* 获取群聊禁言状态
|
||||||
* @param groupId 群组ID
|
* 使用 Zustand selector 直接订阅
|
||||||
* @param currentUserId 当前用户ID(用于判断禁言通知是否针对当前用户)
|
|
||||||
*/
|
*/
|
||||||
export function useGroupMuted(groupId: string | null, currentUserId?: string): UseGroupMutedReturn {
|
export function useGroupMuted(groupId: string | null, currentUserId?: string): UseGroupMutedReturn {
|
||||||
const [isMuted, setIsMuted] = useState(false);
|
// 使用 Zustand selector 直接订阅禁言状态
|
||||||
|
const isMuted = useMessageStore(state =>
|
||||||
useEffect(() => {
|
groupId ? (state.mutedStatusMap.get(groupId) || false) : false
|
||||||
if (!groupId) {
|
);
|
||||||
setIsMuted(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始值
|
|
||||||
setIsMuted(messageManager.isMuted(groupId));
|
|
||||||
|
|
||||||
// 订阅群通知变化
|
|
||||||
const unsubscribe = messageManager.subscribe((event: MessageEvent) => {
|
|
||||||
if (event.type === 'group_notice' && event.payload.groupId === groupId) {
|
|
||||||
const { noticeType, data } = event.payload;
|
|
||||||
|
|
||||||
// 如果提供了当前用户ID,只响应当前用户的禁言/解禁通知
|
|
||||||
if ((noticeType === 'muted' || noticeType === 'unmuted')) {
|
|
||||||
if (!currentUserId || data?.user_id === currentUserId) {
|
|
||||||
setIsMuted(noticeType === 'muted');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return unsubscribe;
|
|
||||||
}, [groupId, currentUserId]);
|
|
||||||
|
|
||||||
const setMuted = useCallback((muted: boolean) => {
|
const setMuted = useCallback((muted: boolean) => {
|
||||||
if (groupId) {
|
if (groupId) {
|
||||||
messageManager.setMutedStatus(groupId, muted);
|
messageManager.setMutedStatus(groupId, muted);
|
||||||
setIsMuted(muted);
|
|
||||||
}
|
}
|
||||||
}, [groupId]);
|
}, [groupId]);
|
||||||
|
|
||||||
@@ -698,13 +566,12 @@ interface UseMessageListReturn {
|
|||||||
totalUnreadCount: number;
|
totalUnreadCount: number;
|
||||||
systemUnreadCount: number;
|
systemUnreadCount: number;
|
||||||
markAllAsRead: () => Promise<void>;
|
markAllAsRead: () => Promise<void>;
|
||||||
isMarking: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useMessageList(): UseMessageListReturn {
|
export function useMessageList(): UseMessageListReturn {
|
||||||
const { conversations, isLoading, refresh, loadMore, hasMore } = useConversations();
|
const { conversations, isLoading, refresh, loadMore, hasMore } = useConversations();
|
||||||
const { totalUnreadCount, systemUnreadCount } = useUnreadCount();
|
const { totalUnreadCount, systemUnreadCount } = useUnreadCount();
|
||||||
const { markAllAsRead, isMarking } = useMarkAsRead(null);
|
const { markAllAsRead } = useMarkAsRead(null);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
conversations,
|
conversations,
|
||||||
@@ -715,6 +582,5 @@ export function useMessageList(): UseMessageListReturn {
|
|||||||
totalUnreadCount,
|
totalUnreadCount,
|
||||||
systemUnreadCount,
|
systemUnreadCount,
|
||||||
markAllAsRead,
|
markAllAsRead,
|
||||||
isMarking,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user