feat(im): 优化 MessagePage.vue 页面,对齐微信交互

im
YunaiV 2026-04-27 00:51:15 +08:00
parent e1b52be8ea
commit 1a0c11f685
2 changed files with 97 additions and 9 deletions

View File

@ -30,7 +30,7 @@ const props = withDefaults(
defaultWidth?: number //
minWidth?: number //
maxWidth?: number //
storageKey: string // localStorage key StorageKeys.asideWidth(page)
storageKey: string // localStorage key StorageKeys.asideWidth Tab
}>(),
{
defaultWidth: 260,

View File

@ -2,44 +2,132 @@
<!-- 消息 Tab左侧会话列表 + 右侧聊天面板 -->
<div class="flex flex-1 min-w-0 h-full">
<!-- 左侧会话列表可拖拽宽度 -->
<ResizableAside :default-width="260" :storage-key="StorageKeys.asideWidth('message')">
<!-- TODO @AI对齐微信的交互1搜索框2+ 发起群聊添加好友 -->
<ResizableAside :default-width="260" :storage-key="StorageKeys.asideWidth">
<!-- 顶部搜索框 + "+" 号下拉对齐微信 PC发起群聊 / 添加朋友 -->
<div
class="flex flex-shrink-0 items-center h-14 px-4 text-base font-medium text-[var(--el-text-color-primary)] border-b border-[var(--el-border-color-light)]"
class="flex flex-shrink-0 gap-2 items-center px-4 py-2 border-b border-[var(--el-border-color-lighter)]"
>
消息
<el-input v-model="keyword" placeholder="搜索" clearable class="flex-1">
<template #prefix>
<el-icon><Search /></el-icon>
</template>
</el-input>
<el-dropdown trigger="click" placement="bottom">
<el-button size="small" :icon="Plus" circle />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="createGroupVisible = true">
<Icon icon="ant-design:message-outlined" :size="16" />
<span>发起群聊</span>
</el-dropdown-item>
<el-dropdown-item @click="addFriendVisible = true">
<Icon icon="ant-design:user-add-outlined" :size="16" />
<span>添加朋友</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<!-- 会话列表主体 -->
<div class="flex-1 overflow-y-auto">
<ConversationItem
v-for="conversation in sortedConversations"
v-for="conversation in filteredConversations"
:key="`${conversation.type}-${conversation.targetId}`"
:conversation="conversation"
/>
<div
v-if="sortedConversations.length === 0"
v-if="filteredConversations.length === 0"
class="flex items-center justify-center py-10 text-sm text-[var(--el-text-color-secondary)]"
>
暂无会话
{{ keyword ? '没有满足条件的会话' : '暂无会话' }}
</div>
</div>
</ResizableAside>
<!-- 右侧聊天面板 -->
<ChatPanel />
<!-- 添加朋友 / 发起群聊弹窗 -->
<AddFriendDialog v-model="addFriendVisible" @added="handleFriendAdded" />
<CreateGroupDialog
v-model="createGroupVisible"
:friends="friends"
@created="handleGroupCreated"
/>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import { computed, ref } from 'vue'
import { Search, Plus } from '@element-plus/icons-vue'
import Icon from '@/components/Icon/src/Icon.vue'
import { useConversationStore } from '../../store/conversationStore'
import { useFriendStore } from '../../store/friendStore'
import { useGroupStore } from '../../store/groupStore'
import { StorageKeys } from '../../../utils/storage'
import { ImConversationType } from '../../../utils/constants'
import { CommonStatusEnum } from '@/utils/constants'
import type { Friend } from '../../types'
import type { FriendLite } from '../friend/components/FriendItem.vue'
import ResizableAside from '../../components/ResizableAside.vue'
import ConversationItem from './components/conversation/ConversationItem.vue'
import ChatPanel from './components/ChatPanel.vue'
import AddFriendDialog from '../friend/components/AddFriendDialog.vue'
import CreateGroupDialog from '../group/components/CreateGroupDialog.vue'
defineOptions({ name: 'ImMessagePage' })
const conversationStore = useConversationStore()
const friendStore = useFriendStore()
const groupStore = useGroupStore()
const keyword = ref('')
const addFriendVisible = ref(false)
const createGroupVisible = ref(false)
const sortedConversations = computed(() => conversationStore.getSortedConversations)
/** 顶部搜索框过滤会话:只按 name 模糊匹配,避免命中 lastContent 等次要字段干扰 */
const filteredConversations = computed(() => {
const keywordLower = keyword.value.trim().toLowerCase()
if (!keywordLower) {
return sortedConversations.value
}
return sortedConversations.value.filter((c) =>
(c.name || '').toLowerCase().includes(keywordLower)
)
})
/** CreateGroupDialog 需要全量好友列表来勾选成员,结构与 friend / group Tab 保持一致 */
const friends = computed<FriendLite[]>(() =>
friendStore.getActiveFriends.map((friend: Friend) => ({
id: friend.friendUserId,
nickname: friend.nickname,
avatar: friend.avatar,
deleted: friend.status === CommonStatusEnum.DISABLE
}))
)
/** 加好友成功后强制刷新好友列表,让群聊弹窗的勾选项也能看到新好友 */
async function handleFriendAdded() {
await friendStore.loadFriends(true)
}
/** 建群成功后刷新群列表,并直接打开新群会话(自动选中并渲染到右侧 ChatPanel */
async function handleGroupCreated(groupId: number) {
await groupStore.loadGroups(true)
const group = groupStore.getGroup(groupId)
if (!group) {
return
}
conversationStore.openConversation(
groupId,
ImConversationType.GROUP,
group.name,
group.avatar || '',
{ muted: !!group.muted }
)
}
</script>