瀏覽代碼

已成功实现答题卡管理员初始化功能

yourname 6 月之前
父節點
當前提交
292c73e204
共有 4 個文件被更改,包括 99 次插入81 次删除
  1. 54 39
      client/mobile/components/Exam/ExamAdmin.tsx
  2. 34 31
      client/mobile/components/Exam/ExamCard.tsx
  3. 2 2
      docs/exam.md
  4. 9 9
      server/routes_io_exam.ts

+ 54 - 39
client/mobile/components/Exam/ExamAdmin.tsx

@@ -75,6 +75,7 @@ export default function ExamAdmin() {
     socketRoom,
     answerManagement,
     currentQuestion,
+    setCurrentQuestion,
     // calculateCumulativeResults
   } = useSocketClient(classroom as string);
 
@@ -84,42 +85,63 @@ export default function ExamAdmin() {
   const [currentPrice, setCurrentPrice] = useState('0');
   const [mark, setMark] = useState('');
   const [activeTab, setActiveTab] = useState('current');
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState<string | null>(null);
 
-  // 获取当前题目
-  // const [currentQuestion, setCurrentQuestion] = useState<QuizState | null>(null);
-  const [lastMessage, setLastMessage] = useState<ExamSocketRoomMessage | null>(null);
+  const initExamData = async () => {
+    if (!classroom) return;
+    setLoading(true);
+    setError(null);
+    try {
+      // 获取当前问题
+      const question = await answerManagement.getCurrentQuestion(classroom);
+      if (question) {
+        setCurrentQuestion(question);
 
-  // 初始化socket连接和监听
-  // useEffect(() => {
-  //   if (!classroom) return;
+        // 获取答题记录
+        const answers = await answerManagement.getAnswers(
+          classroom,
+          ''
+        );
+        
+        const processedAnswers = answers.map(answer => ({
+          ...answer,
+          profitAmount: answer.profitAmount || 0,
+          profitPercent: answer.profitPercent || 0,
+          holdingStock: answer.holdingStock || '0',
+          holdingCash: answer.holdingCash || '0'
+        }));
 
-  //   socketRoom.joinRoom(classroom);
+        setAnswers(processedAnswers);
+        setDailyAnswers(prev => ({
+          ...prev,
+          [question.date]: processedAnswers
+        }));
+      }
+    } catch (err) {
+      console.error('初始化答题数据失败:', err);
+      setError('初始化答题数据失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+  
+  // 加入/离开房间
+  useEffect(() => {
+    if (!classroom) return;
     
-  //   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]);
+    socketRoom.joinRoom(classroom);
+    initExamData();
+
+    return () => {
+      socketRoom.leaveRoom(classroom);
+    };
+  }, [classroom, socketRoom.joinRoom, socketRoom.leaveRoom]);
+
 
   // 监听答题消息并更新答案
   useEffect(() => {
-    if (!classroom || !currentDate) return;
+    if (!classroom || !currentDate || !socketRoom.client) return;
 
     const handleAnswerMessage = async () => {
       try {
@@ -146,18 +168,11 @@ export default function ExamAdmin() {
       }
     };
 
-    // 初始获取答案
-    handleAnswerMessage();
-
-    // 订阅答题消息
-    if (socketRoom.client) {
-      socketRoom.client.on('exam:answer', handleAnswerMessage);
-    }
+    socketRoom.client.on('exam:answer', handleAnswerMessage);
 
     return () => {
-      if (socketRoom.client) {
-        socketRoom.client.off('exam:answer', handleAnswerMessage);
-      }
+      if (!socketRoom.client) return;
+      socketRoom.client.off('exam:answer', handleAnswerMessage);
     };
   }, [classroom, currentDate, answerManagement, socketRoom]);
 

+ 34 - 31
client/mobile/components/Exam/ExamCard.tsx

@@ -1,6 +1,6 @@
 import React,{ useState, useCallback, useEffect } from 'react';
 import { useQuery } from '@tanstack/react-query';
-import { useSearchParams } from "react-router";
+import { useSearchParams, useNavigate } from "react-router";
 import dayjs from 'dayjs';
 import { message } from 'antd';
 import { useSocketClient } from './hooks/useSocketClient.ts';
@@ -17,6 +17,7 @@ import { useAuth } from "../../hooks.tsx";
 // 答题卡页面
 export default function ExamCard() {
   const { user } = useAuth();
+  const navigate = useNavigate();
   const [searchParams] = useSearchParams();
   const classroom = searchParams.get('classroom');
   const {
@@ -51,13 +52,15 @@ export default function ExamCard() {
   // 初始化答题卡数据
   const initExamCard = useCallback(async () => {
     if (!classroom || !user?.username) {
-      globalThis.location.href = '/exam';
+      // globalThis.location.href = '/exam';
+      navigate('/mobile')
       return;
     }
     
     if (classroomData && classroomData.status !== ClassroomStatus.OPEN) {
       message.error('该教室已关闭');
-      globalThis.location.href = '/exam';
+      // globalThis.location.href = '/exam';
+      navigate('/mobile')
       return;
     }
     
@@ -191,7 +194,7 @@ export default function ExamCard() {
         await answerManagement.storeAnswer(
           classroom as string,
           currentQuestion?.id || '',
-          user.username,
+          String(user.id),
           answer,
           (success) => {
             if (success) {
@@ -207,36 +210,36 @@ export default function ExamCard() {
     }
   }, [classroom, user, currentDate, currentPrice, answerManagement, currentQuestion]);
 
-  // 监听socket消息更新答题记录
-  useEffect(() => {
-    if (!lastMessage?.message) return;
+  // // 监听socket消息更新答题记录
+  // useEffect(() => {
+  //   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 { 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
-              }));
+  //             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);
-            }
-          });
-      }
-    }
-  }, [lastMessage, classroom, user, answerManagement]);
+  //             setAnswerRecords(records);
+  //           }
+  //         });
+  //     }
+  //   }
+  // }, [lastMessage, classroom, user, answerManagement]);
 
   if (isLoading || !classroomData) {
     return <div className="flex items-center justify-center min-h-screen">加载中...</div>;

+ 2 - 2
docs/exam.md

@@ -1,7 +1,7 @@
 从管理后台 教室管理,点击 打开股票训练链接
 股票训练链接打开后,创建 教室号 房间
-答题卡链接打开后,加入教室号 房间, 调用 getCurrentQuestion 获取当前问题, 调用 getUserAnswers 获取 当前用户 回答记录
-答题卡管理员链接打开后,加入教室号 房间
+答题卡链接打开后,加入教室号 房间, 调用 getCurrentQuestion 获取当前问题, 调用 getUserAnswers 获取 当前用户 回答记录
+答题卡管理员链接打开后,加入教室号 房间 后, 调用 getCurrentQuestion 获取当前问题, 调用 getAnswers 获取 当前所有用户 的所有问题 回答记录
 股票 点 下一天按钮时,发送 下一天的 日期,收盘价 到房间
 答提卡每次收到消息后,显示最新日期,价格,同时重置 A,B 按钮
 答提卡点击 选A或选B后,提交答案到redis,及发消息到房间

+ 9 - 9
server/routes_io_exam.ts

@@ -183,14 +183,14 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
       }
 
       // 存储答案到Redis
-      // 新key格式: exam:answers:{roomId}:{userId}:{questionId}
+      // 新key格式: exam:{roomId}:answers:{userId}:{questionId}
       // 优点:
       // 1. 更清晰的命名空间划分(exam:answers开头)
       // 2. 支持两种查询模式:
-      //    - 按用户查询: exam:answers:{roomId}:{userId}:*
-      //    - 按题目查询: exam:answers:{roomId}:*:{questionId}
+      //    - 按用户查询: exam:{roomId}:answers:{userId}:*
+      //    - 按题目查询: exam:{roomId}:answers:*:{questionId}
       // 3. 每个用户答案独立存储,避免大hash性能问题
-      const answerKey = `exam:answers:${roomId}:${user.id}:${questionId}`;
+      const answerKey = `exam:${roomId}:answers:${user.id}:${questionId}`;
       await apiClient.redis.hset(answerKey, 'data', JSON.stringify({
         username: user.username,
         date: answer.date,
@@ -245,11 +245,11 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
 
       // 从Redis获取答案
       // 支持两种查询模式:
-      // 1. 查询特定问题的所有答案: exam:answers:{roomId}:{questionId}:*
-      // 2. 查询房间的所有答案: exam:answers:{roomId}:*
+      // 1. 查询特定问题的所有答案: exam:{roomId}:answers:{questionId}:*
+      // 2. 查询房间的所有答案: exam:{roomId}:answers:*
       const pattern = questionId
-        ? `exam:answers:${roomId}:*:${questionId}`
-        : `exam:answers:${roomId}:*`;
+        ? `exam:${roomId}:answers:*:${questionId}`
+        : `exam:${roomId}:answers:*`;
       
       const keys = await apiClient.redis.keys(pattern);
       const formattedAnswers:Answer[] = [];
@@ -414,7 +414,7 @@ export function setupExamEvents({ socket, apiClient }: Variables) {
       }
 
       // 从Redis获取该用户的所有答案
-      const pattern = `exam:answers:${roomId}:${userId}:*`;
+      const pattern = `exam:${roomId}:answers:${userId}:*`;
       const keys = await apiClient.redis.keys(pattern);
       const userAnswers: Array<Answer> = [];