From d00231279e2eeb04a47497698a65faad1109cffd Mon Sep 17 00:00:00 2001 From: XuqmGroup Date: Tue, 28 Apr 2026 22:32:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=E6=B7=BB=E5=8A=A0=E8=81=8A?= =?UTF-8?q?=E5=A4=A9=E7=95=8C=E9=9D=A2=E8=A7=86=E5=9B=BE=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=92=8C=E8=81=94=E7=B3=BB=E4=BA=BA=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 实现 ChatViewModel 处理消息收发、历史记录加载和状态管理 - 添加消息搜索、草稿保存、引用回复等功能 - 实现多媒体附件发送包括图片、视频、音频和文件 - 添加群组提及用户功能和消息撤回机制 - 实现联系人管理功能包括好友搜索、添加、删除和黑名单管理 - 添加好友请求处理和实时消息监听 - 实现会话列表管理包含未读消息统计和实时更新 - 集成 IM SDK 的连接状态管理和事件监听 - 添加消息状态跟踪和超时处理机制 - 实现数据缓存机制优化用户体验 --- packages/im/src/ImClient.ts | 8 ++++++ packages/im/src/ImSDK.ts | 54 +++++++++++++++++++++++++++++++++++++ packages/im/src/index.ts | 3 +++ packages/im/src/types.ts | 2 ++ 4 files changed, 67 insertions(+) diff --git a/packages/im/src/ImClient.ts b/packages/im/src/ImClient.ts index ca5aee3..ca3b839 100644 --- a/packages/im/src/ImClient.ts +++ b/packages/im/src/ImClient.ts @@ -210,6 +210,14 @@ export class ImClient { if (frame.command === 'MESSAGE') { const message: ImMessage = this.normalizeMessage(JSON.parse(frame.body)) + if (message.status === 'READ') { + this.listeners.forEach(listener => listener.onRead?.(message)) + } + if (message.revoked || message.status === 'REVOKED' || message.msgType === 'REVOKED') { + this.listeners.forEach(listener => + listener.onRevoke?.({ msgId: message.id, operatorId: message.fromId ?? message.fromUserId }), + ) + } if (message.chatType === 'GROUP') { this.listeners.forEach(listener => listener.onGroupMessage?.(message)) } else { diff --git a/packages/im/src/ImSDK.ts b/packages/im/src/ImSDK.ts index ee9accb..c16eef8 100644 --- a/packages/im/src/ImSDK.ts +++ b/packages/im/src/ImSDK.ts @@ -335,6 +335,38 @@ export const ImSDK = { return messages }, + async locateHistoryPage( + toId: string, + messageId: string, + pageSize = 20, + maxPages = 20, + ): Promise { + for (let page = 0; page < Math.max(maxPages, 1); page += 1) { + const messages = await this.fetchHistory(toId, page, pageSize) + if (messages.some((item) => item.id === messageId)) { + return messages + } + if (messages.length < pageSize) return null + } + return null + }, + + async locateGroupHistoryPage( + groupId: string, + messageId: string, + pageSize = 50, + maxPages = 20, + ): Promise { + for (let page = 0; page < Math.max(maxPages, 1); page += 1) { + const messages = await this.fetchGroupHistory(groupId, page, pageSize) + if (messages.some((item) => item.id === messageId)) { + return messages + } + if (messages.length < pageSize) return null + } + return null + }, + async sendMessage( toId: string, chatType: ChatType, @@ -534,6 +566,19 @@ export const ImSDK = { return msg }, + async editMessage(messageId: string, content: string): Promise { + const config = getConfig() + const msg = await apiRequest(`/api/im/messages/${encodeURIComponent(messageId)}`, { + method: 'PUT', + params: { appId: config.appId }, + body: { content }, + }) + if (ImDatabase.isInitialized() && _currentUserId) { + await ImDatabase.saveMessage(normalizeMessage(msg), _currentUserId) + } + return msg + }, + async createGroup(name: string, memberIds: string[], groupType = 'WORK'): Promise { const config = getConfig() return apiRequest('/api/im/groups', { @@ -948,6 +993,15 @@ export const ImSDK = { } listener.onSystemMessage?.(normalizeMessage(msg)) }, + onRead: async (msg) => { + if (ImDatabase.isInitialized() && _currentUserId) { + await ImDatabase.saveMessage(normalizeMessage(msg), _currentUserId) + } + listener.onRead?.(normalizeMessage(msg)) + }, + onRevoke: async (data) => { + listener.onRevoke?.(data) + }, } listenerMap.set(listener, wrapped) client?.addListener(wrapped) diff --git a/packages/im/src/index.ts b/packages/im/src/index.ts index 7cbd547..43c9f05 100644 --- a/packages/im/src/index.ts +++ b/packages/im/src/index.ts @@ -8,6 +8,9 @@ export const removeFriend = (friendId: string): Promise => _ImSDK.removeFr export const searchUsers = (keyword: string, size?: number): ReturnType => _ImSDK.searchUsers(keyword, size) export const searchGroups = (keyword: string, size?: number): ReturnType => _ImSDK.searchGroups(keyword, size) export const searchMessages = (params: Parameters[0]): ReturnType => _ImSDK.searchMessages(params) +export const editMessage = (messageId: string, content: string): ReturnType => _ImSDK.editMessage(messageId, content) +export const locateHistoryPage = (toId: string, messageId: string, pageSize?: number, maxPages?: number): ReturnType => _ImSDK.locateHistoryPage(toId, messageId, pageSize, maxPages) +export const locateGroupHistoryPage = (groupId: string, messageId: string, pageSize?: number, maxPages?: number): ReturnType => _ImSDK.locateGroupHistoryPage(groupId, messageId, pageSize, maxPages) export { ImClient } from './ImClient' export { ImDatabase } from './db/ImDatabase' export type { MessageSearchParams } from './db/ImDatabase' diff --git a/packages/im/src/types.ts b/packages/im/src/types.ts index 1f88c61..b04bd71 100644 --- a/packages/im/src/types.ts +++ b/packages/im/src/types.ts @@ -41,6 +41,8 @@ export interface ImEventListener { onMessage?: (msg: ImMessage) => void onGroupMessage?: (msg: ImMessage) => void onSystemMessage?: (msg: ImMessage) => void + onRead?: (msg: ImMessage) => void + onRevoke?: (data: { msgId: string; operatorId: string }) => void onError?: (error: string) => void }