feat(chat): 添加聊天界面视图模型和联系人管理功能
- 实现 ChatViewModel 处理消息收发、历史记录加载和状态管理 - 添加消息搜索、草稿保存、引用回复等功能 - 实现多媒体附件发送包括图片、视频、音频和文件 - 添加群组提及用户功能和消息撤回机制 - 实现联系人管理功能包括好友搜索、添加、删除和黑名单管理 - 添加好友请求处理和实时消息监听 - 实现会话列表管理包含未读消息统计和实时更新 - 集成 IM SDK 的连接状态管理和事件监听 - 添加消息状态跟踪和超时处理机制 - 实现数据缓存机制优化用户体验
这个提交包含在:
父节点
0886c2e355
当前提交
d00231279e
@ -210,6 +210,14 @@ export class ImClient {
|
|||||||
|
|
||||||
if (frame.command === 'MESSAGE') {
|
if (frame.command === 'MESSAGE') {
|
||||||
const message: ImMessage = this.normalizeMessage(JSON.parse(frame.body))
|
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') {
|
if (message.chatType === 'GROUP') {
|
||||||
this.listeners.forEach(listener => listener.onGroupMessage?.(message))
|
this.listeners.forEach(listener => listener.onGroupMessage?.(message))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -335,6 +335,38 @@ export const ImSDK = {
|
|||||||
return messages
|
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(
|
async sendMessage(
|
||||||
toId: string,
|
toId: string,
|
||||||
chatType: ChatType,
|
chatType: ChatType,
|
||||||
@ -534,6 +566,19 @@ export const ImSDK = {
|
|||||||
return msg
|
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> {
|
async createGroup(name: string, memberIds: string[], groupType = 'WORK'): Promise<ImGroup> {
|
||||||
const config = getConfig()
|
const config = getConfig()
|
||||||
return apiRequest<ImGroup>('/api/im/groups', {
|
return apiRequest<ImGroup>('/api/im/groups', {
|
||||||
@ -948,6 +993,15 @@ export const ImSDK = {
|
|||||||
}
|
}
|
||||||
listener.onSystemMessage?.(normalizeMessage(msg))
|
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)
|
listenerMap.set(listener, wrapped)
|
||||||
client?.addListener(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 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 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 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 { ImClient } from './ImClient'
|
||||||
export { ImDatabase } from './db/ImDatabase'
|
export { ImDatabase } from './db/ImDatabase'
|
||||||
export type { MessageSearchParams } from './db/ImDatabase'
|
export type { MessageSearchParams } from './db/ImDatabase'
|
||||||
|
|||||||
@ -41,6 +41,8 @@ export interface ImEventListener {
|
|||||||
onMessage?: (msg: ImMessage) => void
|
onMessage?: (msg: ImMessage) => void
|
||||||
onGroupMessage?: (msg: ImMessage) => void
|
onGroupMessage?: (msg: ImMessage) => void
|
||||||
onSystemMessage?: (msg: ImMessage) => void
|
onSystemMessage?: (msg: ImMessage) => void
|
||||||
|
onRead?: (msg: ImMessage) => void
|
||||||
|
onRevoke?: (data: { msgId: string; operatorId: string }) => void
|
||||||
onError?: (error: string) => void
|
onError?: (error: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户