feat(phase2e-2): 会议内聊天面板落地(Task 12)

- 新增 ChatPanel 组件:右侧抽屉式聊天 UI,支持发送/加载更早消息/未读清零
- store/meeting.js:扩展 chatHasMore/chatUnreadCount/chatHistoryLoaded 等状态与 loadMoreChats/markChatsRead 动作;WS 消息到达时按 id 去重
- room.vue:替换临时 toast 占位为 ChatPanel,绑定未读徽标、成员展示名映射;watch unread 自动在面板打开时清零
- 后端:SendChat WS 广播与 REST 响应补充 user_name/user_avatar,聊天面板无需额外查询即可展示昵称

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-04-22 15:16:17 +08:00
parent 59f824ad27
commit 1222bf551d
5 changed files with 605 additions and 17 deletions

View File

@@ -92,6 +92,20 @@
@close="inviteVisible = false"
/>
<!-- 聊天面板 -->
<ChatPanel
:visible="chatVisible"
:messages="chatMessages"
:current-user-id="myUserId"
:display-name-map="displayNameMap"
:has-more="chatHasMore"
:loading-more="chatLoadingMore"
@close="closeChat"
@send="onChatSend"
@load-more="onChatLoadMore"
@request-initial-load="onChatRequestInitialLoad"
/>
<!-- 离会确认弹窗 -->
<view v-if="leaveDialogVisible" class="leave-mask" @click.self="leaveDialogVisible = false">
<view class="leave-modal">
@@ -132,17 +146,19 @@ import MeetingToolbar from '@/components/meeting/MeetingToolbar.vue'
import MemberPanel from '@/components/meeting/MemberPanel.vue'
import InviteDialog from '@/components/meeting/InviteDialog.vue'
import NetworkBadge from '@/components/meeting/NetworkBadge.vue'
import ChatPanel from '@/components/meeting/ChatPanel.vue'
const meetingStore = useMeetingStore()
const userStore = useUserStore()
const memberVisible = ref(false)
const inviteVisible = ref(false)
const chatVisible = ref(false)
const leaveDialogVisible = ref(false)
const audioLoading = ref(false)
const videoLoading = ref(false)
const chatLoadingMore = ref(false)
const networkLevel = ref(4) // Task 15 接入真实 getStats 后动态更新
const unreadChatCount = ref(0) // Task 14 接入 chat 后动态更新
const startedAt = ref(0)
const nowTs = ref(Date.now())
let timerHandle = null
@@ -181,6 +197,9 @@ const allowChat = computed(() => {
}
})
const invitePassword = computed(() => meetingStore.draftCreatePayload?.password || '')
const chatMessages = computed(() => meetingStore.chatMessages)
const chatHasMore = computed(() => meetingStore.chatHasMore)
const unreadChatCount = computed(() => meetingStore.chatUnreadCount)
const formattedCode = computed(() => {
const d = String(roomCode.value || '').replace(/\D/g, '')
@@ -358,8 +377,41 @@ const onCamToggle = async () => {
const openInvite = () => { inviteVisible.value = true }
const openMembers = () => { memberVisible.value = true }
const openChat = () => {
// Task 14 会议聊天入口;当前阶段仅提示
uni.showToast({ title: '聊天功能即将上线', icon: 'none' })
chatVisible.value = true
meetingStore.markChatsRead()
}
const closeChat = () => { chatVisible.value = false }
watch(unreadChatCount, (count) => {
if (count > 0 && chatVisible.value) {
meetingStore.markChatsRead()
}
})
const onChatSend = async (content) => {
try {
await meetingStore.sendChat(content)
} catch (err) {
uni.showToast({ title: err?.message || '发送失败', icon: 'none' })
throw err
}
}
const onChatLoadMore = async () => {
if (chatLoadingMore.value) return
chatLoadingMore.value = true
try {
await meetingStore.loadMoreChats(30)
} catch (err) {
uni.showToast({ title: err?.message || '加载失败', icon: 'none' })
} finally {
chatLoadingMore.value = false
}
}
const onChatRequestInitialLoad = async () => {
if (meetingStore.chatHistoryLoaded) return
await onChatLoadMore()
}
const onKickMember = async (uid) => {