Procházet zdrojové kódy

✨ feat(classroom): 新增学生IM聊天禁言功能

- 添加IM禁言和取消禁言功能,支持单独控制学生聊天权限
- 优化RTC静音流程,分离IM通知和实际RTC操作
- 完善学生管理面板,区分音频静音和IM禁言按钮
- 增强操作确认对话框,明确区分不同类型的静音操作
- 添加全体IM禁言和取消全体禁言功能

✨ refactor(classroom): 优化静音操作逻辑

- 重构toggleMuteMember函数,分离IM通知和RTC实际操作
- 完善muteStudent函数,增加RTC静音实现
- 优化学生管理按钮组件UI,使用flex-wrap确保按钮不溢出
yourname před 6 měsíci
rodič
revize
cfa367513d

+ 32 - 8
src/client/mobile/components/Classroom/TeacherStudentManagementButton.tsx

@@ -15,11 +15,11 @@ import {
 } from '@/client/components/ui/alert-dialog';
 
 export const TeacherStudentManagementButton: React.FC = () => {
-  const { students, toggleMuteMember, kickStudent } = useClassroomContext();
+  const { students, toggleMuteMember, kickStudent, muteStudentIM, unmuteStudentIM } = useClassroomContext();
   const [showPanel, setShowPanel] = useState(false);
   const [confirmDialog, setConfirmDialog] = useState<{
     open: boolean;
-    action: 'mute' | 'unmute' | 'kick' | null;
+    action: 'mute' | 'unmute' | 'kick' | 'mute_im' | 'unmute_im' | null;
     studentId: string;
     studentName: string;
   }>({
@@ -29,7 +29,7 @@ export const TeacherStudentManagementButton: React.FC = () => {
     studentName: '',
   });
 
-  const openConfirmDialog = (action: 'mute' | 'unmute' | 'kick', studentId: string, studentName: string) => {
+  const openConfirmDialog = (action: 'mute' | 'unmute' | 'kick' | 'mute_im' | 'unmute_im', studentId: string, studentName: string) => {
     setConfirmDialog({
       open: true,
       action,
@@ -51,6 +51,12 @@ export const TeacherStudentManagementButton: React.FC = () => {
       case 'kick':
         kickStudent(studentId);
         break;
+      case 'mute_im':
+        muteStudentIM(studentId);
+        break;
+      case 'unmute_im':
+        unmuteStudentIM(studentId);
+        break;
     }
     
     setConfirmDialog({
@@ -114,14 +120,14 @@ export const TeacherStudentManagementButton: React.FC = () => {
                   {students.map((student) => (
                     <div key={student.id} className="flex items-center justify-between p-2 border rounded text-sm">
                       <span>{student.name}</span>
-                      <div className="flex gap-1">
+                      <div className="flex gap-1 flex-wrap">
                         <Button
                          onClick={() => openConfirmDialog('mute', student.id, student.name)}
                          variant="outline"
                          size="sm"
                          className="text-xs bg-yellow-100 text-yellow-700 hover:bg-yellow-200 border-yellow-300"
                        >
-                         静音
+                         音频静音
                        </Button>
                        <Button
                          onClick={() => openConfirmDialog('unmute', student.id, student.name)}
@@ -129,7 +135,23 @@ export const TeacherStudentManagementButton: React.FC = () => {
                          size="sm"
                          className="text-xs bg-green-100 text-green-700 hover:bg-green-200 border-green-300"
                        >
-                         取消
+                         取消音频
+                       </Button>
+                       <Button
+                         onClick={() => openConfirmDialog('mute_im', student.id, student.name)}
+                         variant="outline"
+                         size="sm"
+                         className="text-xs bg-purple-100 text-purple-700 hover:bg-purple-200 border-purple-300"
+                       >
+                         IM禁言
+                       </Button>
+                       <Button
+                         onClick={() => openConfirmDialog('unmute_im', student.id, student.name)}
+                         variant="outline"
+                         size="sm"
+                         className="text-xs bg-blue-100 text-blue-700 hover:bg-blue-200 border-blue-300"
+                       >
+                         取消IM禁言
                        </Button>
                        <Button
                          onClick={() => openConfirmDialog('kick', student.id, student.name)}
@@ -154,8 +176,10 @@ export const TeacherStudentManagementButton: React.FC = () => {
           <AlertDialogHeader>
             <AlertDialogTitle>操作确认</AlertDialogTitle>
             <AlertDialogDescription>
-              {confirmDialog.action === 'mute' && `确定要静音 ${confirmDialog.studentName} 吗?`}
-              {confirmDialog.action === 'unmute' && `确定要取消静音 ${confirmDialog.studentName} 吗?`}
+              {confirmDialog.action === 'mute' && `确定要静音 ${confirmDialog.studentName} 吗?(RTC音频)`}
+              {confirmDialog.action === 'unmute' && `确定要取消静音 ${confirmDialog.studentName} 吗?(RTC音频)`}
+              {confirmDialog.action === 'mute_im' && `确定要禁言 ${confirmDialog.studentName} 吗?(IM聊天)`}
+              {confirmDialog.action === 'unmute_im' && `确定要取消禁言 ${confirmDialog.studentName} 吗?(IM聊天)`}
               {confirmDialog.action === 'kick' && `确定要移出 ${confirmDialog.studentName} 吗?`}
             </AlertDialogDescription>
           </AlertDialogHeader>

+ 77 - 1
src/client/mobile/components/Classroom/useClassroom.ts

@@ -707,6 +707,7 @@ export const useClassroom = ({ user }:{ user : User }) => {
     if (!imMessageManager.current || !classId || role !== Role.Teacher) return;
     
     try {
+      // 1. 发送IM消息通知静音状态变化
       await imMessageManager.current.sendGroupMessage({
         groupId: classId,
         data: JSON.stringify({
@@ -717,6 +718,12 @@ export const useClassroom = ({ user }:{ user : User }) => {
         type: 88890,
         level: HIGH,
       });
+      
+      // 2. 执行真正的RTC静音/取消静音操作
+      if (aliRtcEngine.current) {
+        aliRtcEngine.current.muteRemoteAudioPlaying(userId, mute);
+      }
+      
       showToast('info', mute ? `已静音用户 ${userId}` : `已取消静音用户 ${userId}`);
     } catch (err: any) {
       setErrorMessage(`操作失败: ${err.message}`);
@@ -930,6 +937,7 @@ export const useClassroom = ({ user }:{ user : User }) => {
     if (!imMessageManager.current || !classId || role !== Role.Teacher) return;
     
     try {
+      // 1. 发送IM消息通知学生被静音
       await imMessageManager.current.sendGroupMessage({
         groupId: classId,
         data: JSON.stringify({
@@ -940,6 +948,12 @@ export const useClassroom = ({ user }:{ user : User }) => {
         type: 88890,
         level: HIGH,
       });
+      
+      // 2. 执行真正的RTC静音操作
+      if (aliRtcEngine.current) {
+        aliRtcEngine.current.muteRemoteAudioPlaying(studentId, true);
+      }
+      
       showToast('info', `已静音学生 ${studentId}`);
     } catch (err: any) {
       setErrorMessage(`静音失败: ${err.message}`);
@@ -968,6 +982,64 @@ export const useClassroom = ({ user }:{ user : User }) => {
     }
   };
 
+  // IM禁言功能 - 禁止学生在聊天室发送消息
+  const muteStudentIM = async (studentId: string): Promise<void> => {
+    if (!imGroupManager.current || !classId || role !== Role.Teacher) return;
+    
+    try {
+      await imGroupManager.current.muteUser({
+        groupId: classId,
+        userList: [studentId],
+      });
+      showToast('info', `已禁言学生 ${studentId}(IM聊天)`);
+    } catch (err: any) {
+      setErrorMessage(`IM禁言失败: ${err.message}`);
+    }
+  };
+
+  // 取消IM禁言
+  const unmuteStudentIM = async (studentId: string): Promise<void> => {
+    if (!imGroupManager.current || !classId || role !== Role.Teacher) return;
+    
+    try {
+      await imGroupManager.current.cancelMuteUser({
+        groupId: classId,
+        userList: [studentId],
+      });
+      showToast('info', `已取消禁言学生 ${studentId}(IM聊天)`);
+    } catch (err: any) {
+      setErrorMessage(`取消IM禁言失败: ${err.message}`);
+    }
+  };
+
+  // 全体IM禁言
+  const muteAllIM = async (): Promise<void> => {
+    if (!imGroupManager.current || !classId || role !== Role.Teacher) return;
+    
+    try {
+      await imGroupManager.current.muteAll({
+        groupId: classId,
+      });
+      showToast('info', '已全体禁言(IM聊天)');
+    } catch (err: any) {
+      setErrorMessage(`全体IM禁言失败: ${err.message}`);
+    }
+  };
+
+  // 取消全体IM禁言
+  const unmuteAllIM = async (): Promise<void> => {
+    if (!imGroupManager.current || !classId || role !== Role.Teacher) return;
+    
+    try {
+      await imGroupManager.current.cancelMuteAll({
+        groupId: classId
+      });
+      showToast('info', '已取消全体禁言(IM聊天)');
+    } catch (err: any) {
+      setErrorMessage(`取消全体IM禁言失败: ${err.message}`);
+    }
+  };
+
   const answerHandUp = async (studentId: string): Promise<void> => {
     if (!imMessageManager.current || !classId || role !== Role.Teacher) return;
     
@@ -1064,7 +1136,11 @@ export const useClassroom = ({ user }:{ user : User }) => {
     answerHandUp,
     sendQuestion,
     muteStudent,
-    kickStudent
+    kickStudent,
+    muteStudentIM,
+    unmuteStudentIM,
+    muteAllIM,
+    unmuteAllIM
   };
 };