diff --git a/electron/main.ts b/electron/main.ts index 2b44252..55464cd 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -2253,6 +2253,10 @@ function registerIpcHandlers() { return chatService.getSessions() }) + ipcMain.handle('chat:markAllSessionsRead', async () => { + return chatService.markAllSessionsRead() + }) + ipcMain.handle('chat:getSessionStatuses', async (_, usernames: string[]) => { return chatService.getSessionStatuses(usernames) }) diff --git a/electron/preload.ts b/electron/preload.ts index 562d968..b4f6371 100644 --- a/electron/preload.ts +++ b/electron/preload.ts @@ -185,6 +185,7 @@ contextBridge.exposeInMainWorld('electronAPI', { chat: { connect: () => ipcRenderer.invoke('chat:connect'), getSessions: () => ipcRenderer.invoke('chat:getSessions'), + markAllSessionsRead: () => ipcRenderer.invoke('chat:markAllSessionsRead'), getAntiRevokeSessions: () => ipcRenderer.invoke('chat:getAntiRevokeSessions'), getSessionStatuses: (usernames: string[]) => ipcRenderer.invoke('chat:getSessionStatuses', usernames), getExportTabCounts: () => ipcRenderer.invoke('chat:getExportTabCounts'), diff --git a/electron/services/chatService.ts b/electron/services/chatService.ts index c436218..bab7136 100644 --- a/electron/services/chatService.ts +++ b/electron/services/chatService.ts @@ -978,6 +978,23 @@ class ChatService { } } + async markAllSessionsRead(): Promise<{ success: boolean; error?: string }> { + try { + const connectResult = await this.ensureConnected() + if (!connectResult.success) { + return { success: false, error: connectResult.error } + } + const result = await wcdbService.markAllSessionsRead() + if (result.success) { + this.syntheticUnreadState.clear() + } + return result + } catch (e) { + console.error('ChatService: 一键已读失败:', e) + return { success: false, error: String(e) } + } + } + private getSessionUsername(row: Record): string { return String( row.username || diff --git a/electron/services/wcdbCore.ts b/electron/services/wcdbCore.ts index 5055f1c..d2bf8f8 100644 --- a/electron/services/wcdbCore.ts +++ b/electron/services/wcdbCore.ts @@ -35,6 +35,7 @@ export class WcdbCore { private wcdbUpdateMessage: any = null private wcdbDeleteMessage: any = null private wcdbGetSessions: any = null + private wcdbMarkAllSessionsRead: any = null private wcdbGetMessages: any = null private wcdbGetMessageCount: any = null private wcdbGetDisplayNames: any = null @@ -810,6 +811,13 @@ export class WcdbCore { // wcdb_status wcdb_get_sessions(wcdb_handle handle, char** out_json) this.wcdbGetSessions = this.lib.func('int32 wcdb_get_sessions(int64 handle, _Out_ void** outJson)') + // wcdb_status wcdb_mark_all_sessions_read(wcdb_handle handle, char** out_error) + try { + this.wcdbMarkAllSessionsRead = this.lib.func('int32 wcdb_mark_all_sessions_read(int64 handle, _Out_ void** outError)') + } catch { + this.wcdbMarkAllSessionsRead = null + } + // wcdb_status wcdb_get_messages(wcdb_handle handle, const char* username, int32_t limit, int32_t offset, char** out_json) this.wcdbGetMessages = this.lib.func('int32 wcdb_get_messages(int64 handle, const char* username, int32 limit, int32 offset, _Out_ void** outJson)') @@ -1697,6 +1705,39 @@ export class WcdbCore { } } + async markAllSessionsRead(): Promise<{ success: boolean; error?: string }> { + if (!this.ensureReady()) { + return { success: false, error: 'WCDB 未连接' } + } + if (!this.wcdbMarkAllSessionsRead) { + return { success: false, error: '当前数据服务版本不支持一键已读' } + } + try { + await new Promise(resolve => setImmediate(resolve)) + + const outPtr = [null as any] + const result = this.wcdbMarkAllSessionsRead(this.handle, outPtr) + let message = '' + if (outPtr[0]) { + try { message = this.koffi.decode(outPtr[0], 'char', -1) } catch { } + try { this.wcdbFreeString(outPtr[0]) } catch { } + } + + await new Promise(resolve => setImmediate(resolve)) + + if (result !== 0) { + this.writeLog(`markAllSessionsRead failed: code=${result} error=${message}`) + return { success: false, error: message || `一键已读失败: ${result}` } + } + this.clearMediaStreamSessionCache() + this.writeLog('markAllSessionsRead ok') + return { success: true } + } catch (e) { + this.writeLog(`markAllSessionsRead exception: ${String(e)}`) + return { success: false, error: String(e) } + } + } + async getMessages(sessionId: string, limit: number, offset: number): Promise<{ success: boolean; messages?: any[]; error?: string }> { if (!this.ensureReady()) { return { success: false, error: 'WCDB 未连接' } diff --git a/electron/services/wcdbService.ts b/electron/services/wcdbService.ts index 9577170..3347c4b 100644 --- a/electron/services/wcdbService.ts +++ b/electron/services/wcdbService.ts @@ -204,6 +204,10 @@ export class WcdbService { return this.callWorker('getSessions') } + async markAllSessionsRead(): Promise<{ success: boolean; error?: string }> { + return this.callWorker('markAllSessionsRead') + } + /** * 获取消息列表 */ diff --git a/electron/wcdbWorker.ts b/electron/wcdbWorker.ts index 103d085..be4bf68 100644 --- a/electron/wcdbWorker.ts +++ b/electron/wcdbWorker.ts @@ -50,6 +50,9 @@ if (parentPort) { case 'getSessions': result = await core.getSessions() break + case 'markAllSessionsRead': + result = await core.markAllSessionsRead() + break case 'getMessages': result = await core.getMessages(payload.sessionId, payload.limit, payload.offset) break diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index 30d3902..9a246c4 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -1453,6 +1453,7 @@ function ChatPage(props: ChatPageProps) { const [showScrollToBottom, setShowScrollToBottom] = useState(false) const [sidebarWidth, setSidebarWidth] = useState(260) const [isResizing, setIsResizing] = useState(false) + const [isMarkingAllSessionsRead, setIsMarkingAllSessionsRead] = useState(false) const [showDetailPanel, setShowDetailPanel] = useState(false) const [showGroupMembersPanel, setShowGroupMembersPanel] = useState(false) const [sessionDetail, setSessionDetail] = useState(null) @@ -3130,6 +3131,35 @@ function ChatPage(props: ChatPageProps) { } } + const handleMarkAllSessionsRead = async () => { + if (isMarkingAllSessionsRead || isLoadingSessions || isRefreshingSessions) return + setIsMarkingAllSessionsRead(true) + setConnectionError(null) + try { + const result = await window.electronAPI.chat.markAllSessionsRead() + if (!result.success) { + setConnectionError(result.error || '一键已读失败') + return + } + + const latestSessions = useChatStore.getState().sessions || [] + const nextSessions = latestSessions.map((session) => ( + session.unreadCount > 0 ? { ...session, unreadCount: 0 } : session + )) + setSessions(nextSessions) + sessionsRef.current = nextSessions + + const scope = await resolveChatCacheScope() + persistSessionListCache(scope, nextSessions) + await loadSessions({ silent: true }) + } catch (e) { + console.error('一键已读失败:', e) + setConnectionError(`一键已读失败: ${String(e)}`) + } finally { + setIsMarkingAllSessionsRead(false) + } + } + // 分批异步加载联系人信息(优化:缓存优先 + 可持续队列 + 首屏优先批次) const enrichSessionsContactInfo = async (sessions: ChatSession[]) => { if (Array.isArray(sessions) && sessions.length > 0) { @@ -6775,6 +6805,15 @@ function ChatPage(props: ChatPageProps) { + {/* 折叠群 header */} diff --git a/src/types/electron.d.ts b/src/types/electron.d.ts index 1d25a0e..e1ec35f 100644 --- a/src/types/electron.d.ts +++ b/src/types/electron.d.ts @@ -271,6 +271,7 @@ export interface ElectronAPI { chat: { connect: () => Promise<{ success: boolean; error?: string }> getSessions: () => Promise<{ success: boolean; sessions?: ChatSession[]; error?: string }> + markAllSessionsRead: () => Promise<{ success: boolean; error?: string }> getAntiRevokeSessions: () => Promise<{ success: boolean; sessions?: ChatSession[]; error?: string }> getSessionStatuses: (usernames: string[]) => Promise<{ success: boolean