@@ -204,9 +193,10 @@
-
+
@@ -374,6 +364,34 @@ const isVoice = computed(() => props.message.type === ImMessageType.VOICE)
const isVideo = computed(() => props.message.type === ImMessageType.VIDEO)
const isCard = computed(() => props.message.type === ImMessageType.CARD)
+// ==================== 气泡配色 helper ====================
+// self / other 两侧 ::before 三角靠 message-bubble--self/other 类承载;本 helper 把 4 处 selfSend ternary
+// 集中到一处,新增 variant 直接在 switch 加 case;class 字面量保留以便 UnoCSS 静态扫描
+
+/** 文本 / 文件 / 语音气泡的整体 class(含 selfSend 配色 + ::before 三角的 side class) */
+function bubbleClass(variant: 'text' | 'file' | 'voice'): string[] {
+ const isSelf = props.message.selfSend
+ const side = isSelf ? 'message-bubble--self' : 'message-bubble--other'
+ switch (variant) {
+ case 'text':
+ return [
+ side,
+ isSelf
+ ? 'text-black bg-[#95ec69]'
+ : 'text-[var(--el-text-color-primary)] bg-[var(--el-fill-color-light)]'
+ ]
+ case 'file':
+ return [
+ side,
+ isSelf
+ ? 'bg-[#95ec69] border-[var(--el-border-color-lighter)]'
+ : 'bg-[var(--el-bg-color)] border-[var(--el-border-color-light)] hover:border-[#409eff]'
+ ]
+ case 'voice':
+ return [side, isSelf ? 'bg-[#95ec69]' : 'bg-[var(--el-fill-color-light)]']
+ }
+}
+
// TODO @AI:抽到 message.ts 里?作为一个工具方法?
/**
* 时间分隔线文案:
@@ -468,13 +486,13 @@ function handleCardClick(e: MouseEvent) {
if (!card?.userId) {
return
}
- uiStore.openUserInfoCard(
+ uiStore.openUserInfoCardAtEvent(
{
id: card.userId,
nickname: card.nickname,
avatar: card.avatar
},
- { x: e.clientX + 20, y: e.clientY },
+ e,
ImFriendAddSource.CARD
)
}
diff --git a/src/views/im/home/pages/conversation/index.vue b/src/views/im/home/pages/conversation/index.vue
index c1312ff99..ff52aca16 100644
--- a/src/views/im/home/pages/conversation/index.vue
+++ b/src/views/im/home/pages/conversation/index.vue
@@ -107,7 +107,7 @@ import { useFriendStore } from '../../store/friendStore'
import { useGroupStore } from '../../store/groupStore'
import { StorageKeys } from '../../../utils/storage'
import { ImConversationType } from '../../../utils/constants'
-import { getConversationKey } from '../../../utils/conversation'
+import { filterConversationsByKeyword, getConversationKey } from '../../../utils/conversation'
import { CommonStatusEnum } from '@/utils/constants'
import type { Conversation, Friend, FriendLite } from '../../types'
import ResizableAside from '../../components/ResizableAside.vue'
@@ -129,15 +129,9 @@ 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)
- )
-})
+const filteredConversations = computed(() =>
+ filterConversationsByKeyword(sortedConversations.value, keyword.value)
+)
// ==================== 置顶相关 ====================
diff --git a/src/views/im/home/store/uiStore.ts b/src/views/im/home/store/uiStore.ts
index 6556a9af3..54c2eeef5 100644
--- a/src/views/im/home/store/uiStore.ts
+++ b/src/views/im/home/store/uiStore.ts
@@ -41,6 +41,16 @@ export const useImUiStore = defineStore('imUiStore', () => {
userInfoCard.show = true
}
+ /** 鼠标点击位置 + 20px 横向偏移打开名片:避免名片直接覆盖触发元素,对齐头像 / 名片消息等点击交互的统一观感 */
+ function openUserInfoCardAtEvent(
+ user: User,
+ e: MouseEvent,
+ addSource: number = ImFriendAddSource.SEARCH,
+ addSourceExtra: string = ''
+ ) {
+ openUserInfoCard(user, { x: e.clientX + 20, y: e.clientY }, addSource, addSourceExtra)
+ }
+
/** 关闭用户名片 */
function closeUserInfoCard() {
userInfoCard.show = false
@@ -86,6 +96,7 @@ export const useImUiStore = defineStore('imUiStore', () => {
return {
userInfoCard,
openUserInfoCard,
+ openUserInfoCardAtEvent,
closeUserInfoCard,
contextMenu,
diff --git a/src/views/im/utils/conversation.ts b/src/views/im/utils/conversation.ts
index 74ef70432..8b61d19de 100644
--- a/src/views/im/utils/conversation.ts
+++ b/src/views/im/utils/conversation.ts
@@ -20,6 +20,18 @@ export function getConversationKey(conversation: { type: number; targetId: numbe
return `${conversation.type}-${conversation.targetId}`
}
+/** 按昵称模糊过滤会话列表:空 keyword 原样返回,命中走 toLowerCase 不区分大小写 */
+export function filterConversationsByKeyword(
+ list: T[],
+ keyword: string
+): T[] {
+ const trimmed = keyword.trim().toLowerCase()
+ if (!trimmed) {
+ return list
+ }
+ return list.filter((c) => (c.name || '').toLowerCase().includes(trimmed))
+}
+
/** 撤回提示文案:自己撤回固定文案,对方撤回带 sender 名(实时算 + fallbackName 兜底) */
export function buildRecallTip(
senderId: number,