|
@@ -1,12 +1,15 @@
|
|
|
import React from 'react';
|
|
import React from 'react';
|
|
|
import { useClassroomContext } from './ClassroomProvider';
|
|
import { useClassroomContext } from './ClassroomProvider';
|
|
|
import { ClassStatus } from './useClassroom';
|
|
import { ClassStatus } from './useClassroom';
|
|
|
-import {
|
|
|
|
|
- StopIcon,
|
|
|
|
|
- UserGroupIcon,
|
|
|
|
|
|
|
+import {
|
|
|
|
|
+ StopIcon,
|
|
|
|
|
+ UserGroupIcon,
|
|
|
HandRaisedIcon,
|
|
HandRaisedIcon,
|
|
|
QuestionMarkCircleIcon
|
|
QuestionMarkCircleIcon
|
|
|
} from '@heroicons/react/24/outline';
|
|
} from '@heroicons/react/24/outline';
|
|
|
|
|
+import { Button } from '@/client/components/ui/button';
|
|
|
|
|
+import { Input } from '@/client/components/ui/input';
|
|
|
|
|
+import { Card, CardContent, CardHeader, CardTitle } from '@/client/components/ui/card';
|
|
|
|
|
|
|
|
export const TeacherView: React.FC = () => {
|
|
export const TeacherView: React.FC = () => {
|
|
|
const {
|
|
const {
|
|
@@ -53,139 +56,156 @@ export const TeacherView: React.FC = () => {
|
|
|
return (
|
|
return (
|
|
|
<div className="space-y-4 p-4">
|
|
<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>
|
|
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle className="text-lg flex items-center">
|
|
|
|
|
+ <StopIcon className="w-5 h-5 mr-2 text-red-500" />
|
|
|
|
|
+ 课堂控制
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-4">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={handleEndClass}
|
|
|
|
|
+ disabled={classStatus === ClassStatus.ENDED}
|
|
|
|
|
+ className={`w-full ${
|
|
|
|
|
+ classStatus === ClassStatus.ENDED
|
|
|
|
|
+ ? 'bg-gray-400 cursor-not-allowed'
|
|
|
|
|
+ : 'bg-red-500 hover:bg-red-600'
|
|
|
|
|
+ }`}
|
|
|
|
|
+ >
|
|
|
|
|
+ {classStatus === ClassStatus.ENDED ? '课堂已结束' : '结束课堂'}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+
|
|
|
|
|
+ {shareLink && (
|
|
|
|
|
+ <div className="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"
|
|
|
|
|
+ />
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={() => navigator.clipboard.writeText(shareLink)}
|
|
|
|
|
+ className="px-3 py-2 bg-blue-500 text-white rounded text-sm hover:bg-blue-600"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ >
|
|
|
|
|
+ 复制
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ )}
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
|
|
|
{/* 学生管理区域 */}
|
|
{/* 学生管理区域 */}
|
|
|
- <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>
|
|
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle className="text-lg flex items-center">
|
|
|
|
|
+ <UserGroupIcon className="w-5 h-5 mr-2 text-blue-500" />
|
|
|
|
|
+ 学生管理 ({students.length})
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ {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"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ >
|
|
|
|
|
+ 静音
|
|
|
|
|
+ </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"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ >
|
|
|
|
|
+ 取消静音
|
|
|
|
|
+ </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"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ >
|
|
|
|
|
+ 移出
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- </div>
|
|
|
|
|
- ))}
|
|
|
|
|
- </div>
|
|
|
|
|
- )}
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
|
|
|
{/* 举手管理区域 */}
|
|
{/* 举手管理区域 */}
|
|
|
{handUpList.length > 0 && (
|
|
{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>
|
|
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle className="text-lg flex items-center">
|
|
|
|
|
+ <HandRaisedIcon className="w-5 h-5 mr-2 text-orange-500" />
|
|
|
|
|
+ 举手请求 ({handUpList.length})
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ <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"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ >
|
|
|
|
|
+ 应答
|
|
|
|
|
+ </Button>
|
|
|
</div>
|
|
</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>
|
|
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
)}
|
|
)}
|
|
|
|
|
|
|
|
{/* 问题管理区域 */}
|
|
{/* 问题管理区域 */}
|
|
|
{questions.length > 0 && (
|
|
{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>
|
|
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle className="text-lg flex items-center">
|
|
|
|
|
+ <QuestionMarkCircleIcon className="w-5 h-5 mr-2 text-purple-500" />
|
|
|
|
|
+ 学生提问 ({questions.length})
|
|
|
|
|
+ </CardTitle>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent>
|
|
|
|
|
+ <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>
|
|
|
- <p className="text-sm text-gray-700">{question.question}</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- ))}
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
)}
|
|
)}
|
|
|
</div>
|
|
</div>
|
|
|
);
|
|
);
|