✨ feat(im): 优化 MessagePage.vue 页面,对齐微信交互
parent
e1b52be8ea
commit
1a0c11f685
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue