From 5873cda8f5f0e175ba51f20e626077abbbde5e47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=E6=98=B5=E7=A7=B0=E9=87=8D=E8=A6=81=E5=90=97O?= Date: Sat, 14 Mar 2026 22:17:20 +0800 Subject: [PATCH] =?UTF-8?q?feat(meeting):=20=E4=BC=98=E5=8C=96=E4=BC=9A?= =?UTF-8?q?=E8=AE=AE=E5=AE=A4=E9=A1=B5=E9=9D=A2=E4=BD=93=E9=AA=8C=E5=B9=B6?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E9=9F=B3=E8=A7=86=E9=A2=91=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加房间加入加载状态提示界面 - 实现学生ID到姓名的映射显示功能 - 优化音视频订阅处理逻辑增强稳定性 - 统一教师端和监控端的加载状态管理 - 完善学生列表获取和排序机制 - 修复用户发布事件处理中的潜在错误 --- .../meeting-room/meeting-room-monitor.vue | 159 ++++++++++++++---- .../meeting-room/meeting-room-teacher.vue | 137 +++++++++++---- .../meeting/meeting-room/useAgoraMeeting.ts | 75 +++++---- 3 files changed, 274 insertions(+), 97 deletions(-) diff --git a/src/views/meeting/meeting-room/meeting-room-monitor.vue b/src/views/meeting/meeting-room/meeting-room-monitor.vue index 4a3289a..da5576d 100644 --- a/src/views/meeting/meeting-room/meeting-room-monitor.vue +++ b/src/views/meeting/meeting-room/meeting-room-monitor.vue @@ -15,7 +15,17 @@
- + +
+ + + +

正在加入房间...

+
+
@@ -30,7 +40,7 @@
- {{ u.userName || `用户 ${u.uid}` }} + {{ shortIdToNameMap.get(Number(u.uid)) || `用户 ${u.uid}` }}
@@ -43,12 +53,14 @@ import type { IAgoraRTCRemoteUser } from 'agora-rtc-sdk-ng'; import IconMdiWifi from '~icons/mdi/wifi'; import IconMdiVideo from '~icons/mdi/video'; + import IconMdiLoading from '~icons/mdi/loading'; + import { ElIcon, ElMessageBox, ElMessage as Toast } from 'element-plus'; import { computed, onMounted, onUnmounted, ref, watchEffect } from 'vue'; import { useRoute, useRouter } from 'vue-router'; - import { ElMessageBox, ElMessage as Toast } from 'element-plus'; - import type { OnJoinRoomData, ServerToClientEvents } from './types'; + import type { ServerToClientEvents } from './types'; import { useAgoraMeeting } from './useAgoraMeeting'; import { destroyMeetingSocket, useMeetingSocket, useWebSocket } from './websocket'; + import { getStudentDetailsAxios } from '@/api/meeting'; const route = useRoute(); const router = useRouter(); @@ -74,10 +86,8 @@ /** 老师短 UID */ const teacherUid = ref(0); - /** 监控端是否发布本地音视频(默认不发布) */ - const isPublishLocalTracks = ref(false); - /** 是否禁用本地音视频轨道 */ - const disableLocalTracks = computed(() => !isPublishLocalTracks.value); + /** 监控端永远禁用本地音视频轨道(只订阅不发布) */ + const disableLocalTracks = computed(() => true); const meeting = useAgoraMeeting( courseRoomId, @@ -115,6 +125,24 @@ const roomEndTimestamp = ref(null); /** 是否正在弹出续期窗口 */ const isRenewPrompting = ref(false); + /** 是否已加入房间 */ + const isRoomJoined = ref(false); + + /** 学生长 ID 到姓名的映射 */ + const studentNameMap = ref>(new Map()); + + /** 用于排序的学生列表 */ + const studentListForSort = ref>([]); + + /** 学生短 ID 到姓名的映射 (用于显示) */ + const shortIdToNameMap = computed(() => { + const map = new Map(); + // 从 studentListForSort 中构建 shortId -> name 的映射 + studentListForSort.value.forEach((student) => { + map.set(student.shortId, student.studentName); + }); + return map; + }); watchEffect(() => { if (roomEndTimestamp.value === null && endTimestamp.value) { @@ -257,32 +285,31 @@ selectedUserUid.value = packet.data.targetUid; break; } - case 'sev_student_order_changed': { - // 学生排序变更,根据短 ID 数组重新排序学生列表 - const newOrder = packet.data.studentShortIds; - if (Array.isArray(newOrder) && newOrder.length > 0) { - // 这里可以添加排序逻辑,或者让 Agora 自动处理 - console.log('[监控端] 学生排序已更新:', newOrder); - } - break; - } + // 学生排序变更事件 (类型定义待添加) + // case 'sev_student_order_changed': { + // const newOrder = packet.data.studentShortIds; + // if (Array.isArray(newOrder) && newOrder.length > 0) { + // console.log('[监控端] 学生排序已更新:', newOrder); + // } + // break; + // } case 'sev_class_started': { classStatus.value = 'in_class'; - meetingSocket - .joinRoom(socket) - .then(async (roomData: OnJoinRoomData) => { - roomId.value = roomData.roomId; - shortUid.value = roomData.shortUid; - teacherUid.value = roomData.teacherUid; - await meeting.initAndJoin(roomData); - }) - .catch((e: unknown) => { - if (e instanceof Error && e.message) { - Toast.error(e.message); - return; - } - Toast.error('加入失败'); - }); + console.log('[监控端] 收到上课消息'); + + // 如果已经在上课状态,忽略 + if (meeting.client.value) { + console.log('[监控端] Agora 已初始化,仅更新学生信息'); + fetchStudentList(); + break; + } + + // 第一次上课,初始化 Agora + console.log('[监控端] 开始初始化 Agora...'); + handleClassStarted().catch((error) => { + console.error('[监控端] 初始化 Agora 失败:', error); + Toast.error('加入会议失败'); + }); break; } case 'sev_class_ended': { @@ -313,8 +340,16 @@ shortUid.value = roomData.shortUid; teacherUid.value = roomData.teacherUid; classStatus.value = roomData?.classStatus || 'not_started'; + // 标记已加入房间 + isRoomJoined.value = true; + if (classStatus.value === 'in_class') { + console.log('[监控端] 课程已开始,初始化 Agora...'); await meeting.initAndJoin(roomData); + // 获取学生信息列表 + await fetchStudentList(); + } else { + console.log('[监控端] 等待老师上课...'); } } catch (error) { console.error('加入监控端失败:', error); @@ -335,6 +370,51 @@ } catch {} destroyMeetingSocket(); }); + + /** + * 获取学生信息列表 + */ + async function fetchStudentList(): Promise { + if (!homeworkId.value || !roomId.value) { + return; + } + try { + const result = await getStudentDetailsAxios(homeworkId.value, roomId.value); + if (result.data && Array.isArray(result.data)) { + // 保存完整的学生列表 (包含 shortId) + studentListForSort.value = result.data; + + // 构建学生长 ID 到姓名的映射 + const longIdMap = new Map(); + result.data.forEach((student) => { + longIdMap.set(student.longId, student.studentName); + }); + studentNameMap.value = longIdMap; + + console.log('[监控端] 获取到学生信息:', result.data.length, '人'); + } + } catch (error) { + console.error('[监控端] 获取学生列表失败:', error); + } + } + + /** + * 处理上课事件 (第一次上课时初始化 Agora) + */ + async function handleClassStarted(): Promise { + try { + const roomData = await meetingSocket.joinRoom(socket); + roomId.value = roomData.roomId; + shortUid.value = roomData.shortUid; + teacherUid.value = roomData.teacherUid; + await meeting.initAndJoin(roomData); + await fetchStudentList(); + console.log('[监控端] Agora 初始化完成'); + } catch (error) { + console.error('[监控端] 初始化 Agora 失败:', error); + throw error; + } + }