Răsfoiți Sursa

✨ feat(classroom): 添加学生在线和观看统计功能

- 引入在线人数(onlineCount)和观看人数(pvCount)统计数据
- 新增统计信息显示卡片,使用UsersIcon和EyeIcon图标
- 监听"memberdatachange"事件实时更新统计数据
- 初始化时获取群组统计信息并设置初始值
yourname 6 luni în urmă
părinte
comite
d10f2351e1

+ 39 - 25
src/client/mobile/components/Classroom/TeacherStudentManagementButton.tsx

@@ -1,6 +1,6 @@
 import React, { useState } from 'react';
 import { useClassroomContext } from './ClassroomProvider';
-import { UserGroupIcon, XMarkIcon } from '@heroicons/react/24/outline';
+import { UserGroupIcon, XMarkIcon, EyeIcon, UsersIcon } from '@heroicons/react/24/outline';
 import { Button } from '@/client/components/ui/button';
 import { Card, CardContent, CardHeader, CardTitle } from '@/client/components/ui/card';
 import {
@@ -17,7 +17,7 @@ import { IMToggleMuteButton } from './IMToggleMuteButton';
 import { RTCToggleMuteButton } from './RTCToggleMuteButton';
 
 export const TeacherStudentManagementButton: React.FC = () => {
-  const { students, kickStudent } = useClassroomContext();
+  const { students, kickStudent, onlineCount, pvCount } = useClassroomContext();
   const [showPanel, setShowPanel] = useState(false);
   const [confirmDialog, setConfirmDialog] = useState<{
     open: boolean;
@@ -102,30 +102,44 @@ export const TeacherStudentManagementButton: React.FC = () => {
                 <XMarkIcon className="w-4 h-4" />
               </Button>
             </CardHeader>
-            <CardContent className="p-3 max-h-60 overflow-y-auto">
-              {students.length === 0 ? (
-                <p className="text-gray-500 text-sm">暂无学生加入</p>
-              ) : (
-                <div className="space-y-2">
-                  {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 flex-wrap">
-                       <RTCToggleMuteButton userId={student.id} userName={student.name} />
-                       <IMToggleMuteButton userId={student.id} userName={student.name} />
-                       <Button
-                         onClick={() => openConfirmDialog('kick', student.id, student.name)}
-                         variant="outline"
-                         size="sm"
-                         className="text-xs bg-red-100 text-red-700 hover:bg-red-200 border-red-300"
-                       >
-                         移出
-                       </Button>
-                      </div>
-                    </div>
-                  ))}
+            <CardContent className="p-3">
+              {/* 统计信息 */}
+              <div className="flex justify-between mb-3 p-2 bg-gray-50 rounded text-sm">
+                <div className="flex items-center">
+                  <UsersIcon className="w-4 h-4 mr-1 text-blue-500" />
+                  <span>在线: {onlineCount}人</span>
+                </div>
+                <div className="flex items-center">
+                  <EyeIcon className="w-4 h-4 mr-1 text-green-500" />
+                  <span>看过: {pvCount}人</span>
                 </div>
-              )}
+              </div>
+              
+              <div className="max-h-60 overflow-y-auto">
+                {students.length === 0 ? (
+                  <p className="text-gray-500 text-sm">暂无学生加入</p>
+                ) : (
+                  <div className="space-y-2">
+                    {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 flex-wrap">
+                         <RTCToggleMuteButton userId={student.id} userName={student.name} />
+                         <IMToggleMuteButton userId={student.id} userName={student.name} />
+                         <Button
+                           onClick={() => openConfirmDialog('kick', student.id, student.name)}
+                           variant="outline"
+                           size="sm"
+                           className="text-xs bg-red-100 text-red-700 hover:bg-red-200 border-red-300"
+                         >
+                           移出
+                         </Button>
+                        </div>
+                      </div>
+                    ))}
+                  </div>
+                )}
+              </div>
             </CardContent>
           </Card>
         </div>

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

@@ -93,6 +93,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
   const [students, setStudents] = useState<Array<{id: string, name: string}>>([]);
   const [shareLink, setShareLink] = useState<string>('');
   const [showCameraOverlay, setShowCameraOverlay] = useState<boolean>(true);
+  const [onlineCount, setOnlineCount] = useState<number>(0);
+  const [pvCount, setPvCount] = useState<number>(0);
 
   // SDK实例
   const imEngine = useRef<ImEngine | null>(null);
@@ -201,6 +203,17 @@ export const useClassroom = ({ user }:{ user : User }) => {
         return updatedStudents;
       });
     });
+
+    // 监听群组成员变化事件(1.4.1版本新增)
+    imGroupManager.current.on('memberdatachange', (data: any) => {
+      console.log('群组成员统计变化:', data);
+      if (data.onlineCount !== undefined) {
+        setOnlineCount(data.onlineCount);
+      }
+      if (data.pv !== undefined) {
+        setPvCount(data.pv);
+      }
+    });
   };
 
   const listenMessageEvents = (): void => {
@@ -575,6 +588,18 @@ export const useClassroom = ({ user }:{ user : User }) => {
         }
       }
 
+      // 获取群组统计信息
+      try {
+        const groupInfo = await gm!.queryGroup(classId);
+        if (groupInfo && groupInfo.statistics) {
+          setOnlineCount(groupInfo.statistics.onlineCount || 0);
+          setPvCount(groupInfo.statistics.pv || 0);
+          console.log('群组统计信息:', groupInfo.statistics);
+        }
+      } catch (err) {
+        console.error('获取群组统计信息失败:', err);
+      }
+
       await joinRtcChannel(classId);
 
       buildShareLink(classId)
@@ -1236,7 +1261,11 @@ export const useClassroom = ({ user }:{ user : User }) => {
     checkAllMuteStatus,
     checkRtcMuteStatus,
     checkRtcMuteStatusBatch,
-    getAllRtcMuteStatus
+    getAllRtcMuteStatus,
+
+    // 统计信息
+    onlineCount,
+    pvCount
   };
 };