u
This commit is contained in:
1 parent
a6dbe24dd4
commit
52b16ed2b0
21 files changed
+444
-107
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import type { CreateFriendParams, ImFriend, ImGroup, ImGroupMessage, ImGroupUser, ImPrivateMessage, QueryFriendParams, QueryGroupMessageParams, QueryGroupParams, QueryGroupUserParams, QueryPrivateMessageParams, UpdateGroupUnreadParams, UpdatePrivateUnreadParams } from '@/types/im'
|
||||
import { httpPostJson } from '@/request/utils'
|
||||
import type { CreateFriendParams, ImFriend, ImFriendProfile, ImGroup, ImGroupMessage, ImGroupProfile, ImGroupUser, ImPrivateMessage, QueryFriendParams, QueryGroupMessageParams, QueryGroupParams, QueryGroupUserParams, QueryPrivateMessageParams, UpdateGroupUnreadParams, UpdatePrivateUnreadParams } from '@/types/im'
|
||||
import { httpGet, httpPostJson } from '@/request/utils'
|
||||
|
||||
export function createFriendApi(params: CreateFriendParams) {
|
||||
return httpPostJson<void>(`/im/friend/create`, params)
|
||||
@@ -31,3 +31,11 @@ export function updatePrivateUnreadApi(params: UpdatePrivateUnreadParams) {
|
||||
export function queryGroupUserApi(params: QueryGroupUserParams) {
|
||||
return httpPostJson<ImGroupUser[]>('/im/group/user/query', params)
|
||||
}
|
||||
|
||||
export function getFriendProfileApi(friendId: number) {
|
||||
return httpGet<ImFriendProfile>(`/im/friend/${friendId}`)
|
||||
}
|
||||
|
||||
export function getGroupProfileApi(groupId: number) {
|
||||
return httpGet<ImGroupProfile>(`/im/group/${groupId}`)
|
||||
}
|
||||
@@ -256,7 +256,7 @@ function createWebSocket(options?: Partial<UseWebSocketOptions>) {
|
||||
}
|
||||
|
||||
function sendGroup(groupId: number, content: any) {
|
||||
send(MessageType.GROUP, { groupId, content })
|
||||
send(MessageType.GROUP, content, groupId)
|
||||
}
|
||||
|
||||
/* ================== 初始化 ================== */
|
||||
|
||||
@@ -13,7 +13,6 @@ export enum MessageType {
|
||||
|
||||
PRIVATE = 8,
|
||||
GROUP = 9,
|
||||
FRIEND_UPDATE = 10,
|
||||
}
|
||||
|
||||
export interface MessageRequest {
|
||||
@@ -48,15 +47,12 @@ export interface QueryFriendParams {
|
||||
}
|
||||
|
||||
export interface ImFriend {
|
||||
id: number
|
||||
|
||||
friendId: number
|
||||
friendName: string
|
||||
friendAvatar: string
|
||||
|
||||
unreadCount: number
|
||||
|
||||
lastMessageId: number
|
||||
lastMessageTime: number
|
||||
lastMessageType: number
|
||||
lastMessageContent: string
|
||||
@@ -79,12 +75,10 @@ export interface ImGroup {
|
||||
|
||||
unreadCount: number
|
||||
|
||||
lastMessageId: number
|
||||
lastMessageTime: number
|
||||
lastMessageType: number
|
||||
lastMessageContent: string
|
||||
|
||||
lastMessageSenderId: number
|
||||
lastMessageSenderName: string
|
||||
}
|
||||
|
||||
@@ -152,3 +146,17 @@ export interface ImGroupUser {
|
||||
userName: string
|
||||
isOnline: boolean
|
||||
}
|
||||
|
||||
export interface ImFriendProfile {
|
||||
friendId: number
|
||||
friendName: string
|
||||
friendAvatar: string
|
||||
unreadCount: number
|
||||
}
|
||||
|
||||
export interface ImGroupProfile {
|
||||
groupId: number
|
||||
groupName: string
|
||||
groupAvatar: string
|
||||
unreadCount: number
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { useScrollWatcher } from '@/hooks/useScrollWatcher'
|
||||
import { queryGroupMessageApi, queryGroupUserApi } from '@/request/api/im'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useWebSocket } from '@/store/websocket'
|
||||
import { MessageType } from '@/types/im'
|
||||
import { formatImTime } from '@/utils/time'
|
||||
import { generateUUID } from '@/utils/uuid'
|
||||
|
||||
@@ -22,6 +23,40 @@ onMounted(() => {
|
||||
|
||||
websocket.onMessage(async (msg) => {
|
||||
const { from, type, data } = msg
|
||||
|
||||
if (type === MessageType.GROUP) {
|
||||
// 自己发的
|
||||
if (userId === data.senderId) {
|
||||
messages.value.forEach((item) => {
|
||||
if (item._tempId === data.tempId) {
|
||||
item._status = 'sent'
|
||||
item._tempId = ''
|
||||
item.id = data.id
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
if (props.group.groupId === from) {
|
||||
const newData: ImGroupMessage = {
|
||||
id: data.id,
|
||||
groupId: data.groupId,
|
||||
senderId: data.senderId,
|
||||
senderName: data.senderName,
|
||||
senderAvatar: data.senderAvatar,
|
||||
content: data.content,
|
||||
type: data.type,
|
||||
sendTime: data.sendTime,
|
||||
}
|
||||
|
||||
messages.value.push(newData)
|
||||
// 更新未读数量,交给外部
|
||||
|
||||
// 等待 DOM 更新再滚动到底部
|
||||
await nextTick()
|
||||
scrollToLatestMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -52,6 +87,7 @@ watch(
|
||||
pagination.value = { pageSize: 10, loading: false, hasMore: true }
|
||||
loadMessages('down')
|
||||
|
||||
groupUsers.value = []
|
||||
loadGroupUsers()
|
||||
}
|
||||
},
|
||||
@@ -106,8 +142,6 @@ async function loadMessages(direction = 'down') {
|
||||
|
||||
pagination.value.loading = true
|
||||
|
||||
const userId = userStore.user.id
|
||||
|
||||
let beforeTimestamp: number | null = null
|
||||
let beforeId: number | null = null
|
||||
|
||||
@@ -197,39 +231,44 @@ async function sendMessage() {
|
||||
ElMessage.warning('发送消息不能为空')
|
||||
}
|
||||
|
||||
// const userId = userStore.user.id
|
||||
// const _tempId = generateUUID()
|
||||
const userId = userStore.user.id
|
||||
const groupId = props.group.groupId
|
||||
const _tempId = generateUUID()
|
||||
|
||||
// const localMessage: ImPrivateMessage = {
|
||||
// id: -Date.now(),
|
||||
// senderId: userId,
|
||||
// receiverId: props.friend.friendId,
|
||||
// content,
|
||||
// type: 0,
|
||||
// sendTime: Date.now(),
|
||||
// _tempId,
|
||||
// _status: 'sending',
|
||||
// }
|
||||
const localMessage: ImGroupMessage = {
|
||||
id: -Date.now(),
|
||||
groupId,
|
||||
senderId: userId,
|
||||
senderAvatar: userStore.user.avatar,
|
||||
senderName: userStore.user.name,
|
||||
content,
|
||||
type: 0,
|
||||
sendTime: Date.now(),
|
||||
_tempId,
|
||||
_status: 'sending',
|
||||
}
|
||||
|
||||
// messages.value.push(localMessage)
|
||||
messages.value.push(localMessage)
|
||||
|
||||
// // 清空输入框
|
||||
// if (chatInputRef.value) {
|
||||
// chatInputRef.value.innerHTML = ''
|
||||
// }
|
||||
// 清空输入框
|
||||
if (chatInputRef.value) {
|
||||
chatInputRef.value.innerHTML = ''
|
||||
}
|
||||
|
||||
// const privateData: any = {
|
||||
// content,
|
||||
// type: 0,
|
||||
// tempId: _tempId,
|
||||
// }
|
||||
const sentData: any = {
|
||||
content,
|
||||
type: 0,
|
||||
tempId: _tempId,
|
||||
senderAvatar: userStore.user.avatar,
|
||||
senderName: userStore.user.name,
|
||||
}
|
||||
|
||||
// websocket.sendPrivate(props.friend.friendId, privateData)
|
||||
websocket.sendGroup(groupId, sentData)
|
||||
|
||||
// chatInputValue.value = ''
|
||||
// // 等待 DOM 更新再滚动到底部
|
||||
// await nextTick()
|
||||
// scrollToLatestMessage()
|
||||
chatInputValue.value = ''
|
||||
// 等待 DOM 更新再滚动到底部
|
||||
await nextTick()
|
||||
scrollToLatestMessage()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ImFriend, ImGroup } from '@/types/im'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { queryFriendApi, queryGroupApi, updateGroupUnreadApi, updatePrivateUnreadApi } from '@/request/api/im'
|
||||
import { getFriendProfileApi, getGroupProfileApi, queryFriendApi, queryGroupApi, updateGroupUnreadApi, updatePrivateUnreadApi } from '@/request/api/im'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useWebSocket } from '@/store/websocket'
|
||||
import { MessageType } from '@/types/im'
|
||||
@@ -78,7 +78,7 @@ onMounted(() => {
|
||||
loadGroups()
|
||||
loadFriends()
|
||||
|
||||
websocket.onMessage((msg) => {
|
||||
websocket.onMessage(async (msg) => {
|
||||
const { type, from, data } = msg
|
||||
|
||||
// 更新在线状态
|
||||
@@ -94,55 +94,164 @@ onMounted(() => {
|
||||
else if (type === MessageType.OFFLINE) {
|
||||
updateOnlineStatus(false)
|
||||
}
|
||||
else if (type === MessageType.FRIEND_UPDATE) {
|
||||
const updatedFriend = data as ImFriend
|
||||
|
||||
// 检查是否已经存在
|
||||
const exists = friendState.value.list.some(
|
||||
item => item.friendId === updatedFriend.friendId,
|
||||
)
|
||||
|
||||
// 如果不存在,放到第一个
|
||||
if (!exists) {
|
||||
friendState.value.list.unshift(updatedFriend)
|
||||
friendState.value.total += 1
|
||||
}
|
||||
}
|
||||
else if (type === MessageType.PRIVATE) {
|
||||
const chatFriendId = currentFriendChat.value?.friendId
|
||||
const { senderId, receiverId, type: contentType, content, sendTime } = data
|
||||
|
||||
// 遍历好友列表更新最近消息和未读
|
||||
friendState.value.list.forEach((item) => {
|
||||
if (item.friendId === senderId || item.friendId === receiverId) {
|
||||
// 更新最近消息
|
||||
item.lastMessageTime = sendTime
|
||||
item.lastMessageContent = content
|
||||
item.lastMessageType = contentType
|
||||
// 私聊中,对方一定是 sender 或 receiver
|
||||
const targetFriendId = senderId === userId ? receiverId : senderId
|
||||
|
||||
// 如果是别人发的消息并且不是当前聊天好友,增加未读
|
||||
if (userId !== senderId && senderId !== chatFriendId && item.friendId === senderId) {
|
||||
item.unreadCount = (item.unreadCount || 0) + 1
|
||||
// 先找本地好友
|
||||
const friend = friendState.value.list.find(
|
||||
item => item.friendId === targetFriendId,
|
||||
)
|
||||
|
||||
// 如果本地不存在,按需拉取
|
||||
if (!friend) {
|
||||
try {
|
||||
const res = await getFriendProfileApi(targetFriendId)
|
||||
|
||||
const newFriend: ImFriend = {
|
||||
friendId: res.data.friendId,
|
||||
friendAvatar: res.data.friendAvatar,
|
||||
friendName: res.data.friendName,
|
||||
unreadCount: res.data.unreadCount,
|
||||
|
||||
lastMessageTime: sendTime,
|
||||
lastMessageContent: content,
|
||||
lastMessageType: contentType,
|
||||
|
||||
isOnline: true,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// 更新当前聊天对象最近消息
|
||||
if (currentFriendChat.value && (senderId === chatFriendId || receiverId === chatFriendId)) {
|
||||
friendState.value.list.unshift(newFriend)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('getFriendProfileApi failed', e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!friend) {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新最近消息
|
||||
friend.lastMessageTime = sendTime
|
||||
friend.lastMessageContent = content
|
||||
friend.lastMessageType = contentType
|
||||
|
||||
// 未读数处理
|
||||
if (userId !== senderId && targetFriendId !== chatFriendId) {
|
||||
friend.unreadCount = (friend.unreadCount || 0) + 1
|
||||
}
|
||||
|
||||
// 当前聊天窗口
|
||||
if (currentFriendChat.value && chatFriendId === targetFriendId) {
|
||||
currentFriendChat.value.lastMessageTime = sendTime
|
||||
currentFriendChat.value.lastMessageContent = content
|
||||
currentFriendChat.value.lastMessageType = contentType
|
||||
|
||||
// 重置未读数量,如果是别人发的消息且当前聊天窗口打开
|
||||
if (userId !== senderId) {
|
||||
currentFriendChat.value.unreadCount = 0
|
||||
// 调用接口同步重置
|
||||
updatePrivateUnreadApi({
|
||||
userId,
|
||||
friendId: chatFriendId!,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 永远置顶
|
||||
const index = friendState.value.list.findIndex(
|
||||
item => item.friendId === targetFriendId,
|
||||
)
|
||||
if (index > -1) {
|
||||
const [f] = friendState.value.list.splice(index, 1)
|
||||
friendState.value.list.unshift(f)
|
||||
}
|
||||
}
|
||||
else if (type === MessageType.GROUP) {
|
||||
const {
|
||||
groupId,
|
||||
senderId,
|
||||
senderName,
|
||||
type: contentType,
|
||||
content,
|
||||
sendTime,
|
||||
} = data
|
||||
|
||||
const currentGroupId = currentGroupChat.value?.groupId
|
||||
|
||||
// 1️⃣ 查找本地群
|
||||
const group = groupState.value.list.find(
|
||||
item => item.groupId === groupId,
|
||||
)
|
||||
|
||||
// 2️⃣ 本地没有 → 懒加载群 Profile
|
||||
if (!group) {
|
||||
try {
|
||||
const res = await getGroupProfileApi(groupId)
|
||||
|
||||
const newGroup: ImGroup = {
|
||||
groupId: res.data.groupId,
|
||||
groupAvatar: res.data.groupAvatar,
|
||||
groupName: res.data.groupName,
|
||||
unreadCount: res.data.unreadCount,
|
||||
|
||||
lastMessageTime: sendTime,
|
||||
lastMessageContent: content,
|
||||
lastMessageType: contentType,
|
||||
|
||||
lastMessageSenderName: senderName,
|
||||
}
|
||||
|
||||
groupState.value.list.unshift(newGroup)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('getGroupProfileApi failed', e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!group) {
|
||||
return
|
||||
}
|
||||
// 3️⃣ 更新最近消息
|
||||
group.lastMessageTime = sendTime
|
||||
group.lastMessageContent = content
|
||||
group.lastMessageType = contentType
|
||||
group.lastMessageSenderName = senderName
|
||||
|
||||
// 4️⃣ 未读处理(不是自己 & 当前群未打开)
|
||||
if (senderId !== userId && groupId !== currentGroupId) {
|
||||
group.unreadCount = (group.unreadCount || 0) + 1
|
||||
}
|
||||
|
||||
// 5️⃣ 当前群窗口处理
|
||||
if (currentGroupChat.value && currentGroupId === groupId) {
|
||||
currentGroupChat.value.lastMessageTime = sendTime
|
||||
currentGroupChat.value.lastMessageContent = content
|
||||
currentGroupChat.value.lastMessageType = contentType
|
||||
currentGroupChat.value.lastMessageSenderName = senderName
|
||||
|
||||
if (senderId !== userId) {
|
||||
currentGroupChat.value.unreadCount = 0
|
||||
|
||||
updateGroupUnreadApi({
|
||||
userId,
|
||||
groupId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 6️⃣ 永远置顶
|
||||
const index = groupState.value.list.findIndex(
|
||||
item => item.groupId === groupId,
|
||||
)
|
||||
if (index > -1) {
|
||||
const [g] = groupState.value.list.splice(index, 1)
|
||||
groupState.value.list.unshift(g)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -342,7 +451,7 @@ function onGroupChat(group: ImGroup) {
|
||||
</div>
|
||||
<div class="im__list-body">
|
||||
<div
|
||||
v-for="item in friendState.list" :key="item.id" class="im__chat-item"
|
||||
v-for="item in friendState.list" :key="item.friendId" class="im__chat-item"
|
||||
:class="{
|
||||
'im__chat-item--selected':
|
||||
isPrivateChat && item.friendId === currentFriendChat?.friendId,
|
||||
@@ -411,8 +520,11 @@ function onGroupChat(group: ImGroup) {
|
||||
</div>
|
||||
|
||||
<div class="im__chat-item-message">
|
||||
<div v-if="item.lastMessageId" class="im__chat-item-last-message">
|
||||
{{ item.lastMessageSenderName }}:{{ item.lastMessageContent }}
|
||||
<div class="im__chat-item-last-message">
|
||||
<template v-if="item.lastMessageSenderName">
|
||||
{{ item.lastMessageSenderName }}:
|
||||
</template>
|
||||
{{ item.lastMessageContent }}
|
||||
</div>
|
||||
<el-badge :value="item.unreadCount || 0" :show-zero="false" :max="99" class="im__chat-item-unread-count" />
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user