From 0c7d1f0df6a4c504e4aa11d44b05a61261622d6a Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 30 Apr 2026 14:07:03 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(im):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=80=9A=E8=AE=AF=E5=BD=95=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../im/home/components/user/UserInfo.vue | 41 ++-- .../im/home/pages/contact/FriendList.vue | 120 ++++++++++ .../im/home/pages/contact/GroupDetail.vue | 91 ++++++++ src/views/im/home/pages/contact/GroupList.vue | 62 ++++++ src/views/im/home/pages/contact/index.vue | 206 ++++++++++++++++++ .../conversation/ConversationGroupSide.vue | 87 +++----- .../conversation/ConversationItem.vue | 6 +- .../conversation/ConversationPrivateSide.vue | 27 ++- .../components/input/MessageInput.vue | 69 +++--- .../components/message/MessageHistory.vue | 5 - .../components/message/MessageItem.vue | 9 +- 11 files changed, 567 insertions(+), 156 deletions(-) create mode 100644 src/views/im/home/pages/contact/FriendList.vue create mode 100644 src/views/im/home/pages/contact/GroupDetail.vue create mode 100644 src/views/im/home/pages/contact/GroupList.vue create mode 100644 src/views/im/home/pages/contact/index.vue diff --git a/src/views/im/home/components/user/UserInfo.vue b/src/views/im/home/components/user/UserInfo.vue index 9ff103734..662733c24 100644 --- a/src/views/im/home/components/user/UserInfo.vue +++ b/src/views/im/home/components/user/UserInfo.vue @@ -300,15 +300,9 @@ async function saveRemark() { if (next === (props.displayName || '')) { return } - try { - await friendStore.setDisplayName(userId, next) - message.success('已更新备注') - emit('saved', next) - } catch (e) { - // TODO @AI:这里也是,需要有个 userId 之类的上下文 - console.error('[IM] 更新备注失败', e) - message.error('更新备注失败') - } + await friendStore.setDisplayName(userId, next) + message.success('已更新备注') + emit('saved', next) } function cancelEditRemark() { @@ -328,16 +322,11 @@ async function handleAddFriend() { if (!props.user?.id) { return } - try { - await friendStore.addFriend(props.user.id, { - nickname: props.user.nickname, - avatar: props.user.avatar - }) - message.success('已添加好友') - } catch (e: any) { - console.error('[IM] 添加好友失败', e) - message.error(e?.message || '添加好友失败') - } + await friendStore.addFriend(props.user.id, { + nickname: props.user.nickname, + avatar: props.user.avatar + }) + message.success('已添加好友') } /** 删除联系人:confirm → friendStore.deleteFriend(内部级联清会话)→ 通知父级关浮层 / 清选中 */ @@ -346,17 +335,15 @@ async function handleDeleteFriend() { return } const target = props.user + // 二次确认(用户点取消时 message.confirm 抛 reject,吃掉直接 return) try { await message.confirm(`确定删除好友「${target.nickname || ''}」吗?`, '删除联系人') - await friendStore.deleteFriend(target.id) - message.success('已删除好友') - emit('deleted', target) - } catch (e) { - if (e !== 'cancel' && e !== 'close') { - console.error('[IM] 删除好友失败', e) - message.error('删除好友失败') - } + } catch { + return } + await friendStore.deleteFriend(target.id) + message.success('已删除好友') + emit('deleted', target) } /** 占位提示:语音 / 视频聊天能力尚未接入,先以"开发中"友好提示 */ diff --git a/src/views/im/home/pages/contact/FriendList.vue b/src/views/im/home/pages/contact/FriendList.vue new file mode 100644 index 000000000..a5e418076 --- /dev/null +++ b/src/views/im/home/pages/contact/FriendList.vue @@ -0,0 +1,120 @@ + + + diff --git a/src/views/im/home/pages/contact/GroupDetail.vue b/src/views/im/home/pages/contact/GroupDetail.vue new file mode 100644 index 000000000..ddc12e9d5 --- /dev/null +++ b/src/views/im/home/pages/contact/GroupDetail.vue @@ -0,0 +1,91 @@ + + + diff --git a/src/views/im/home/pages/contact/GroupList.vue b/src/views/im/home/pages/contact/GroupList.vue new file mode 100644 index 000000000..5f7fda00b --- /dev/null +++ b/src/views/im/home/pages/contact/GroupList.vue @@ -0,0 +1,62 @@ + + + diff --git a/src/views/im/home/pages/contact/index.vue b/src/views/im/home/pages/contact/index.vue new file mode 100644 index 000000000..840e9daf9 --- /dev/null +++ b/src/views/im/home/pages/contact/index.vue @@ -0,0 +1,206 @@ + + + diff --git a/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue b/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue index 15c530b45..e4a3417ef 100644 --- a/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue +++ b/src/views/im/home/pages/conversation/components/conversation/ConversationGroupSide.vue @@ -431,15 +431,10 @@ async function saveName() { if (!props.group) { return } - try { - await updateGroup({ id: props.group.id, name: editName.value }) - namePopoverVisible.value = false - message.success('保存成功') - emit('reload') - } catch (error) { - console.error('[IM ConversationGroupSide] 保存群名失败', error) - message.error('保存失败') - } + await updateGroup({ id: props.group.id, name: editName.value }) + namePopoverVisible.value = false + message.success('保存成功') + emit('reload') } /** 群主:保存群公告 */ @@ -447,15 +442,10 @@ async function saveNotice() { if (!props.group) { return } - try { - await updateGroup({ id: props.group.id, notice: editNotice.value }) - noticePopoverVisible.value = false - message.success('保存成功') - emit('reload') - } catch (error) { - console.error('[IM ConversationGroupSide] 保存群公告失败', error) - message.error('保存失败') - } + await updateGroup({ id: props.group.id, notice: editNotice.value }) + noticePopoverVisible.value = false + message.success('保存成功') + emit('reload') } /** 备注:仅本地 localStorage 落盘(后端无字段;多端不同步是已知限制) */ @@ -478,18 +468,13 @@ async function saveRemark() { if (!props.group) { return } - try { - await updateGroupMember({ - groupId: props.group.id, - displayUserName: editRemark.value - }) - remarkPopoverVisible.value = false - message.success('保存成功') - emit('reload') - } catch (error) { - console.error('[IM ConversationGroupSide] 保存本群昵称失败', error) - message.error('保存失败') - } + await updateGroupMember({ + groupId: props.group.id, + displayUserName: editRemark.value + }) + remarkPopoverVisible.value = false + message.success('保存成功') + emit('reload') } /** @@ -524,26 +509,19 @@ async function handleQuit() { if (!props.group) { return } - // 1. 二次确认(用户点取消时 message.confirm 抛 reject,吃掉直接 return) + // 二次确认(用户点取消时 message.confirm 抛 reject,吃掉直接 return) try { await message.confirm('退出群聊后将不再接收群里的消息,确认退出吗?', '确认退出') } catch { return } const groupId = props.group.id - try { - // 2. 调后端 /im/group/quit - await quitGroup(groupId) - // 3. 同步清本地:会话列表 + 群 store;不清的话冷启动前还会残留这条群 / 会话 - conversationStore.removeConversation(ImConversationType.GROUP, groupId) - groupStore.removeGroup(groupId) - // 4. 关抽屉,让用户回到主面板 - message.success('已退出群聊') - visible.value = false - } catch (error) { - console.error('[IM ConversationGroupSide] 退出群聊失败', { groupId }, error) - message.error('退出群聊失败') - } + await quitGroup(groupId) + // 同步清本地:会话列表 + 群 store;不清的话冷启动前还会残留这条群 / 会话 + conversationStore.removeConversation(ImConversationType.GROUP, groupId) + groupStore.removeGroup(groupId) + message.success('已退出群聊') + visible.value = false } /** 移除群成员(仅群主入口)*/ @@ -551,20 +529,13 @@ async function handleRemoveComplete(members: GroupMemberFlag[]) { if (!props.group || members.length === 0) { return } - const groupId = props.group.id - try { - // 1. 一次性批量踢人:把选中成员 userId 数组传给后端,比循环调 N 次接口省往返 - await removeGroupMember({ - groupId, - memberUserIds: members.map((member) => member.userId) - }) - // 2. emit reload 让父组件重新拉群成员,UI 刷新看到被踢的人消失 - message.success(`已移除 ${members.length} 位成员`) - emit('reload') - } catch (error) { - console.error('[IM ConversationGroupSide] 移除群成员失败', { groupId }, error) - message.error('移除成员失败') - } + // 一次性批量踢人:把选中成员 userId 数组传给后端,比循环调 N 次接口省往返 + await removeGroupMember({ + groupId: props.group.id, + memberUserIds: members.map((member) => member.userId) + }) + message.success(`已移除 ${members.length} 位成员`) + emit('reload') } diff --git a/src/views/im/home/pages/conversation/components/conversation/ConversationItem.vue b/src/views/im/home/pages/conversation/components/conversation/ConversationItem.vue index 268b97c8d..639df2c2f 100644 --- a/src/views/im/home/pages/conversation/components/conversation/ConversationItem.vue +++ b/src/views/im/home/pages/conversation/components/conversation/ConversationItem.vue @@ -192,14 +192,12 @@ function handleMuted() { }) } -/** 删除会话:二次确认后软删(用户取消走 catch 静默) */ +/** 删除会话:二次确认后软删 */ async function handleDelete() { try { await message.confirm(`确定删除与「${props.conversation.name}」的会话吗?`, '删除会话') conversationStore.removeConversation(props.conversation.type, props.conversation.targetId) - } catch { - // 用户取消 - } + } catch {} } /** 右键菜单:置顶 / 免打扰 / 删除 */ diff --git a/src/views/im/home/pages/conversation/components/conversation/ConversationPrivateSide.vue b/src/views/im/home/pages/conversation/components/conversation/ConversationPrivateSide.vue index 07e8c0fae..1dff0cb73 100644 --- a/src/views/im/home/pages/conversation/components/conversation/ConversationPrivateSide.vue +++ b/src/views/im/home/pages/conversation/components/conversation/ConversationPrivateSide.vue @@ -172,26 +172,29 @@ async function handleSaveDisplayName() { if (!props.friend) { return } - try { - await friendStore.setDisplayName(props.friend.friendUserId, editDisplayName.value) - displayNamePopoverVisible.value = false - message.success('保存成功') - } catch (error) { - console.error('[IM ConversationPrivateSide] 保存好友备注失败', error) - message.error('保存失败') - } + await friendStore.setDisplayName(props.friend.friendUserId, editDisplayName.value) + displayNamePopoverVisible.value = false + message.success('保存成功') } -/** 切免打扰:双写 conversationStore(本地排序状态)+ friendStore(与后端同步) */ +/** + * 切免打扰:乐观双写 conversationStore + friendStore;后端失败回滚 conversation 状态,保持与 ConversationItem.handleMuted 一致 + */ function handleMutedChange(value: boolean | string | number) { if (!props.conversation) { return } const next = !!value - conversationStore.setMuted(props.conversation.type, props.conversation.targetId, next) - if (props.conversation.type === ImConversationType.PRIVATE) { - friendStore.setMuted(props.conversation.targetId, next) + const { type, targetId } = props.conversation + conversationStore.setMuted(type, targetId, next) + if (type !== ImConversationType.PRIVATE) { + return } + friendStore.setMuted(targetId, next).catch((e) => { + console.error('[IM ConversationPrivateSide] 切换免打扰失败', { targetId }, e) + message.error('切换免打扰失败') + conversationStore.setMuted(type, targetId, !next) + }) } /** 切置顶:纯本地 conversationStore 排序态(无后端字段) */ diff --git a/src/views/im/home/pages/conversation/components/input/MessageInput.vue b/src/views/im/home/pages/conversation/components/input/MessageInput.vue index 11b351fc5..123ab060e 100644 --- a/src/views/im/home/pages/conversation/components/input/MessageInput.vue +++ b/src/views/im/home/pages/conversation/components/input/MessageInput.vue @@ -117,7 +117,6 @@ diff --git a/src/views/im/home/pages/conversation/components/message/MessageHistory.vue b/src/views/im/home/pages/conversation/components/message/MessageHistory.vue index 4a6642e7b..c59d27edf 100644 --- a/src/views/im/home/pages/conversation/components/message/MessageHistory.vue +++ b/src/views/im/home/pages/conversation/components/message/MessageHistory.vue @@ -289,7 +289,6 @@