feat(chat): 添加聊天界面视图模型和联系人管理功能

- 实现 ChatViewModel 处理消息收发、历史记录加载和状态管理
- 添加消息搜索、草稿保存、引用回复等功能
- 实现多媒体附件发送包括图片、视频、音频和文件
- 添加群组提及用户功能和消息撤回机制
- 实现联系人管理功能包括好友搜索、添加、删除和黑名单管理
- 添加好友请求处理和实时消息监听
- 实现会话列表管理包含未读消息统计和实时更新
- 集成 IM SDK 的连接状态管理和事件监听
- 添加消息状态跟踪和超时处理机制
- 实现数据缓存机制优化用户体验
这个提交包含在:
XuqmGroup 2026-04-28 22:32:21 +08:00
父节点 0886c2e355
当前提交 d00231279e
共有 4 个文件被更改,包括 67 次插入0 次删除

查看文件

@ -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 {

查看文件

@ -335,6 +335,38 @@ export const ImSDK = {
return messages
},
async locateHistoryPage(
toId: string,
messageId: string,
pageSize = 20,
maxPages = 20,
): Promise<ImMessage[] | null> {
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<ImMessage[] | null> {
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<ImMessage> {
const config = getConfig()
const msg = await apiRequest<ImMessage>(`/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<ImGroup> {
const config = getConfig()
return apiRequest<ImGroup>('/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)

查看文件

@ -8,6 +8,9 @@ export const removeFriend = (friendId: string): Promise<void> => _ImSDK.removeFr
export const searchUsers = (keyword: string, size?: number): ReturnType<typeof _ImSDK.searchUsers> => _ImSDK.searchUsers(keyword, size)
export const searchGroups = (keyword: string, size?: number): ReturnType<typeof _ImSDK.searchGroups> => _ImSDK.searchGroups(keyword, size)
export const searchMessages = (params: Parameters<typeof _ImSDK.searchMessages>[0]): ReturnType<typeof _ImSDK.searchMessages> => _ImSDK.searchMessages(params)
export const editMessage = (messageId: string, content: string): ReturnType<typeof _ImSDK.editMessage> => _ImSDK.editMessage(messageId, content)
export const locateHistoryPage = (toId: string, messageId: string, pageSize?: number, maxPages?: number): ReturnType<typeof _ImSDK.locateHistoryPage> => _ImSDK.locateHistoryPage(toId, messageId, pageSize, maxPages)
export const locateGroupHistoryPage = (groupId: string, messageId: string, pageSize?: number, maxPages?: number): ReturnType<typeof _ImSDK.locateGroupHistoryPage> => _ImSDK.locateGroupHistoryPage(groupId, messageId, pageSize, maxPages)
export { ImClient } from './ImClient'
export { ImDatabase } from './db/ImDatabase'
export type { MessageSearchParams } from './db/ImDatabase'

查看文件

@ -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
}