feat(chat): 添加聊天界面视图模型和联系人管理功能
- 实现 ChatViewModel 处理消息收发、历史记录加载和状态管理 - 添加消息搜索、草稿保存、引用回复等功能 - 实现多媒体附件发送包括图片、视频、音频和文件 - 添加群组提及用户功能和消息撤回机制 - 实现联系人管理功能包括好友搜索、添加、删除和黑名单管理 - 添加好友请求处理和实时消息监听 - 实现会话列表管理包含未读消息统计和实时更新 - 集成 IM SDK 的连接状态管理和事件监听 - 添加消息状态跟踪和超时处理机制 - 实现数据缓存机制优化用户体验
这个提交包含在:
父节点
d3eb86ae8c
当前提交
cc139e14e2
@ -49,7 +49,14 @@ export class ImClient {
|
||||
try {
|
||||
const frame = JSON.parse(event.data as string)
|
||||
if (frame.type === 'MESSAGE') {
|
||||
this.emit('message', this.normalizeMessage(frame.payload as ImMessage))
|
||||
const message = this.normalizeMessage(frame.payload as ImMessage)
|
||||
if (message.status === 'READ') {
|
||||
this.emit('read', message)
|
||||
}
|
||||
if (message.revoked || message.status === 'REVOKED' || message.msgType === 'REVOKED') {
|
||||
this.emit('revoke', { msgId: message.id, operatorId: message.fromId ?? message.fromUserId })
|
||||
}
|
||||
this.emit('message', message)
|
||||
} else if (frame.type === 'REVOKE') {
|
||||
this.emit('revoke', frame.payload as { msgId: string; operatorId: string })
|
||||
}
|
||||
|
||||
@ -48,6 +48,54 @@ export function fetchGroupHistory(groupId: string, query: HistoryQuery = {}): Pr
|
||||
)
|
||||
}
|
||||
|
||||
export async function locateHistoryPage(
|
||||
toId: string,
|
||||
messageId: string,
|
||||
pageSize = 20,
|
||||
maxPages = 20,
|
||||
): Promise<ImMessage[] | null> {
|
||||
for (let page = 0; page < Math.max(maxPages, 1); page += 1) {
|
||||
const result = await fetchHistory(toId, { page, size: pageSize })
|
||||
if (result.content.some((item) => item.id === messageId)) {
|
||||
return result.content
|
||||
}
|
||||
if (result.content.length < pageSize) return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export async function locateGroupHistoryPage(
|
||||
groupId: string,
|
||||
messageId: string,
|
||||
pageSize = 20,
|
||||
maxPages = 20,
|
||||
): Promise<ImMessage[] | null> {
|
||||
for (let page = 0; page < Math.max(maxPages, 1); page += 1) {
|
||||
const result = await fetchGroupHistory(groupId, { page, size: pageSize })
|
||||
if (result.content.some((item) => item.id === messageId)) {
|
||||
return result.content
|
||||
}
|
||||
if (result.content.length < pageSize) return null
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export function editMessage(messageId: string, content: string): Promise<ImMessage> {
|
||||
return http.put<ImMessage>(
|
||||
`/api/im/messages/${encodeURIComponent(messageId)}`,
|
||||
{ content },
|
||||
appQuery(),
|
||||
)
|
||||
}
|
||||
|
||||
export function revokeMessage(messageId: string): Promise<ImMessage> {
|
||||
return http.post<ImMessage>(
|
||||
`/api/im/messages/${encodeURIComponent(messageId)}/revoke`,
|
||||
undefined,
|
||||
appQuery(),
|
||||
)
|
||||
}
|
||||
|
||||
export function markRead(targetId: string, chatType: ChatType = 'SINGLE'): Promise<void> {
|
||||
return http.put<void>(
|
||||
`/api/im/conversations/${encodeURIComponent(targetId)}/read`,
|
||||
|
||||
@ -7,6 +7,10 @@ import {
|
||||
getGroupInfo,
|
||||
fetchGroupHistory,
|
||||
fetchHistory,
|
||||
editMessage,
|
||||
locateGroupHistoryPage,
|
||||
locateHistoryPage,
|
||||
revokeMessage,
|
||||
listFriendRequests,
|
||||
listFriends,
|
||||
listGroupJoinRequests,
|
||||
@ -64,6 +68,14 @@ export function useIm() {
|
||||
return fetchGroupHistory(groupId, query)
|
||||
}
|
||||
|
||||
function jumpToMessagePage(toId: string, messageId: string, pageSize = 20, maxPages = 20) {
|
||||
return locateHistoryPage(toId, messageId, pageSize, maxPages)
|
||||
}
|
||||
|
||||
function jumpToGroupMessagePage(groupId: string, messageId: string, pageSize = 20, maxPages = 20) {
|
||||
return locateGroupHistoryPage(groupId, messageId, pageSize, maxPages)
|
||||
}
|
||||
|
||||
function connect() {
|
||||
const im = new ImClient()
|
||||
im.on('connected', () => {
|
||||
@ -75,6 +87,10 @@ export function useIm() {
|
||||
upsertMessage(msg)
|
||||
void refreshConversations().catch(() => {})
|
||||
})
|
||||
im.on('read', (msg) => {
|
||||
upsertMessage(msg)
|
||||
void refreshConversations().catch(() => {})
|
||||
})
|
||||
im.on('error', (e) => { error.value = e })
|
||||
im.connect()
|
||||
client.value = im
|
||||
@ -89,7 +105,11 @@ export function useIm() {
|
||||
}
|
||||
|
||||
function revoke(msgId: string) {
|
||||
client.value?.revoke(msgId)
|
||||
return revokeMessage(msgId)
|
||||
}
|
||||
|
||||
function edit(msgId: string, content: string) {
|
||||
return editMessage(msgId, content)
|
||||
}
|
||||
|
||||
function disconnect() {
|
||||
@ -168,6 +188,7 @@ export function useIm() {
|
||||
connect,
|
||||
send,
|
||||
revoke,
|
||||
edit,
|
||||
disconnect,
|
||||
messages,
|
||||
conversations,
|
||||
@ -176,6 +197,8 @@ export function useIm() {
|
||||
refreshConversations,
|
||||
loadHistory,
|
||||
loadGroupHistory,
|
||||
jumpToMessagePage,
|
||||
jumpToGroupMessagePage,
|
||||
setConversationRead,
|
||||
setConversationPinnedState,
|
||||
setConversationMutedState,
|
||||
|
||||
@ -8,6 +8,10 @@ export {
|
||||
getGroupInfo,
|
||||
fetchGroupHistory,
|
||||
fetchHistory,
|
||||
editMessage,
|
||||
locateGroupHistoryPage,
|
||||
locateHistoryPage,
|
||||
revokeMessage,
|
||||
listFriendRequests,
|
||||
listFriends,
|
||||
listGroupJoinRequests,
|
||||
|
||||
@ -130,6 +130,7 @@ export interface SendMessageParams {
|
||||
|
||||
export interface ImEventMap {
|
||||
message: (msg: ImMessage) => void
|
||||
read: (msg: ImMessage) => void
|
||||
revoke: (data: { msgId: string; operatorId: string }) => void
|
||||
connected: () => void
|
||||
disconnected: (code: number, reason: string) => void
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户