Преглед на файлове

优化ExamAdmin.tsx中的socket消息处理逻辑

yourname преди 6 месеца
родител
ревизия
f0f465c4c3

+ 72 - 36
client/mobile/components/Exam/ExamAdmin.tsx

@@ -74,6 +74,7 @@ export default function ExamAdmin() {
   const {
     socketRoom,
     answerManagement,
+    currentQuestion,
     // calculateCumulativeResults
   } = useSocketClient(classroom as string);
 
@@ -85,57 +86,92 @@ export default function ExamAdmin() {
   const [activeTab, setActiveTab] = useState('current');
 
   // 获取当前题目
-  const [currentQuestion, setCurrentQuestion] = useState<QuizState | null>(null);
+  // const [currentQuestion, setCurrentQuestion] = useState<QuizState | null>(null);
   const [lastMessage, setLastMessage] = useState<ExamSocketRoomMessage | null>(null);
 
   // 初始化socket连接和监听
-  useEffect(() => {
-    if (!classroom) return;
+  // useEffect(() => {
+  //   if (!classroom) return;
 
-    socketRoom.joinRoom(classroom);
+  //   socketRoom.joinRoom(classroom);
     
-    const handleMessage = (data: ExamSocketRoomMessage) => {
-      setLastMessage(data);
-    };
+  //   const handleMessage = (data: ExamSocketRoomMessage) => {
+  //     setLastMessage(data);
+  //   };
+
+  //   socketRoom.onRoomMessage(handleMessage);
+
+  //   return () => {
+  //     socketRoom.leaveRoom(classroom);
+  //     socketRoom.onRoomMessage(handleMessage);
+  //   };
+  // }, [classroom]);
+    // 加入/离开房间
+    useEffect(() => {
+      if (!classroom) return;
+      
+      socketRoom.joinRoom(classroom);
+      
+      return () => {
+        socketRoom.leaveRoom(classroom);
+      };
+    }, [classroom, socketRoom.joinRoom, socketRoom.leaveRoom]);
+
+  // // 获取当前题目状态
+  // useEffect(() => {
+  //   if (!classroom) return;
 
-    socketRoom.onRoomMessage(handleMessage);
+  //   const fetchCurrentQuestion = async () => {
+  //     const question = await answerManagement.getCurrentQuestion(classroom);
+  //     setCurrentQuestion(question);
+  //   };
 
-    return () => {
-      socketRoom.leaveRoom(classroom);
-      socketRoom.onRoomMessage(handleMessage);
-    };
-  }, [classroom]);
+  //   fetchCurrentQuestion();
+  // }, [classroom, lastMessage]);
 
-  // 获取当前题目状态
+  // 监听答题消息并更新答案
   useEffect(() => {
-    if (!classroom) return;
+    if (!classroom || !currentDate) return;
 
-    const fetchCurrentQuestion = async () => {
-      const question = await answerManagement.getCurrentQuestion(classroom);
-      setCurrentQuestion(question);
+    const handleAnswerMessage = async () => {
+      try {
+        const answers = await answerManagement.getAnswers(
+          classroom as string,
+          currentDate
+        );
+        
+        const processedAnswers = answers.map(answer => ({
+          ...answer,
+          profitAmount: answer.profitAmount || 0,
+          profitPercent: answer.profitPercent || 0,
+          holdingStock: answer.holdingStock || '0',
+          holdingCash: answer.holdingCash || '0'
+        }));
+
+        setAnswers(processedAnswers);
+        setDailyAnswers(prev => ({
+          ...prev,
+          [currentDate]: processedAnswers
+        }));
+      } catch (error) {
+        console.error('获取答案失败:', error);
+      }
     };
 
-    fetchCurrentQuestion();
-  }, [classroom, lastMessage]);
+    // 初始获取答案
+    handleAnswerMessage();
 
-  // 获取答案
-  useEffect(() => {
-    if (!classroom || !currentDate) return;
+    // 订阅答题消息
+    if (socketRoom.client) {
+      socketRoom.client.on('exam:answer', handleAnswerMessage);
+    }
 
-    const fetchAnswers = async () => {
-      const answers = await answerManagement.getAnswers(
-        classroom as string,
-        currentDate
-      );
-      setAnswers(answers);
-      setDailyAnswers(prev => ({
-        ...prev,
-        [currentDate]: answers
-      }));
+    return () => {
+      if (socketRoom.client) {
+        socketRoom.client.off('exam:answer', handleAnswerMessage);
+      }
     };
-
-    fetchAnswers();
-  }, [classroom, currentDate, lastMessage]);
+  }, [classroom, currentDate, answerManagement, socketRoom]);
 
   // // 更新答案状态
   // useEffect(() => {

+ 16 - 2
client/mobile/components/Exam/ExamCard.tsx

@@ -119,7 +119,14 @@ export default function ExamCard() {
           classroom as string,
           currentQuestion?.id || '',
           nickname,
-          answer
+          answer,
+          (success) => {
+            if (success) {
+              message.success('答案提交成功');
+            } else {
+              message.error('提交答案失败');
+            }
+          }
         );
       } catch (error) {
         message.error('提交答案失败');
@@ -145,7 +152,14 @@ export default function ExamCard() {
           classroom as string,
           currentQuestion?.id || '',
           nickname,
-          answer
+          answer,
+          (success) => {
+            if (success) {
+              message.success('答案提交成功');
+            } else {
+              message.error('提交答案失败');
+            }
+          }
         );
       } catch (error) {
         message.error('提交答案失败');

+ 10 - 6
client/mobile/components/Exam/hooks/useSocketClient.ts

@@ -181,7 +181,7 @@ export function useSocketClient(roomId: string | null) {
 
 
   // 存储答案
-  const storeAnswer = useCallback(async (roomId: string, questionId: string, userId: string, answer: QuizContent) => {
+  const storeAnswer = useCallback(async (roomId: string, questionId: string, userId: string, answer: QuizContent, callback?: (success: boolean) => void) => {
     if (!client) return;
 
     return handleAsyncOperation(async () => {
@@ -208,6 +208,8 @@ export function useSocketClient(roomId: string | null) {
           questionId,
           userId,
           answer: initialAnswer
+        }, (success: boolean) => {
+          callback?.(success);
         });
         return;
       }
@@ -253,11 +255,13 @@ export function useSocketClient(roomId: string | null) {
         totalProfitPercent
       };
 
-      client.emit('exam:storeAnswer', { 
-        roomId, 
-        questionId, 
-        userId, 
-        answer: answerWithProfit 
+      client.emit('exam:storeAnswer', {
+        roomId,
+        questionId,
+        userId,
+        answer: answerWithProfit
+      }, (success: boolean) => {
+        callback?.(success);
       });
     }, '存储答案失败');
   }, [client, getAnswers]);

+ 2 - 2
docs/exam.md

@@ -1,11 +1,11 @@
 从管理后台 教室管理,点击 打开股票训练链接
 股票训练链接打开后,创建 教室号 房间
 答题卡链接打开后,加入教室号 房间
-答题卡管理员链接打开后,加入教室号 房间(将现有答题卡里的底部按钮组,二维码区域 拆成 管理员路由页面)
+答题卡管理员链接打开后,加入教室号 房间
 股票 点 下一天按钮时,发送 下一天的 日期,收盘价 到房间
 答提卡每次收到消息后,显示最新日期,价格,同时重置 A,B 按钮
 答提卡点击 选A或选B后,提交答案到redis,及发消息给管理员
 答题卡答题列表中,显示 答题卡 回答记录
 管理员页收到答题消息后显示出来
-管理员点击收卷,从redis读取教室的所有答案,汇总提交后交卷后端api.v1 接口
+管理员点击收卷,从redis读取教室的所有答案,汇总提交后交卷后端api 接口
 最后管理员清理redis,结束所有答题卡连接

+ 13 - 1
server/routes_io_exam.ts

@@ -165,13 +165,14 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
   });
 
   // 存储答案
-  socket.on('exam:storeAnswer', async (data: ExamAnswerData) => {
+  socket.on('exam:storeAnswer', async (data: ExamAnswerData, callback: (success: boolean) => void) => {
     try {
       const { roomId, questionId, answer } = data;
       const user = socket.user;
       
       if (!user) {
         socket.emit('error', '未授权访问');
+        callback(false);
         return;
       }
 
@@ -197,10 +198,21 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
         username: user.username
       });
 
+      // 通知管理员有新答案提交
+      socket.to(`admin-${roomId}`).emit('exam:newAnswer', {
+        roomId,
+        questionId,
+        userId: user.id,
+        username: user.username,
+        timestamp: new Date().toISOString()
+      });
+
       console.log(`用户 ${user.username} 在房间 ${roomId} 存储答案`);
+      callback(true);
     } catch (error) {
       console.error('存储答案失败:', error);
       socket.emit('error', '存储答案失败');
+      callback(false);
     }
   });