2
0
فهرست منبع

✨ feat(exam): 新增考试卡片日涨幅显示功能

- 添加dailyGain状态变量存储单日涨幅数据
- 在ExamCard组件中显示涨幅信息,正数红色,负数绿色
- 完善QuizState类型定义,增加gain字段表示单日涨幅
- 调整相关接口和数据存储逻辑,支持涨幅数据的传递与保存
- 在问题更新和初始化时同步设置涨幅数据
- 清理考试数据时重置涨幅数据为0
yourname 6 ماه پیش
والد
کامیت
dbbbe65881

+ 7 - 0
src/client/mobile/components/Exam/ExamCard.tsx

@@ -23,6 +23,7 @@ export default function ExamCard() {
   } = useSocketClient(classroom as string);
   const [currentDate, setCurrentDate] = useState('');
   const [currentPrice, setCurrentPrice] = useState('0');
+  const [dailyGain, setDailyGain] = useState(0);
   const [holdingStock, setHoldingStock] = useState('0');
   const [holdingCash, setHoldingCash] = useState('0');
   const [isStarted, setIsStarted] = useState(false);
@@ -67,6 +68,7 @@ export default function ExamCard() {
     if (question) {
       setCurrentDate(question.date);
       setCurrentPrice(String(question.price));
+      setDailyGain(question.gain || 0);
       setIsStarted(true);
     }
     
@@ -225,6 +227,7 @@ export default function ExamCard() {
     const handleQuestionUpdate = (question:QuizState ) => {
       setCurrentDate(question.date);
       setCurrentPrice(String(question.price));
+      setDailyGain(question.gain || 0);
       setIsStarted(true);
     };
 
@@ -241,6 +244,7 @@ export default function ExamCard() {
     const handleCleaned = () => {
       setCurrentDate('');
       setCurrentPrice('0');
+      setDailyGain(0);
       setHoldingStock('0');
       setHoldingCash('0');
       setIsStarted(false);
@@ -287,6 +291,9 @@ export default function ExamCard() {
             {isStarted ? (
               <>
                 <span>价格: {currentPrice}</span>
+                <span className={dailyGain >= 0 ? 'text-red-500' : 'text-green-500'}>
+                  涨幅: {dailyGain > 0 ? '+' : ''}{dailyGain.toFixed(2)}%
+                </span>
               </>
             ) : (
               <div className="text-blue-600">

+ 1 - 0
src/client/mobile/components/Exam/types.ts

@@ -23,6 +23,7 @@ export interface QuizState {
   date: string;
   price: number | string;
   id?: string; // 新增可选id字段
+  gain?: number; // 单日涨幅
 }
 
 export type ExamSocketMessageType = 'question' | 'answer' | 'settlement' | 'submit' | 'restart';

+ 4 - 2
src/client/mobile/components/stock/hooks/useStockSocketClient.ts

@@ -7,13 +7,14 @@ interface ExamData {
   question: {
     date: string;
     price: number;
+    gain?: number;
   };
 }
 
 interface StockSocketClient {
   // connect(): void;
   // disconnect(): void;
-  pushExamData(data: { roomId: string; question: { date: string; price: number } }): void;
+  pushExamData(data: { roomId: string; question: { date: string; price: number; gain?: number } }): void;
   error: Error | null;
   isConnected: boolean;
 }
@@ -80,7 +81,8 @@ export function useStockSocket(classRoom: string | null): StockSocketClient {
         roomId: data.roomId,
         question: {
           date: data.question.date,
-          price: data.question.price
+          price: data.question.price,
+          gain: data.question.gain
         }
       });
     }

+ 2 - 1
src/client/mobile/components/stock/stock_main.tsx

@@ -56,7 +56,8 @@ export function StockMain() {
         roomId: classroom, 
         question: {
           date: currentDate,
-          price: profitSummary.dailyStats.close
+          price: profitSummary.dailyStats.close,
+          gain: profitSummary.dailyStats.change
         }
       });
     }

+ 2 - 2
src/server/socket/services/exam.service.ts

@@ -53,14 +53,14 @@ export class ExamService {
     }
   }
 
-  async pushQuestion(socket: AuthenticatedSocket, roomId: string, question: { date: string; price: string }) {
+  async pushQuestion(socket: AuthenticatedSocket, roomId: string, question: { date: string; price: string; gain?: number }) {
     try {
       if (!socket.user) throw new Error('用户未认证');
       
       await redisService.storeCurrentQuestion(roomId, question);
       await redisService.storePrice(roomId, question.date, question.price);
       
-      const quizState = { id: question.date, date: question.date, price: question.price };
+      const quizState = { id: question.date, date: question.date, price: question.price, gain: question.gain };
       socket.to(roomId).emit('exam:question', quizState);
       
       log(`用户 ${socket.user.username} 在房间 ${roomId} 推送题目`);

+ 4 - 2
src/server/socket/services/redis.service.ts

@@ -27,18 +27,19 @@ export class RedisService {
   }
 
   // 考试相关的方法
-  async storeCurrentQuestion(roomId: string, question: { date: string; price: string }) {
+  async storeCurrentQuestion(roomId: string, question: { date: string; price: string; gain?: number }) {
     const key = `exam:${roomId}:current_question`;
     await this.client.hset(key, {
       date: question.date,
       price: question.price,
+      gain: question.gain ? question.gain.toString() : '0',
       updated_at: new Date().toISOString(),
     });
     // 设置过期时间,24小时
     await this.client.expire(key, 24 * 60 * 60);
   }
 
-  async getCurrentQuestion(roomId: string): Promise<{ date: string; price: string } | null> {
+  async getCurrentQuestion(roomId: string): Promise<{ date: string; price: string; gain?: number } | null> {
     const key = `exam:${roomId}:current_question`;
     const data = await this.client.hgetall(key);
     
@@ -49,6 +50,7 @@ export class RedisService {
     return {
       date: data.date,
       price: data.price,
+      gain: data.gain ? parseFloat(data.gain) : undefined,
     };
   }
 

+ 2 - 0
src/server/socket/types/socket.types.ts

@@ -36,12 +36,14 @@ export interface ExamPriceData {
 export interface QuizContent {
   date: string;
   price: string;
+  gain?: number;
 }
 
 export interface QuizState {
   id: string;
   date: string;
   price: string;
+  gain?: number;
 }
 
 export interface Answer {