|
|
@@ -0,0 +1,192 @@
|
|
|
+import React from 'react';
|
|
|
+import { useClassroomContext } from './ClassroomProvider';
|
|
|
+import { ClassStatus } from './useClassroom';
|
|
|
+import {
|
|
|
+ StopIcon,
|
|
|
+ UserGroupIcon,
|
|
|
+ HandRaisedIcon,
|
|
|
+ QuestionMarkCircleIcon
|
|
|
+} from '@heroicons/react/24/outline';
|
|
|
+
|
|
|
+export const TeacherView: React.FC = () => {
|
|
|
+ const {
|
|
|
+ endClass,
|
|
|
+ classStatus,
|
|
|
+ handUpList,
|
|
|
+ questions,
|
|
|
+ students,
|
|
|
+ shareLink,
|
|
|
+ toggleMuteMember,
|
|
|
+ answerHandUp,
|
|
|
+ kickStudent
|
|
|
+ } = useClassroomContext();
|
|
|
+
|
|
|
+ const handleEndClass = async () => {
|
|
|
+ if (window.confirm('确定要结束课堂吗?所有学生将被断开连接。')) {
|
|
|
+ await endClass();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleMuteStudent = (studentId: string, studentName: string) => {
|
|
|
+ if (window.confirm(`确定要静音 ${studentName} 吗?`)) {
|
|
|
+ toggleMuteMember(studentId, true);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleUnmuteStudent = (studentId: string, studentName: string) => {
|
|
|
+ if (window.confirm(`确定要取消静音 ${studentName} 吗?`)) {
|
|
|
+ toggleMuteMember(studentId, false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleAnswerHandUp = (studentId: string, studentName: string) => {
|
|
|
+ answerHandUp(studentId);
|
|
|
+ alert(`已应答 ${studentName} 的举手`);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleKickStudent = (studentId: string, studentName: string) => {
|
|
|
+ if (window.confirm(`确定要移出 ${studentName} 吗?`)) {
|
|
|
+ kickStudent(studentId);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="space-y-4 p-4">
|
|
|
+ {/* 课堂控制区域 */}
|
|
|
+ <div className="bg-white rounded-lg shadow p-4">
|
|
|
+ <h3 className="text-lg font-semibold mb-4 flex items-center">
|
|
|
+ <StopIcon className="w-5 h-5 mr-2 text-red-500" />
|
|
|
+ 课堂控制
|
|
|
+ </h3>
|
|
|
+
|
|
|
+ <button
|
|
|
+ onClick={handleEndClass}
|
|
|
+ disabled={classStatus === ClassStatus.ENDED}
|
|
|
+ className={`w-full py-3 px-4 rounded-lg text-white font-medium transition-colors ${
|
|
|
+ classStatus === ClassStatus.ENDED
|
|
|
+ ? 'bg-gray-400 cursor-not-allowed'
|
|
|
+ : 'bg-red-500 hover:bg-red-600'
|
|
|
+ }`}
|
|
|
+ >
|
|
|
+ {classStatus === ClassStatus.ENDED ? '课堂已结束' : '结束课堂'}
|
|
|
+ </button>
|
|
|
+
|
|
|
+ {shareLink && (
|
|
|
+ <div className="mt-4 p-3 bg-blue-50 rounded-lg">
|
|
|
+ <p className="text-sm text-blue-800 mb-2">课堂邀请链接:</p>
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ value={shareLink}
|
|
|
+ readOnly
|
|
|
+ className="flex-1 text-sm border border-blue-200 rounded px-3 py-2 bg-white"
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ onClick={() => navigator.clipboard.writeText(shareLink)}
|
|
|
+ className="px-3 py-2 bg-blue-500 text-white rounded text-sm hover:bg-blue-600"
|
|
|
+ >
|
|
|
+ 复制
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 学生管理区域 */}
|
|
|
+ <div className="bg-white rounded-lg shadow p-4">
|
|
|
+ <h3 className="text-lg font-semibold mb-4 flex items-center">
|
|
|
+ <UserGroupIcon className="w-5 h-5 mr-2 text-blue-500" />
|
|
|
+ 学生管理 ({students.length})
|
|
|
+ </h3>
|
|
|
+
|
|
|
+ {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">
|
|
|
+ <span className="text-sm">{student.name}</span>
|
|
|
+ <div className="flex gap-1">
|
|
|
+ <button
|
|
|
+ onClick={() => handleMuteStudent(student.id, student.name)}
|
|
|
+ className="px-2 py-1 bg-yellow-500 text-white text-xs rounded hover:bg-yellow-600"
|
|
|
+ >
|
|
|
+ 静音
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ onClick={() => handleUnmuteStudent(student.id, student.name)}
|
|
|
+ className="px-2 py-1 bg-green-500 text-white text-xs rounded hover:bg-green-600"
|
|
|
+ >
|
|
|
+ 取消静音
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ onClick={() => handleKickStudent(student.id, student.name)}
|
|
|
+ className="px-2 py-1 bg-red-500 text-white text-xs rounded hover:bg-red-600"
|
|
|
+ >
|
|
|
+ 移出
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 举手管理区域 */}
|
|
|
+ {handUpList.length > 0 && (
|
|
|
+ <div className="bg-white rounded-lg shadow p-4">
|
|
|
+ <h3 className="text-lg font-semibold mb-4 flex items-center">
|
|
|
+ <HandRaisedIcon className="w-5 h-5 mr-2 text-orange-500" />
|
|
|
+ 举手请求 ({handUpList.length})
|
|
|
+ </h3>
|
|
|
+
|
|
|
+ <div className="space-y-2">
|
|
|
+ {handUpList.map((request, index) => (
|
|
|
+ <div key={index} className="p-3 border rounded-lg bg-orange-50">
|
|
|
+ <div className="flex items-center justify-between mb-2">
|
|
|
+ <span className="font-medium">{request.studentName || request.studentId}</span>
|
|
|
+ <span className="text-xs text-gray-500">
|
|
|
+ {new Date(request.timestamp).toLocaleTimeString()}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ {request.question && (
|
|
|
+ <p className="text-sm text-gray-600 mb-2">问题:{request.question}</p>
|
|
|
+ )}
|
|
|
+ <button
|
|
|
+ onClick={() => handleAnswerHandUp(request.studentId, request.studentName || request.studentId)}
|
|
|
+ className="px-3 py-1 bg-green-500 text-white text-sm rounded hover:bg-green-600"
|
|
|
+ >
|
|
|
+ 应答
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 问题管理区域 */}
|
|
|
+ {questions.length > 0 && (
|
|
|
+ <div className="bg-white rounded-lg shadow p-4">
|
|
|
+ <h3 className="text-lg font-semibold mb-4 flex items-center">
|
|
|
+ <QuestionMarkCircleIcon className="w-5 h-5 mr-2 text-purple-500" />
|
|
|
+ 学生提问 ({questions.length})
|
|
|
+ </h3>
|
|
|
+
|
|
|
+ <div className="space-y-3">
|
|
|
+ {questions.map((question, index) => (
|
|
|
+ <div key={index} className="p-3 border rounded-lg bg-purple-50">
|
|
|
+ <div className="flex items-center justify-between mb-2">
|
|
|
+ <span className="font-medium">{question.studentName}</span>
|
|
|
+ <span className="text-xs text-gray-500">
|
|
|
+ {new Date(question.timestamp).toLocaleTimeString()}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <p className="text-sm text-gray-700">{question.question}</p>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|