瀏覽代碼

添加了setCurrentQuestion调用,确保获取问题后更新状态

yourname 6 月之前
父節點
當前提交
0fcdc700ac

+ 0 - 12
client/mobile/components/Exam/ExamAdmin.tsx

@@ -117,18 +117,6 @@ export default function ExamAdmin() {
       };
     }, [classroom, socketRoom.joinRoom, socketRoom.leaveRoom]);
 
-  // // 获取当前题目状态
-  // useEffect(() => {
-  //   if (!classroom) return;
-
-  //   const fetchCurrentQuestion = async () => {
-  //     const question = await answerManagement.getCurrentQuestion(classroom);
-  //     setCurrentQuestion(question);
-  //   };
-
-  //   fetchCurrentQuestion();
-  // }, [classroom, lastMessage]);
-
   // 监听答题消息并更新答案
   useEffect(() => {
     if (!classroom || !currentDate) return;

+ 69 - 21
client/mobile/components/Exam/ExamCard.tsx

@@ -23,6 +23,7 @@ export default function ExamCard() {
     socketRoom: { joinRoom, leaveRoom },
     answerManagement,
     currentQuestion,
+    setCurrentQuestion,
     lastMessage,
     userAnswers
   } = useSocketClient(classroom as string);
@@ -47,23 +48,61 @@ export default function ExamCard() {
     enabled: !!classroom
   });
 
-  useEffect(() => {
+  // 初始化答题卡数据
+  const initExamCard = useCallback(async () => {
     if (!classroom || !user?.username) {
       globalThis.location.href = '/exam';
       return;
     }
+    
     if (classroomData && classroomData.status !== ClassroomStatus.OPEN) {
       message.error('该教室已关闭');
       globalThis.location.href = '/exam';
       return;
     }
-  }, [classroom, user, classroomData]);
+    
+    // 获取当前问题并更新状态
+    try {
+      const question = await answerManagement.getCurrentQuestion(classroom);
+      setCurrentQuestion(question);
+    } catch (error) {
+      console.error('获取当前问题失败:', error);
+    }
+    
+    // 获取用户回答记录
+    if (user?.id) {
+      try {
+        const answers = await answerManagement.getUserAnswers(classroom, String(user.id));
+        if (answers && answers.length > 0) {
+          const lastAnswer = answers[answers.length - 1];
+          setHoldingStock(lastAnswer.holdingStock);
+          setHoldingCash(lastAnswer.holdingCash);
+          
+          const records = answers.map((answer: Answer, index: number): AnswerRecord => ({
+            date: answer.date,
+            price: String(answer.price || '0'),
+            holdingStock: answer.holdingStock,
+            holdingCash: answer.holdingCash,
+            profitAmount: answer.profitAmount || 0,
+            profitPercent: answer.profitPercent || 0,
+            index: index + 1
+          }));
+          
+          setAnswerRecords(records);
+        }
+      } catch (error) {
+        console.error('获取用户回答记录失败:', error);
+      }
+    }
+  }, [classroom, user, classroomData, answerManagement]);
 
   // 加入/离开房间
   useEffect(() => {
     if (!classroom) return;
     
     joinRoom(classroom);
+
+    initExamCard();
     
     return () => {
       leaveRoom(classroom);
@@ -168,27 +207,36 @@ export default function ExamCard() {
     }
   }, [classroom, user, currentDate, currentPrice, answerManagement, currentQuestion]);
 
-  // 初始化用户的答题记录
+  // 监听socket消息更新答题记录
   useEffect(() => {
-    if (userAnswers && userAnswers.length > 0) {
-      const lastAnswer = userAnswers[userAnswers.length - 1];
-      setHoldingStock(lastAnswer.holdingStock);
-      setHoldingCash(lastAnswer.holdingCash);
-      
-      // 直接使用 userAnswers 中已计算好的数据
-      const records = userAnswers.map((answer: Answer, index: number): AnswerRecord => ({
-        date: answer.date,
-        price: String(answer.price || '0'),
-        holdingStock: answer.holdingStock,
-        holdingCash: answer.holdingCash,
-        profitAmount: answer.profitAmount || 0,
-        profitPercent: answer.profitPercent || 0,
-        index: index + 1
-      }));
-      
-      setAnswerRecords(records);
+    if (!lastMessage?.message) return;
+
+    const { type } = lastMessage.message;
+    if (type === 'answer' || type === 'question' || type === 'restart') {
+      if (classroom && user?.id) {
+        answerManagement.getUserAnswers(classroom, String(user.id))
+          .then(answers => {
+            if (answers && answers.length > 0) {
+              const lastAnswer = answers[answers.length - 1];
+              setHoldingStock(lastAnswer.holdingStock);
+              setHoldingCash(lastAnswer.holdingCash);
+              
+              const records = answers.map((answer: Answer, index: number): AnswerRecord => ({
+                date: answer.date,
+                price: String(answer.price || '0'),
+                holdingStock: answer.holdingStock,
+                holdingCash: answer.holdingCash,
+                profitAmount: answer.profitAmount || 0,
+                profitPercent: answer.profitPercent || 0,
+                index: index + 1
+              }));
+              
+              setAnswerRecords(records);
+            }
+          });
+      }
     }
-  }, [userAnswers]);
+  }, [lastMessage, classroom, user, answerManagement]);
 
   if (isLoading || !classroomData) {
     return <div className="flex items-center justify-center min-h-screen">加载中...</div>;

+ 32 - 42
client/mobile/components/Exam/hooks/useSocketClient.ts

@@ -56,21 +56,6 @@ function getAnswers(client: Socket | null, roomId: string, questionId: string):
   });
 }
 
-function getCurrentQuestion(client: Socket | null, roomId: string, getAnswersFn: typeof getAnswers): Promise<QuizState | null> {
-  if (!client) return Promise.resolve(null);
-
-  return getAnswersFn(client, roomId, 'current_state').then(answers => {
-    const currentState = answers[0];
-    if (currentState) {
-      return {
-        date: currentState.date || '',
-        price: currentState.price || '0'
-      };
-    }
-    return null;
-  });
-}
-
 export function useSocketClient(roomId: string | null) {
   const { token } = useAuth();
   const queryClient = useQueryClient();
@@ -154,7 +139,6 @@ export function useSocketClient(roomId: string | null) {
     if (!client || !roomId) return;
 
     const handleQuestionUpdate = (question:QuizState ) => {
-      // const question = await getCurrentQuestion(client, roomId, getAnswers);
       setCurrentQuestion(question);
     };
 
@@ -216,14 +200,9 @@ export function useSocketClient(roomId: string | null) {
 
       // 获取该用户的所有历史答案
       const dates = Object.keys(pricesData).sort();
-      const allAnswers = await Promise.all(
-        dates.map(date => getAnswers(client, roomId, date))
-      );
-
-      // 计算收益
-      const userAnswers = allAnswers
-        .flat()
-        .filter((a: Answer) => a.userId === userId)
+      const allUserAnswers = await getUserAnswers(roomId, userId);
+      const userAnswers = allUserAnswers
+        .filter((a: Answer) => dates.includes(a.date || ''))
         .map((a: Answer) => ({
           ...a,
           price: pricesData[a.date || '']?.price || '0'
@@ -263,10 +242,14 @@ export function useSocketClient(roomId: string | null) {
       }, (success: boolean) => {
         callback?.(success);
 
-        setUserAnswers([...userAnswers, answerWithProfit])
+        
+        // 提交成功后获取最新回答记录
+        if (success && roomId && answerWithProfit.userId) {
+          setUserAnswers([...userAnswers, answerWithProfit])
+        }
       });
     }, '存储答案失败');
-  }, [client, getAnswers]);
+  }, [client]);
 
   // 清理房间数据
   const cleanupRoom = useCallback(async (roomId: string, questionId?: string) => {
@@ -342,14 +325,6 @@ export function useSocketClient(roomId: string | null) {
     }, '获取答案失败');
   }, [client]);
 
-  // 获取当前题目 (封装为useCallback)
-  const getCurrentQuestionCallback = useCallback((roomId: string): Promise<QuizState | null> => {
-    if (!client) return Promise.resolve(null);
-    return handleAsyncOperation(async () => {
-      return getCurrentQuestion(client, roomId, getAnswers);
-    }, '获取当前题目状态失败');
-  }, [client]);
-
   // 清理socket连接
   useEffect(() => {
     return () => {
@@ -369,26 +344,42 @@ export function useSocketClient(roomId: string | null) {
   };
 
   // 获取用户答案
-  const getUserAnswers = useCallback(async (examId: string, userId: string): Promise<Answer[]> => {
-    if (!client || !examId || !userId) return Promise.resolve([]);
+  const getUserAnswers = useCallback(async (roomId: string, userId: string): Promise<Answer[]> => {
+    if (!client || !roomId || !userId) return Promise.resolve([]);
 
     return handleAsyncOperation(async () => {
       return new Promise((resolve) => {
-        client.emit('exam:getUserAnswers', { examId, userId }, (answers: Answer[]) => {
+        client.emit('exam:getUserAnswers', { roomId, userId }, (answers: Answer[]) => {
           resolve(answers || []);
         });
       });
     }, '获取用户答案失败');
   }, [client]);
 
+  const getCurrentQuestion = useCallback(async (roomId: string): Promise<QuizState> => {
+      if (!client) return Promise.reject(new Error('Socket not connected'));
+      
+      return handleAsyncOperation(async () => {
+        return new Promise((resolve, reject) => {
+          client.emit('exam:currentQuestion', { roomId }, (question: QuizState) => {
+            if (!question) {
+              reject(new Error('No current question available'));
+              return;
+            }
+            resolve(question);
+          });
+        });
+      }, '获取当前问题失败');
+  }, [client])
+
   const answerManagement = {
     storeAnswer,
     getAnswers: getAnswersCallback,
     cleanupRoom,
     sendNextQuestion,
-    getCurrentQuestion: getCurrentQuestionCallback,
     getPriceHistory,
-    getUserAnswers
+    getUserAnswers,
+    getCurrentQuestion
   };
 
   // 计算累计结果
@@ -414,12 +405,11 @@ export function useSocketClient(roomId: string | null) {
     answerManagement,
     // calculateCumulativeResults,
     currentQuestion,
+    setCurrentQuestion,
     lastMessage,
     userAnswers,
     // 兼容旧版导入
     ...socketRoom,
     ...answerManagement
   };
-}
-
-// 保留原有其他hook实现...
+}

+ 2 - 2
docs/exam.md

@@ -1,11 +1,11 @@
 从管理后台 教室管理,点击 打开股票训练链接
 股票训练链接打开后,创建 教室号 房间
-答题卡链接打开后,加入教室号 房间
+答题卡链接打开后,加入教室号 房间, 调用 getCurrentQuestion 获取当前问题, 调用 getUserAnswers 获取 当前用户 回答记录
 答题卡管理员链接打开后,加入教室号 房间
 股票 点 下一天按钮时,发送 下一天的 日期,收盘价 到房间
 答提卡每次收到消息后,显示最新日期,价格,同时重置 A,B 按钮
 答提卡点击 选A或选B后,提交答案到redis,及发消息到房间
-提交答案后,答题卡答题列表中,刷新显示 当前用户 回答记录
+提交答案后,答题卡答题列表中,通过 getUserAnswers 刷新显示 当前用户 回答记录
 管理员页收到答题消息后显示出来
 管理员点击收卷,从redis读取教室的所有答案,汇总提交后交卷后端api 接口
 最后管理员清理redis,结束所有答题卡连接

+ 43 - 22
server/routes_io_exam.ts

@@ -402,12 +402,7 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
   socket.on('exam:getUserAnswers', async (data: {
     roomId: string;
     userId: string;
-  }, callback: (answers: Array<{
-    userId: string;
-    questionId: string;
-    answer: Answer;
-    timestamp: string;
-  }>) => void) => {
+  }, callback: (answers: Array<Answer>) => void) => {
     try {
       const { roomId, userId } = data;
       const user = socket.user;
@@ -421,27 +416,15 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
       // 从Redis获取该用户的所有答案
       const pattern = `exam:answers:${roomId}:${userId}:*`;
       const keys = await apiClient.redis.keys(pattern);
-      const userAnswers: Array<{
-        userId: string;
-        questionId: string;
-        answer: Answer;
-        timestamp: string;
-      }> = [];
+      const userAnswers: Array<Answer> = [];
 
       // 并行获取所有答案
       await Promise.all(keys.map(async (key: string) => {
         try {
           const answerStr = await apiClient.redis.hget(key, 'data');
           if (answerStr) {
-            const answer = JSON.parse(answerStr);
-            // 从key中提取questionId (第五部分)
-            const questionId = key.split(':')[4];
-            userAnswers.push({
-              userId,
-              questionId,
-              answer,
-              timestamp: await apiClient.redis.hget(key, 'timestamp') || new Date().toISOString()
-            });
+            const answer = JSON.parse(answerStr) as Answer;
+            userAnswers.push(answer)
           }
         } catch (error) {
           console.log('获取用户答案失败:', error);
@@ -455,4 +438,42 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
       callback([]);
     }
   });
-}
+
+  // 获取当前问题
+  socket.on('exam:currentQuestion', async (data: ExamRoomData, callback: (question: QuizState | null) => void) => {
+    try {
+      const { roomId } = data;
+      const user = socket.user;
+      
+      if (!user) {
+        socket.emit('error', '未授权访问');
+        callback(null);
+        return;
+      }
+
+      // 从Redis获取当前问题数据
+      const questionKey = `exam:${roomId}:current_question`;
+      const date = await apiClient.redis.hget(questionKey, 'date');
+      const price = await apiClient.redis.hget(questionKey, 'price');
+
+      if (!date || !price) {
+        callback(null);
+        return;
+      }
+
+      const quizState: QuizState = {
+        id: date,
+        date,
+        price
+      };
+
+      callback(quizState);
+      console.log(`用户 ${user.username} 获取房间 ${roomId} 的当前问题`);
+    } catch (error) {
+      console.error('获取当前问题失败:', error);
+      socket.emit('error', '获取当前问题失败');
+      callback(null);
+    }
+  });
+
+}