diff --git a/components.d.ts b/components.d.ts
index fca6a51..4acaedd 100644
--- a/components.d.ts
+++ b/components.d.ts
@@ -16,6 +16,7 @@ declare module 'vue' {
AppNavBar: typeof import('./src/components/common/AppNavBar.vue')['default']
AppTabBar: typeof import('./src/components/common/AppTabBar.vue')['default']
CallWindow: typeof import('./src/components/call/CallWindow.vue')['default']
+ EmojiPicker: typeof import('./src/components/chat/EmojiPicker.vue')['default']
GlobalCallProvider: typeof import('./src/components/call/GlobalCallProvider.vue')['default']
GroupCallBanner: typeof import('./src/components/call/GroupCallBanner.vue')['default']
GroupCallIncoming: typeof import('./src/components/call/GroupCallIncoming.vue')['default']
diff --git a/src/components/chat/EmojiPicker.vue b/src/components/chat/EmojiPicker.vue
new file mode 100644
index 0000000..358220a
--- /dev/null
+++ b/src/components/chat/EmojiPicker.vue
@@ -0,0 +1,247 @@
+
+
+
+
+
+ {{ cat.icon }}
+
+
+
+
+
+
+
+ {{ emoji }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/pages/chat/index.vue b/src/pages/chat/index.vue
index 1455ed1..5f4d3ab 100644
--- a/src/pages/chat/index.vue
+++ b/src/pages/chat/index.vue
@@ -197,6 +197,13 @@
@close="showMentionPicker = false"
/>
+
+
+
-
+
@@ -358,6 +365,7 @@ import { useTheme } from '@/composables/useTheme'
import { useWebRTC } from '@/composables/useWebRTC'
import AppAvatar from '@/components/common/AppAvatar.vue'
import MentionPicker from '@/components/chat/MentionPicker.vue'
+import EmojiPicker from '@/components/chat/EmojiPicker.vue'
import type { MentionUser } from '@/components/chat/MentionPicker.vue'
import * as roomApi from '@/api/modules/room'
import type { ChatMessage, Attachment, GroupMember } from '@/types/api'
@@ -420,8 +428,24 @@ function getMediaTypeClass(type: number) { if (type === 1) return 'media-bubble
function getMediaUrl(msg: ChatMessage): string { let url = ''; if (typeof msg.extra === 'string') { try { const extra = JSON.parse(msg.extra); url = extra.url || msg.content } catch { url = msg.content } } else { url = msg.extra?.url || msg.content } return resolveImageUrl(url) }
function getFileName(msg: ChatMessage): string { if (typeof msg.extra === 'string') { try { const extra = JSON.parse(msg.extra); return extra.name || '未知文件' } catch { return '未知文件' } } return msg.extra?.name || '未知文件' }
function getFileSize(msg: ChatMessage): number { if (typeof msg.extra === 'string') { try { const extra = JSON.parse(msg.extra); return extra.size || 0 } catch { return 0 } } return msg.extra?.size || 0 }
-function getMessageSenderAvatar(msg: ChatMessage): string { return targetUser.value?.avatar || '' }
-function getMessageSenderName(msg: ChatMessage): string { return targetUser.value?.name || chatName.value || '未知' }
+function getMessageSenderAvatar(msg: ChatMessage): string {
+ // 群聊时从群成员列表获取头像
+ if (isGroupChat.value && msg.sender_user_id) {
+ const member = groupMembers.value.find(m => m.user_id === msg.sender_user_id)
+ if (member?.user?.avatar) return member.user.avatar
+ }
+ // 单聊时使用对方头像
+ return targetUser.value?.avatar || ''
+}
+function getMessageSenderName(msg: ChatMessage): string {
+ // 群聊时从群成员列表获取名称
+ if (isGroupChat.value && msg.sender_user_id) {
+ const member = groupMembers.value.find(m => m.user_id === msg.sender_user_id)
+ if (member) return member.nickname || member.user?.name || '群成员'
+ }
+ // 单聊时使用对方名称
+ return targetUser.value?.name || chatName.value || '未知'
+}
function onAvatarClick(msg: ChatMessage) { const userId = msg.sender_user_id; if (!userId) return; uni.navigateTo({ url: `/pages/contact/detail?userId=${userId}` }) }
function handleMessageLongPress(msg: ChatMessage) { selectedMessage.value = msg; showMsgActions.value = true }
async function onMsgActionSelect(action: { value: string }) { if (!selectedMessage.value) return; const msg = selectedMessage.value; showMsgActions.value = false; switch (action.value) { case 'copy': copyMessage(msg); break; case 'recall': await recallMessage(msg); break; case 'delete': deleteMessage(msg); break } }
@@ -431,6 +455,7 @@ function deleteMessage(msg: ChatMessage) { const index = messages.value.findInde
function onInputChange(e: any) { const value = e.detail?.value || inputText.value; if (value.endsWith('@') && isGroupChat.value) { showMentionPicker.value = true } }
function triggerMention() { inputText.value += '@'; showMentionPicker.value = true }
function handleMentionSelect(user: MentionUser) { if (inputText.value.endsWith('@')) { inputText.value = inputText.value.slice(0, -1) + `@${user.name} ` } else { inputText.value += `@${user.name} ` } showMentionPicker.value = false }
+function handleEmojiSelect(emoji: string) { inputText.value += emoji }
function previewImage(msg: ChatMessage) { const url = getMediaUrl(msg); if (url) { uni.previewImage({ urls: [url], current: url }) } }
// ========== 语音录制处理 ==========
@@ -624,20 +649,20 @@ async function sendVoiceMessage(filePath: string, duration: number) { toast.load
.end-text { color: var(--text-tertiary); font-size: 24rpx; }
}
-// 消息项容器
+// 消息项容器 - 与设计稿一致
.message-item {
- margin-bottom: 40rpx;
+ margin-bottom: 48rpx;
.timestamp {
display: flex;
justify-content: center;
- margin-bottom: 30rpx;
+ margin-bottom: 32rpx;
text {
background: rgba(0, 0, 0, 0.04);
- padding: 6rpx 18rpx;
+ padding: 8rpx 20rpx;
border-radius: 20rpx;
- font-size: 22rpx;
+ font-size: 24rpx;
color: var(--text-tertiary);
.theme-dark & { background: rgba(255, 255, 255, 0.08); }
@@ -646,44 +671,44 @@ async function sendVoiceMessage(filePath: string, duration: number) { toast.load
.system-notice {
text-align: center;
- margin: 20rpx 0;
+ margin: 24rpx 0;
text { font-size: 24rpx; color: var(--text-tertiary); }
}
}
-// 核心布局
+// 核心布局 - 与设计稿一致
.bubble-group {
display: flex;
align-items: flex-start;
- gap: 16rpx;
+ gap: 24rpx;
&.is-me {
- justify-content: flex-end;
+ flex-direction: row-reverse;
}
}
-// 头像容器
+// 头像容器 - 与设计稿一致(40px=80rpx)
.avatar-box {
width: 80rpx;
height: 80rpx;
flex-shrink: 0;
- &.left { order: 1; }
- &.right { order: 3; }
+ :deep(.app-avatar) {
+ border-radius: 24rpx;
+ }
}
-// 内容容器
+// 内容容器 - 与设计稿一致
.content-box {
- order: 2;
max-width: 75%;
display: flex;
flex-direction: column;
.sender-label {
- font-size: 22rpx;
+ font-size: 20rpx;
color: var(--text-tertiary);
margin-bottom: 8rpx;
- margin-left: 12rpx;
+ margin-left: 4rpx;
}
.is-me & {
@@ -698,16 +723,17 @@ async function sendVoiceMessage(filePath: string, duration: number) { toast.load
padding: 24rpx 28rpx;
border-radius: 32rpx;
font-size: 30rpx;
- line-height: 1.6;
+ line-height: 1.5;
position: relative;
word-break: break-all;
+ max-width: 100%;
// 对方气泡 - 白色背景
&.bubble-other {
background: var(--bubble-other-bg);
color: var(--text-msg-other);
border-bottom-left-radius: 8rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+ box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
.theme-dark & {
border: 1rpx solid var(--bubble-other-border);
@@ -732,7 +758,7 @@ async function sendVoiceMessage(filePath: string, duration: number) { toast.load
padding: 24rpx 28rpx;
background: var(--bubble-other-bg) !important;
border-radius: 32rpx;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05) !important;
+ box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05) !important;
.theme-dark & {
border: 1rpx solid var(--bubble-other-border) !important;
@@ -992,7 +1018,7 @@ async function sendVoiceMessage(filePath: string, duration: number) { toast.load
bottom: 0;
left: 0;
right: 0;
- z-index: 99;
+ z-index: 200;
background: var(--bg-card);
border-top: 1rpx solid var(--border-subtle);
padding: 24rpx 32rpx;
@@ -1039,7 +1065,7 @@ async function sendVoiceMessage(filePath: string, duration: number) { toast.load
flex: 1;
min-height: 88rpx;
background: var(--input-bg);
- border-radius: 40rpx;
+ border-radius: 32rpx;
display: flex;
align-items: center;
padding: 20rpx 28rpx;
@@ -1052,9 +1078,9 @@ async function sendVoiceMessage(filePath: string, duration: number) { toast.load
.text-input {
width: 100%;
- min-height: 48rpx;
+ min-height: 40rpx;
max-height: 200rpx;
- font-size: 30rpx;
+ font-size: 28rpx;
line-height: 1.5;
color: var(--text-main);
background: transparent;
diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue
index f687a2b..37f0a08 100644
--- a/src/pages/index/index.vue
+++ b/src/pages/index/index.vue
@@ -87,15 +87,12 @@
>
-
-
- {{ (item.name || '?').charAt(0) }}
-
@@ -185,6 +182,7 @@ import * as conversationApi from '@/api/modules/conversation'
import AppTabBar from '@/components/common/AppTabBar.vue'
import AppDrawer from '@/components/common/AppDrawer.vue'
import PlusMenu from '@/components/common/PlusMenu.vue'
+import AppAvatar from '@/components/common/AppAvatar.vue'
import type { Conversation } from '@/types/conversation'
const authStore = useAuthStore()