|
|
@@ -30,6 +30,23 @@ enum ClassStatus {
|
|
|
}
|
|
|
|
|
|
// 课堂上下文类型
|
|
|
+// 互动消息类型
|
|
|
+type InteractionAction = 'hand_up' | 'cancel_hand_up' | 'answer_hand_up';
|
|
|
+
|
|
|
+// 互动消息基础接口
|
|
|
+interface InteractionMessage {
|
|
|
+ action: InteractionAction;
|
|
|
+ studentId: string;
|
|
|
+ studentName?: string;
|
|
|
+ timestamp?: number;
|
|
|
+ question?: string;
|
|
|
+}
|
|
|
+
|
|
|
+// 举手请求类型
|
|
|
+interface HandUpRequest extends InteractionMessage {
|
|
|
+ timestamp: number;
|
|
|
+}
|
|
|
+
|
|
|
type ClassroomContextType = {
|
|
|
userId: string;
|
|
|
role: 'teacher' | 'student';
|
|
|
@@ -38,10 +55,15 @@ type ClassroomContextType = {
|
|
|
messageList: string[];
|
|
|
errorMessage: string;
|
|
|
classStatus: ClassStatus;
|
|
|
+ handUpList: HandUpRequest[]; // 举手列表
|
|
|
+ questions: string[]; // 问题列表
|
|
|
setRole: (role: 'teacher' | 'student') => void;
|
|
|
startClass: () => Promise<void>;
|
|
|
endClass: () => Promise<void>;
|
|
|
toggleMuteMember: (userId: string, mute: boolean) => Promise<void>;
|
|
|
+ handUp: (question?: string) => Promise<void>; // 学生举手
|
|
|
+ answerHandUp: (studentId: string) => Promise<void>; // 老师应答
|
|
|
+ sendQuestion: (question: string) => Promise<void>; // 发送问题
|
|
|
};
|
|
|
|
|
|
const ClassroomContext = createContext<ClassroomContextType | null>(null);
|
|
|
@@ -108,6 +130,8 @@ export const ClassroomPage = () => {
|
|
|
const [messageList, setMessageList] = useState<string[]>([]);
|
|
|
const [errorMessage, setErrorMessage] = useState<string>('');
|
|
|
const [classStatus, setClassStatus] = useState<ClassStatus>(ClassStatus.NOT_STARTED);
|
|
|
+ const [handUpList, setHandUpList] = useState<HandUpRequest[]>([]);
|
|
|
+ const [questions, setQuestions] = useState<string[]>([]);
|
|
|
|
|
|
// SDK实例
|
|
|
const imEngine = useRef<ImEngine | null>(null);
|
|
|
@@ -170,6 +194,40 @@ export const ClassroomPage = () => {
|
|
|
}
|
|
|
} else {
|
|
|
showMessage(`收到消息: ${msg.data}`);
|
|
|
+ } else if (msg.type === 88891) { // 举手消息
|
|
|
+ try {
|
|
|
+ const data = JSON.parse(msg.data) as InteractionMessage;
|
|
|
+ if (data.action === 'hand_up') {
|
|
|
+ const handUpData: HandUpRequest = {
|
|
|
+ ...data,
|
|
|
+ timestamp: data.timestamp || Date.now()
|
|
|
+ };
|
|
|
+ setHandUpList([...handUpList, handUpData]);
|
|
|
+ showMessage(`${data.studentName || data.studentId} 举手了`);
|
|
|
+ } else if (data.action === 'cancel_hand_up') {
|
|
|
+ setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('解析举手消息失败', err);
|
|
|
+ }
|
|
|
+ } else if (msg.type === 88892) { // 问题消息
|
|
|
+ try {
|
|
|
+ const data = JSON.parse(msg.data) as {question: string};
|
|
|
+ setQuestions([...questions, data.question]);
|
|
|
+ showMessage(`收到问题: ${data.question}`);
|
|
|
+ } catch (err) {
|
|
|
+ console.error('解析问题消息失败', err);
|
|
|
+ }
|
|
|
+ } else if (msg.type === 88893) { // 应答消息
|
|
|
+ try {
|
|
|
+ const data = JSON.parse(msg.data) as InteractionMessage;
|
|
|
+ if (data.action === 'answer_hand_up' && data.studentId === userId) {
|
|
|
+ showMessage('老师已应答你的举手');
|
|
|
+ setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('解析应答消息失败', err);
|
|
|
+ }
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
@@ -377,6 +435,62 @@ export const ClassroomPage = () => {
|
|
|
};
|
|
|
}, []);
|
|
|
|
|
|
+ // 学生举手
|
|
|
+ const handUp = async (question?: string): Promise<void> => {
|
|
|
+ if (!imMessageManager.current || !classId || role !== 'student') return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ await imMessageManager.current.sendGroupMessage({
|
|
|
+ groupId: classId,
|
|
|
+ data: JSON.stringify({
|
|
|
+ action: 'hand_up',
|
|
|
+ studentId: userId,
|
|
|
+ timestamp: Date.now(),
|
|
|
+ question
|
|
|
+ }),
|
|
|
+ type: 88891,
|
|
|
+ level: ImMessageLevel.NORMAL,
|
|
|
+ });
|
|
|
+ } catch (err: any) {
|
|
|
+ setErrorMessage(`举手失败: ${err.message}`);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 老师应答举手
|
|
|
+ const answerHandUp = async (studentId: string): Promise<void> => {
|
|
|
+ if (!imMessageManager.current || !classId || role !== 'teacher') return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ await imMessageManager.current.sendGroupMessage({
|
|
|
+ groupId: classId,
|
|
|
+ data: JSON.stringify({
|
|
|
+ action: 'answer_hand_up',
|
|
|
+ studentId
|
|
|
+ }),
|
|
|
+ type: 88893,
|
|
|
+ level: ImMessageLevel.HIGH,
|
|
|
+ });
|
|
|
+ } catch (err: any) {
|
|
|
+ setErrorMessage(`应答失败: ${err.message}`);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 发送问题
|
|
|
+ const sendQuestion = async (question: string): Promise<void> => {
|
|
|
+ if (!imMessageManager.current || !classId) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ await imMessageManager.current.sendGroupMessage({
|
|
|
+ groupId: classId,
|
|
|
+ data: question,
|
|
|
+ type: 88892,
|
|
|
+ level: ImMessageLevel.NORMAL,
|
|
|
+ });
|
|
|
+ } catch (err: any) {
|
|
|
+ setErrorMessage(`问题发送失败: ${err.message}`);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
return (
|
|
|
<ClassroomContext.Provider value={{
|
|
|
userId,
|
|
|
@@ -386,10 +500,15 @@ export const ClassroomPage = () => {
|
|
|
messageList,
|
|
|
errorMessage,
|
|
|
classStatus,
|
|
|
+ handUpList,
|
|
|
+ questions,
|
|
|
setRole,
|
|
|
startClass,
|
|
|
endClass,
|
|
|
- toggleMuteMember
|
|
|
+ toggleMuteMember,
|
|
|
+ handUp,
|
|
|
+ answerHandUp,
|
|
|
+ sendQuestion
|
|
|
}}>
|
|
|
<div className="container mx-auto p-4">
|
|
|
<h1 className="text-2xl font-bold mb-4">互动课堂</h1>
|