|
|
@@ -55,25 +55,23 @@ export const ClassroomLayout = ({ children, role }: ClassroomLayoutProps) => {
|
|
|
>
|
|
|
{/* 屏幕共享视频将在这里动态添加 */}
|
|
|
</div>
|
|
|
-
|
|
|
+
|
|
|
{/* 摄像头小窗容器 - 固定在右上角 */}
|
|
|
<div
|
|
|
id="remoteCameraContainer"
|
|
|
ref={remoteCameraContainer}
|
|
|
- className={`absolute top-4 right-4 z-10 w-1/4 aspect-video ${
|
|
|
- showCameraOverlay ? 'block' : 'hidden'
|
|
|
- }`}
|
|
|
+ className={`absolute top-4 right-4 z-10 w-1/4 aspect-video ${showCameraOverlay ? 'block' : 'hidden'
|
|
|
+ }`}
|
|
|
>
|
|
|
{/* 摄像头视频将在这里动态添加 */}
|
|
|
</div>
|
|
|
-
|
|
|
+
|
|
|
{/* 摄像头小窗开关按钮 */}
|
|
|
<Button
|
|
|
type="button"
|
|
|
onClick={() => setShowCameraOverlay(!showCameraOverlay)}
|
|
|
- className={`absolute top-4 right-4 z-20 p-2 rounded-full ${
|
|
|
- showCameraOverlay ? 'bg-green-500' : 'bg-gray-500'
|
|
|
- } text-white`}
|
|
|
+ className={`absolute top-4 right-4 z-20 p-2 rounded-full ${showCameraOverlay ? 'bg-green-500' : 'bg-gray-500'
|
|
|
+ } text-white`}
|
|
|
title={showCameraOverlay ? '隐藏摄像头小窗' : '显示摄像头小窗'}
|
|
|
size="icon"
|
|
|
>
|
|
|
@@ -91,127 +89,127 @@ export const ClassroomLayout = ({ children, role }: ClassroomLayoutProps) => {
|
|
|
{messageList.map((msg, i) => (
|
|
|
<div key={i} className="text-sm mb-1">{msg}</div>
|
|
|
))}
|
|
|
- </div>
|
|
|
</div>
|
|
|
+ </div>
|
|
|
|
|
|
- {/* 底部固定区域 */}
|
|
|
- <div className="bg-white shadow-lg p-4">
|
|
|
- {/* 控制面板 */}
|
|
|
- <div className="p-2 flex flex-col gap-3 mb-1 border-b border-gray-200">
|
|
|
- <div className="flex flex-wrap gap-2">
|
|
|
- {role === Role.Teacher && (
|
|
|
- <Button
|
|
|
- type="button"
|
|
|
- onClick={() => setShowVideo(!showVideo)}
|
|
|
- className={`p-2 rounded-full ${showVideo ? 'bg-gray-500' : 'bg-gray-300'} text-white`}
|
|
|
- title={showVideo ? '隐藏视频' : '显示视频'}
|
|
|
- size="icon"
|
|
|
- variant="ghost"
|
|
|
- >
|
|
|
- <VideoCameraIcon className="w-4 h-4" />
|
|
|
- </Button>
|
|
|
- )}
|
|
|
+ {/* 底部固定区域 */}
|
|
|
+ <div className="bg-white shadow-lg p-4">
|
|
|
+ {/* 控制面板 */}
|
|
|
+ <div className="p-2 flex flex-col gap-3 mb-1 border-b border-gray-200">
|
|
|
+ <div className="flex flex-wrap gap-2">
|
|
|
+ {role === Role.Teacher && (
|
|
|
<Button
|
|
|
type="button"
|
|
|
- onClick={toggleCamera}
|
|
|
- className={`p-2 rounded-full ${isCameraOn ? 'bg-green-500' : 'bg-red-500'} text-white`}
|
|
|
- title={isCameraOn ? '关闭摄像头' : '开启摄像头'}
|
|
|
+ onClick={() => setShowVideo(!showVideo)}
|
|
|
+ className={`p-2 rounded-full ${showVideo ? 'bg-gray-500' : 'bg-gray-300'} text-white`}
|
|
|
+ title={showVideo ? '隐藏视频' : '显示视频'}
|
|
|
size="icon"
|
|
|
variant="ghost"
|
|
|
>
|
|
|
- <CameraIcon className="w-4 h-4" />
|
|
|
+ <VideoCameraIcon className="w-4 h-4" />
|
|
|
</Button>
|
|
|
+ )}
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ onClick={toggleCamera}
|
|
|
+ className={`p-2 rounded-full ${isCameraOn ? 'bg-green-500' : 'bg-red-500'} text-white`}
|
|
|
+ title={isCameraOn ? '关闭摄像头' : '开启摄像头'}
|
|
|
+ size="icon"
|
|
|
+ variant="ghost"
|
|
|
+ >
|
|
|
+ <CameraIcon className="w-4 h-4" />
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ onClick={toggleAudio}
|
|
|
+ className={`p-2 rounded-full ${isAudioOn ? 'bg-green-500' : 'bg-red-500'} text-white`}
|
|
|
+ title={isAudioOn ? '关闭麦克风' : '开启麦克风'}
|
|
|
+ size="icon"
|
|
|
+ variant="ghost"
|
|
|
+ >
|
|
|
+ <MicrophoneIcon className="w-4 h-4" />
|
|
|
+ </Button>
|
|
|
+ {role === Role.Teacher && (
|
|
|
<Button
|
|
|
type="button"
|
|
|
- onClick={toggleAudio}
|
|
|
- className={`p-2 rounded-full ${isAudioOn ? 'bg-green-500' : 'bg-red-500'} text-white`}
|
|
|
- title={isAudioOn ? '关闭麦克风' : '开启麦克风'}
|
|
|
+ onClick={toggleScreenShare}
|
|
|
+ className={`p-2 rounded-full ${isScreenSharing ? 'bg-green-500' : 'bg-blue-500'} text-white`}
|
|
|
+ title={isScreenSharing ? '停止共享' : '共享屏幕'}
|
|
|
size="icon"
|
|
|
variant="ghost"
|
|
|
>
|
|
|
- <MicrophoneIcon className="w-4 h-4" />
|
|
|
+ <ShareIcon className="w-4 h-4" />
|
|
|
+ </Button>
|
|
|
+ )}
|
|
|
+ {role === Role.Teacher && shareLink && (
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ onClick={() => setShowShareLink(!showShareLink)}
|
|
|
+ className="p-2 rounded-full bg-blue-500 text-white"
|
|
|
+ title="分享链接"
|
|
|
+ size="icon"
|
|
|
+ variant="ghost"
|
|
|
+ >
|
|
|
+ <ClipboardDocumentIcon className="w-4 h-4" />
|
|
|
</Button>
|
|
|
- {role === Role.Teacher && (
|
|
|
- <Button
|
|
|
- type="button"
|
|
|
- onClick={toggleScreenShare}
|
|
|
- className={`p-2 rounded-full ${isScreenSharing ? 'bg-green-500' : 'bg-blue-500'} text-white`}
|
|
|
- title={isScreenSharing ? '停止共享' : '共享屏幕'}
|
|
|
- size="icon"
|
|
|
- variant="ghost"
|
|
|
- >
|
|
|
- <ShareIcon className="w-4 h-4" />
|
|
|
- </Button>
|
|
|
- )}
|
|
|
- {role === Role.Teacher && shareLink && (
|
|
|
- <Button
|
|
|
- type="button"
|
|
|
- onClick={() => setShowShareLink(!showShareLink)}
|
|
|
- className="p-2 rounded-full bg-blue-500 text-white"
|
|
|
- title="分享链接"
|
|
|
- size="icon"
|
|
|
- variant="ghost"
|
|
|
- >
|
|
|
- <ClipboardDocumentIcon className="w-4 h-4" />
|
|
|
- </Button>
|
|
|
- )}
|
|
|
- </div>
|
|
|
-
|
|
|
- {showShareLink && shareLink && (
|
|
|
- <Card className="bg-blue-50 p-2">
|
|
|
- <CardContent className="p-0">
|
|
|
- <div className="flex items-center gap-1">
|
|
|
- <input
|
|
|
- type="text"
|
|
|
- value={shareLink}
|
|
|
- readOnly
|
|
|
- className="flex-1 text-xs border rounded px-2 py-1 truncate"
|
|
|
- />
|
|
|
- <Button
|
|
|
- type="button"
|
|
|
- onClick={() => navigator.clipboard.writeText(shareLink)}
|
|
|
- className="p-2 bg-blue-500 text-white rounded"
|
|
|
- title="复制链接"
|
|
|
- size="icon"
|
|
|
- >
|
|
|
- <ClipboardDocumentIcon className="w-4 h-4" />
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
- </CardContent>
|
|
|
- </Card>
|
|
|
)}
|
|
|
-
|
|
|
- {/* 角色特定内容 */}
|
|
|
- <div className="flex-1 overflow-y-auto">
|
|
|
- {children}
|
|
|
- </div>
|
|
|
</div>
|
|
|
|
|
|
- {/* 消息输入框 */}
|
|
|
- <div className="relative mt-2">
|
|
|
- <Textarea
|
|
|
- value={msgText}
|
|
|
- onChange={(e) => setMsgText(e.target.value)}
|
|
|
- onKeyDown={(e) => {
|
|
|
- if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
- e.preventDefault();
|
|
|
- sendMessage();
|
|
|
- }
|
|
|
- }}
|
|
|
- className="w-full pr-10"
|
|
|
- placeholder="输入消息..."
|
|
|
- rows={3}
|
|
|
- />
|
|
|
- <Button
|
|
|
- type="button"
|
|
|
- onClick={sendMessage}
|
|
|
- className="absolute right-2 bottom-2 p-1 bg-blue-500 text-white rounded-full"
|
|
|
- size="icon"
|
|
|
- >
|
|
|
- <PaperAirplaneIcon className="w-4 h-4" />
|
|
|
- </Button>
|
|
|
+ {showShareLink && shareLink && (
|
|
|
+ <Card className="bg-blue-50 p-2">
|
|
|
+ <CardContent className="p-0">
|
|
|
+ <div className="flex items-center gap-1">
|
|
|
+ <input
|
|
|
+ type="text"
|
|
|
+ value={shareLink}
|
|
|
+ readOnly
|
|
|
+ className="flex-1 text-xs border rounded px-2 py-1 truncate"
|
|
|
+ />
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ onClick={() => navigator.clipboard.writeText(shareLink)}
|
|
|
+ className="p-2 bg-blue-500 text-white rounded"
|
|
|
+ title="复制链接"
|
|
|
+ size="icon"
|
|
|
+ >
|
|
|
+ <ClipboardDocumentIcon className="w-4 h-4" />
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </CardContent>
|
|
|
+ </Card>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 角色特定内容 */}
|
|
|
+ <div className="flex-1 overflow-y-auto">
|
|
|
+ {children}
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ {/* 消息输入框 */}
|
|
|
+ <div className="relative mt-2">
|
|
|
+ <Textarea
|
|
|
+ value={msgText}
|
|
|
+ onChange={(e) => setMsgText(e.target.value)}
|
|
|
+ onKeyDown={(e) => {
|
|
|
+ if (e.key === 'Enter' && !e.shiftKey) {
|
|
|
+ e.preventDefault();
|
|
|
+ sendMessage();
|
|
|
+ }
|
|
|
+ }}
|
|
|
+ className="w-full pr-10"
|
|
|
+ placeholder="输入消息..."
|
|
|
+ rows={3}
|
|
|
+ />
|
|
|
+ <Button
|
|
|
+ type="button"
|
|
|
+ onClick={sendMessage}
|
|
|
+ className="absolute right-2 bottom-2 p-1 bg-blue-500 text-white rounded-full"
|
|
|
+ size="icon"
|
|
|
+ >
|
|
|
+ <PaperAirplaneIcon className="w-4 h-4" />
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
);
|