diff --git a/node_api/src/modules/websocket/meeting.websocket.ts b/node_api/src/modules/websocket/meeting.websocket.ts index 60606fe..10dfb52 100644 --- a/node_api/src/modules/websocket/meeting.websocket.ts +++ b/node_api/src/modules/websocket/meeting.websocket.ts @@ -291,10 +291,10 @@ export class MeetingWebSocketGateway implements OnGatewayConnection, OnGatewayDi const validSocketIds: string[] = []; for (const result of validationResults) { if (result.isValid && result.sockets) { - this.server?.to(result.oldSocketId).emit('message', { - type: 'sev_device_conflict', - data: { fromRoomId: roomId, reason: '您已在其他设备进入相同入口', targetUid: shortUid }, - } satisfies SucceedMessage); + // this.server?.to(result.oldSocketId).emit('message', { + // type: 'sev_device_conflict', + // data: { fromRoomId: roomId, reason: '您已在其他设备进入相同入口', targetUid: shortUid }, + // } satisfies SucceedMessage); result.sockets.forEach((s) => s.disconnect(true)); validSocketIds.push(result.oldSocketId); } diff --git a/src/styles/main/button.scss b/src/styles/main/button.scss index 08d14f3..98e5605 100644 --- a/src/styles/main/button.scss +++ b/src/styles/main/button.scss @@ -1,12 +1,4 @@ -/** - * 统一按钮样式规范 - * 适用于项目中所有的按钮组件 - */ - -/* ======================================== - 会议室控制栏按钮 (.control-btn) - 用于底部控制栏的功能按钮(上课/下课、静音、视频等) - ======================================== */ +// 会议室控制栏按钮 (.control-btn) 用于底部控制栏的功能按钮(上课/下课、静音、视频等) .control-btn { display: flex; flex-direction: column; @@ -19,12 +11,11 @@ line-height: 1.5; color: var(--el-color-primary); cursor: pointer; + user-select: none; background: rgba(255, 255, 255, 0.1); border: 1px solid var(--el-color-primary-light-7); border-radius: 12px; transition: all 0.3s ease; - user-select: none; - -webkit-user-select: none; svg { width: 24px; @@ -90,8 +81,8 @@ &:hover:not(:disabled) { background: linear-gradient(135deg, #5568d3 0%, #63408a 100%); - transform: translateY(-3px); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); + transform: translateY(-3px); } &:active:not(:disabled) { @@ -100,12 +91,10 @@ } } -/* ======================================== - 视频用户控制按钮 (.control-icon-btn) - 用于视频卡片右上角的图标按钮(设为主讲、禁麦、禁视频、踢人等) - ======================================== */ +// 视频用户控制按钮 (.control-icon-btn) 用于视频卡片右上角的图标按钮(设为主讲、禁麦、禁视频、踢人等) .control-icon-btn { display: flex; + flex-shrink: 0; align-items: center; justify-content: center; width: 32px; @@ -113,13 +102,11 @@ padding: 0; color: #ffffff; cursor: pointer; + user-select: none; background: rgba(0, 0, 0, 0.6); border: none; border-radius: 8px; transition: all 0.3s ease; - user-select: none; - -webkit-user-select: none; - flex-shrink: 0; svg { width: 18px; @@ -177,24 +164,20 @@ } } -/* ======================================== - 窗口控制按钮 (.operating-button) - 用于窗口标题栏的控制按钮(最小化、最大化、关闭、返回等) - ======================================== */ +// 窗口控制按钮 (.operating-button) 用于窗口标题栏的控制按钮(最小化、最大化、关闭、返回等) .operating-button { display: flex; + flex-shrink: 0; align-items: center; justify-content: center; width: 20px; height: 20px; margin-right: 15px; cursor: pointer; + user-select: none; background-color: var(--button-bg); border-radius: 50%; transition: all 0.2s ease; - user-select: none; - -webkit-user-select: none; - flex-shrink: 0; svg { width: 12px; @@ -203,8 +186,8 @@ } &:hover { - transform: scale(1.15); filter: brightness(1.1); + transform: scale(1.15); } &:active { @@ -244,62 +227,53 @@ } } -/* ======================================== - Element Plus 按钮覆盖样式 - 用于登录页面等使用 el-button 的场景 - ======================================== */ -.el-button { - &.login-btn { - display: flex; - align-items: center; - justify-content: center; - width: 100%; - height: 40px; - font-size: 16px; - font-weight: 600; - background: linear-gradient(93deg, #74e8ff 0%, #306fff 100%); - border: transparent; - box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); - transition: all 0.3s ease; +// Element Plus 按钮覆盖样式 用于登录页面等使用 el-button 的场景 +.el-button.login-btn { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 40px; + font-size: 16px; + font-weight: 600; + background: linear-gradient(93deg, #74e8ff 0%, #306fff 100%); + border: transparent; + box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); + transition: all 0.3s ease; - &:hover:not(:disabled) { - background: linear-gradient(93deg, #5dd6ff 0%, #2560e8 100%); - transform: translateY(-2px); - box-shadow: 0 4px 16px 0 rgba(48, 111, 255, 0.3); - } + &:hover:not(:disabled) { + background: linear-gradient(93deg, #5dd6ff 0%, #2560e8 100%); + box-shadow: 0 4px 16px 0 rgba(48, 111, 255, 0.3); + transform: translateY(-2px); + } - &:active:not(:disabled) { - transform: translateY(0); - } + &:active:not(:disabled) { + transform: translateY(0); + } - &:disabled { - opacity: 0.6; - cursor: not-allowed; - } + &:disabled { + cursor: not-allowed; + opacity: 0.6; } } -/* ======================================== - 黑名单解除按钮 (.remove-btn) - 用于黑名单列表中的解除按钮 - ======================================== */ +// 黑名单解除按钮 (.remove-btn) 用于黑名单列表中的解除按钮 .remove-btn { padding: 4px 12px; font-size: 12px; line-height: 1.5; color: #ffffff; cursor: pointer; + user-select: none; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; border-radius: 6px; transition: all 0.3s ease; - user-select: none; - -webkit-user-select: none; &:hover { background: linear-gradient(135deg, #5568d3 0%, #63408a 100%); - transform: translateY(-1px); box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); + transform: translateY(-1px); } &:active { @@ -307,9 +281,7 @@ } } -/* ======================================== - 响应式适配 - ======================================== */ +// 响应式适配 @media (max-width: 768px) { .control-btn { min-width: 56px; diff --git a/src/views/meeting/meeting-room/meeting-room-monitor.vue b/src/views/meeting/meeting-room/meeting-room-monitor.vue index 6ecf769..e285b7a 100644 --- a/src/views/meeting/meeting-room/meeting-room-monitor.vue +++ b/src/views/meeting/meeting-room/meeting-room-monitor.vue @@ -174,7 +174,7 @@ const studentUsers = computed(() => { const users = meeting.remoteUsers.value as (IAgoraRTCRemoteUser & { userName?: string })[]; const filteredUsers = users.filter((u) => (teacherUid.value ? u.uid !== teacherUid.value : true)); - + // 按照 studentListForSort 中的顺序重新排列 if (studentListForSort.value.length > 0) { // 创建 shortId -> student 的映射 (用于排序) @@ -182,7 +182,7 @@ studentListForSort.value.forEach((student, index) => { studentOrderMap.set(student.shortId, index); }); - + // 按照 studentListForSort 的顺序排序,但只包含实际存在的用户 return [...filteredUsers].sort((a, b) => { const aIndex = studentOrderMap.get(Number(a.uid)) ?? Number.MAX_SAFE_INTEGER; @@ -190,7 +190,7 @@ return aIndex - bIndex; }); } - + return filteredUsers; }); @@ -365,7 +365,7 @@ if (classStatus.value === 'in_class') { console.log('[监控端] 课程已开始,初始化 Agora...'); await meeting.initAndJoin(roomData); - + // ✅ 获取学生信息列表 await fetchStudentList(); } else { @@ -428,7 +428,7 @@ shortUid.value = roomData.shortUid; teacherUid.value = roomData.teacherUid; await meeting.initAndJoin(roomData); - + // ✅ 获取学生信息列表 await fetchStudentList(); console.log('[监控端] Agora 初始化完成'); @@ -564,7 +564,7 @@ .loading-text { margin-top: 20px; font-size: 16px; - color: #999; + color: #999999; } } diff --git a/src/views/meeting/meeting-room/meeting-room-teacher.vue b/src/views/meeting/meeting-room/meeting-room-teacher.vue index d80574c..6786043 100644 --- a/src/views/meeting/meeting-room/meeting-room-teacher.vue +++ b/src/views/meeting/meeting-room/meeting-room-teacher.vue @@ -149,14 +149,28 @@ 离开 - -
暂无黑名单
- + +
+ + 暂无黑名单 +
+ +
    +
  • +
    + +
    +
    + {{ item.userName }} + ID: {{ item.shortUid }} +
    + +
  • +
+
@@ -168,6 +182,8 @@ import { playPromptTone } from '@/utils/prompt-tone'; import { useResizeObserver } from '@vueuse/core'; import IconMdiAccount from '~icons/mdi/account'; + import IconMdiAccountOff from '~icons/mdi/account-off'; + import IconMdiDelete from '~icons/mdi/delete'; import IconMdiExitToApp from '~icons/mdi/exit-to-app'; import IconMdiLoading from '~icons/mdi/loading'; import IconMdiMicrophone from '~icons/mdi/microphone'; @@ -1078,7 +1094,7 @@ .loading-text { margin-top: 20px; font-size: 16px; - color: #999; + color: #999999; } } @@ -1397,4 +1413,142 @@ // 使用统一按钮样式,移除重复的 .control-btn 定义 } + + // 黑名单抽屉样式 + :deep(.el-drawer__header) { + padding-bottom: 16px; + margin-bottom: 16px; + border-bottom: 1px solid #ebeef5; + + .el-drawer__title { + font-size: 18px; + font-weight: 600; + color: #303133; + } + } + + :deep(.el-drawer__body) { + padding: 0; + } + + .empty { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 60px 20px; + color: #909399; + + .empty-icon { + width: 80px; + height: 80px; + margin-bottom: 16px; + color: #c0c4cc; + } + + .empty-text { + font-size: 14px; + } + } + + .blacklist-scrollbar { + max-height: calc(100vh - 120px); + padding: 16px; + } + + .blacklist { + display: flex; + flex-direction: column; + gap: 12px; + padding: 0; + margin: 0; + list-style: none; + + .blacklist-item { + display: flex; + gap: 12px; + align-items: center; + padding: 12px 16px; + background: #ffffff; + border: 1px solid #e4e7ed; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); + transition: all 0.3s ease; + + &:hover { + background: #f5f7fa; + border-color: #667eea; + box-shadow: 0 4px 16px rgba(102, 126, 234, 0.2); + transform: translateX(4px); + } + + .user-avatar { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 50%; + + .avatar-icon { + width: 24px; + height: 24px; + color: #ffffff; + } + } + + .user-details { + display: flex; + flex: 1; + flex-direction: column; + gap: 4px; + min-width: 0; + + .user-name { + overflow: hidden; + font-size: 14px; + font-weight: 500; + color: #303133; + text-overflow: ellipsis; + white-space: nowrap; + } + + .user-id { + font-size: 12px; + color: #909399; + } + } + + .remove-btn { + display: flex; + gap: 4px; + align-items: center; + padding: 6px 12px; + font-size: 12px; + color: #ffffff; + white-space: nowrap; + cursor: pointer; + background: linear-gradient(135deg, #f56c6c 0%, #e74c3c 100%); + border: none; + border-radius: 6px; + transition: all 0.3s ease; + + .remove-icon { + width: 14px; + height: 14px; + } + + &:hover { + background: linear-gradient(135deg, #e74c3c 0%, #c0392b 100%); + box-shadow: 0 4px 12px rgba(245, 108, 108, 0.4); + transform: scale(1.05); + } + + &:active { + transform: scale(0.98); + } + } + } + } diff --git a/src/views/meeting/meeting-room/student-sort-dialog.vue b/src/views/meeting/meeting-room/student-sort-dialog.vue index 1838c49..f99c7cd 100644 --- a/src/views/meeting/meeting-room/student-sort-dialog.vue +++ b/src/views/meeting/meeting-room/student-sort-dialog.vue @@ -109,8 +109,8 @@