| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- import React, { ReactNode } from 'react';
- import { Role } from './useClassroom.ts';
- import { useClassroomContext } from './ClassroomProvider.tsx';
- import {
- VideoCameraIcon,
- CameraIcon,
- MicrophoneIcon,
- ShareIcon,
- ClipboardDocumentIcon,
- PaperAirplaneIcon
- } from '@heroicons/react/24/outline';
- interface ClassroomLayoutProps {
- children: ReactNode;
- role: Role;
- }
- export const ClassroomLayout = ({ children, role }: ClassroomLayoutProps) => {
- const [showVideo, setShowVideo] = React.useState(role !== Role.Teacher);
- const [showShareLink, setShowShareLink] = React.useState(false);
- const {
- remoteScreenContainer,
- remoteCameraContainer,
- isCameraOn,
- isAudioOn,
- isScreenSharing,
- toggleCamera,
- toggleAudio,
- toggleScreenShare,
- messageList,
- msgText,
- setMsgText,
- sendMessage,
- handUpList,
- questions,
- classStatus,
- shareLink,
- showCameraOverlay,
- setShowCameraOverlay
- } = useClassroomContext();
- return (
- <div className="flex flex-col md:flex-row h-screen bg-gray-100">
- {/* 视频区域 */}
- {showVideo && (
- <div className="relative h-[300px] md:flex-1 md:h-auto bg-black">
- {/* 主屏幕共享容器 */}
- <div
- id="remoteScreenContainer"
- ref={remoteScreenContainer}
- className="w-full h-full"
- >
- {/* 屏幕共享视频将在这里动态添加 */}
- </div>
-
- {/* 摄像头小窗容器 - 固定在右上角 */}
- <div
- id="remoteCameraContainer"
- ref={remoteCameraContainer}
- 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`}
- title={showCameraOverlay ? '隐藏摄像头小窗' : '显示摄像头小窗'}
- >
- <CameraIcon className="w-4 h-4" />
- </button>
- </div>
- )}
- {/* 消息和控制面板列 */}
- <div className={`${showVideo ? 'w-full md:w-96 flex-1' : 'flex-1'} flex flex-col`}>
- {/* 消息区域 */}
- <div className="flex flex-col h-full">
- {/* 消息列表 - 填充剩余空间 */}
- <div className="flex-1 overflow-y-auto bg-white shadow-lg p-4">
- {messageList.map((msg, i) => (
- <div key={i} className="text-sm mb-1">{msg}</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 ? '隐藏视频' : '显示视频'}
- >
- <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 ? '关闭摄像头' : '开启摄像头'}
- >
- <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 ? '关闭麦克风' : '开启麦克风'}
- >
- <MicrophoneIcon 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 ? '停止共享' : '共享屏幕'}
- >
- <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="分享链接"
- >
- <ClipboardDocumentIcon className="w-4 h-4" />
- </button>
- )}
- </div>
- {showShareLink && shareLink && (
- <div className="bg-blue-50 p-2 rounded">
- <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="复制链接"
- >
- <ClipboardDocumentIcon className="w-4 h-4" />
- </button>
- </div>
- </div>
- )}
-
- {/* 角色特定内容 */}
- <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 border rounded px-2 py-1 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"
- >
- <PaperAirplaneIcon className="w-4 h-4" />
- </button>
- </div>
- </div>
- </div>
- </div>
- );
- };
|