|
@@ -150,6 +150,8 @@ export const ClassroomPage = () => {
|
|
|
const [classStatus, setClassStatus] = useState<ClassStatus>(ClassStatus.NOT_STARTED);
|
|
const [classStatus, setClassStatus] = useState<ClassStatus>(ClassStatus.NOT_STARTED);
|
|
|
const [handUpList, setHandUpList] = useState<HandUpRequest[]>([]);
|
|
const [handUpList, setHandUpList] = useState<HandUpRequest[]>([]);
|
|
|
const [questions, setQuestions] = useState<string[]>([]);
|
|
const [questions, setQuestions] = useState<string[]>([]);
|
|
|
|
|
+ const [students, setStudents] = useState<Array<{id: string, name: string}>>([]);
|
|
|
|
|
+ const [shareLink, setShareLink] = useState<string>('');
|
|
|
|
|
|
|
|
// SDK实例
|
|
// SDK实例
|
|
|
const imEngine = useRef<ImEngine | null>(null);
|
|
const imEngine = useRef<ImEngine | null>(null);
|
|
@@ -294,6 +296,35 @@ export const ClassroomPage = () => {
|
|
|
});
|
|
});
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // 获取学生列表
|
|
|
|
|
+ const fetchStudents = async (classId: string) => {
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (!imEngine.current) {
|
|
|
|
|
+ throw new Error('IM引擎未初始化');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const groupManager = imEngine.current.getGroupManager();
|
|
|
|
|
+ if (!groupManager) {
|
|
|
|
|
+ throw new Error('IM群组管理器未初始化');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 使用classId作为群组ID获取成员
|
|
|
|
|
+ const response = await groupManager.listRecentGroupUser(classId);
|
|
|
|
|
+
|
|
|
|
|
+ // 转换IM用户数据格式
|
|
|
|
|
+ const students = response.userList.map((user: ImUser) => ({
|
|
|
|
|
+ id: user.userId,
|
|
|
|
|
+ name: user.userExtension || `用户${user.userId}`
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ setStudents(students);
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.error('从IM获取学生列表失败:', err);
|
|
|
|
|
+ // 可选: 显示错误提示给用户
|
|
|
|
|
+ // setError('获取学生列表失败,请稍后重试');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
// 统一登录逻辑
|
|
// 统一登录逻辑
|
|
|
const login = async (userId: string): Promise<void> => {
|
|
const login = async (userId: string): Promise<void> => {
|
|
|
try {
|
|
try {
|
|
@@ -333,6 +364,9 @@ export const ClassroomPage = () => {
|
|
|
setIsLoggedIn(true);
|
|
setIsLoggedIn(true);
|
|
|
setErrorMessage('');
|
|
setErrorMessage('');
|
|
|
showToast('success', '登录成功');
|
|
showToast('success', '登录成功');
|
|
|
|
|
+
|
|
|
|
|
+ // 登录后获取分享链接占位符
|
|
|
|
|
+ setShareLink(`${window.location.href.split('?')[0]}?classId=${classId || '12345'}`);
|
|
|
} catch (err: any) {
|
|
} catch (err: any) {
|
|
|
setErrorMessage(`登录失败: ${err.message}`);
|
|
setErrorMessage(`登录失败: ${err.message}`);
|
|
|
showToast('error', '登录失败');
|
|
showToast('error', '登录失败');
|
|
@@ -520,6 +554,9 @@ export const ClassroomPage = () => {
|
|
|
const createTime = new Date();
|
|
const createTime = new Date();
|
|
|
showMessage(`创建时间: ${createTime.toLocaleString()}`);
|
|
showMessage(`创建时间: ${createTime.toLocaleString()}`);
|
|
|
|
|
|
|
|
|
|
+ // 创建成功后生成分享链接
|
|
|
|
|
+ setShareLink(`${window.location.href.split('?')[0]}?classId=${response.groupId}`);
|
|
|
|
|
+
|
|
|
return response.groupId;
|
|
return response.groupId;
|
|
|
} catch (joinErr: any) {
|
|
} catch (joinErr: any) {
|
|
|
throw new Error(`创建成功但加入失败: ${joinErr.message}`);
|
|
throw new Error(`创建成功但加入失败: ${joinErr.message}`);
|
|
@@ -646,6 +683,29 @@ export const ClassroomPage = () => {
|
|
|
|
|
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
|
<div className="md:col-span-1">
|
|
<div className="md:col-span-1">
|
|
|
|
|
+ {shareLink && (
|
|
|
|
|
+ <div className="mb-4 p-4 bg-white rounded-md shadow">
|
|
|
|
|
+ <h4 className="text-lg font-medium mb-2">课堂分享链接</h4>
|
|
|
|
|
+ <div className="flex items-center">
|
|
|
|
|
+ <input
|
|
|
|
|
+ type="text"
|
|
|
|
|
+ readOnly
|
|
|
|
|
+ value={shareLink}
|
|
|
|
|
+ className="flex-1 px-3 py-2 border border-gray-300 rounded-l-md"
|
|
|
|
|
+ />
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ className="px-3 py-2 bg-blue-600 text-white rounded-r-md"
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ navigator.clipboard.writeText(shareLink);
|
|
|
|
|
+ showToast('info', '链接已复制');
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ 复制
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
<form>
|
|
<form>
|
|
|
{!isLoggedIn && (
|
|
{!isLoggedIn && (
|
|
|
<div className="mb-2">
|
|
<div className="mb-2">
|
|
@@ -689,6 +749,16 @@ export const ClassroomPage = () => {
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div className="flex space-x-2 mb-2">
|
|
<div className="flex space-x-2 mb-2">
|
|
|
|
|
+ {!isLoggedIn && (
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ className="px-3 py-2 bg-blue-600 text-white rounded-md"
|
|
|
|
|
+ onClick={() => login(userId)}
|
|
|
|
|
+ >
|
|
|
|
|
+ 登录
|
|
|
|
|
+ </button>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
{isLoggedIn && role === Role.Teacher && (
|
|
{isLoggedIn && role === Role.Teacher && (
|
|
|
<button
|
|
<button
|
|
|
type="button"
|
|
type="button"
|
|
@@ -704,15 +774,6 @@ export const ClassroomPage = () => {
|
|
|
创建课堂
|
|
创建课堂
|
|
|
</button>
|
|
</button>
|
|
|
)}
|
|
)}
|
|
|
- <button
|
|
|
|
|
- type="button"
|
|
|
|
|
- className="px-3 py-2 bg-blue-600 text-white rounded-md"
|
|
|
|
|
- disabled={isLoggedIn}
|
|
|
|
|
- onClick={() => login(userId)}
|
|
|
|
|
- >
|
|
|
|
|
- 登录
|
|
|
|
|
- </button>
|
|
|
|
|
-
|
|
|
|
|
<button
|
|
<button
|
|
|
type="button"
|
|
type="button"
|
|
|
className="px-3 py-2 bg-blue-600 text-white rounded-md"
|
|
className="px-3 py-2 bg-blue-600 text-white rounded-md"
|
|
@@ -856,26 +917,27 @@ export const ClassroomPage = () => {
|
|
|
<div>
|
|
<div>
|
|
|
<h5 className="font-medium mb-2">成员管理</h5>
|
|
<h5 className="font-medium mb-2">成员管理</h5>
|
|
|
<div className="space-y-2">
|
|
<div className="space-y-2">
|
|
|
- <div className="flex items-center justify-between">
|
|
|
|
|
- <span>学生A</span>
|
|
|
|
|
- <button
|
|
|
|
|
- type="button"
|
|
|
|
|
- className="px-2 py-1 bg-yellow-500 text-white rounded text-sm"
|
|
|
|
|
- onClick={() => toggleMuteMember('studentA', true)}
|
|
|
|
|
- >
|
|
|
|
|
- 静音
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div className="flex items-center justify-between">
|
|
|
|
|
- <span>学生B</span>
|
|
|
|
|
- <button
|
|
|
|
|
- type="button"
|
|
|
|
|
- className="px-2 py-1 bg-blue-500 text-white rounded text-sm"
|
|
|
|
|
- onClick={() => toggleMuteMember('studentB', false)}
|
|
|
|
|
- >
|
|
|
|
|
- 取消静音
|
|
|
|
|
- </button>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ {students.map(student => (
|
|
|
|
|
+ <div key={student.id} className="flex items-center justify-between">
|
|
|
|
|
+ <span>{student.name}</span>
|
|
|
|
|
+ <div className="space-x-2">
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ className="px-2 py-1 bg-yellow-500 text-white rounded text-sm"
|
|
|
|
|
+ onClick={() => toggleMuteMember(student.id, true)}
|
|
|
|
|
+ >
|
|
|
|
|
+ 静音
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ className="px-2 py-1 bg-blue-500 text-white rounded text-sm"
|
|
|
|
|
+ onClick={() => toggleMuteMember(student.id, false)}
|
|
|
|
|
+ >
|
|
|
|
|
+ 取消静音
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ))}
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|