Jelajahi Sumber

♻️ refactor(exam): 重构累计结果计算逻辑

- 将calculateCumulativeResults函数从ExamAdmin.tsx提取到共享工具库@/share/utils/calculateCumulativeResults.ts
- 修改函数参数,支持直接接收扁平的答案数组而非嵌套对象
- 优化用户ID处理逻辑,增加类型转换和空值检查

✨ feat(exam): 实现累计结果保存功能

- 服务端使用共享工具函数计算累计结果并保存到数据库
- 添加calculateScoreFromCumulative方法,基于累计收益率计算得分
- 调整提交记录结构,仅保存累计收益数据而非单日详情

📝 docs(utils): 添加累计结果计算函数文档

- 为calculateCumulativeResults函数添加JSDoc注释,说明功能和参数
- 明确函数返回值类型和计算逻辑
yourname 6 bulan lalu
induk
melakukan
315bf6e908

+ 4 - 37
src/client/mobile/components/Exam/ExamAdmin.tsx

@@ -5,6 +5,7 @@ import { Table, Button, message, Input, QRCode, Modal, Tabs } from 'antd';
 import type { GetProp , TableProps} from 'antd';
 import dayjs from 'dayjs';
 import { useSocketClient } from './hooks/useSocketClient';
+import { calculateCumulativeResults } from '@/share/utils/calculateCumulativeResults';
 import type {
   QuizState,
   ExamSocketRoomMessage
@@ -264,40 +265,6 @@ export default function ExamAdmin() {
     }
   ];
 
-  // 计算累计结果的函数
-  const calculateCumulativeResults = (dailyAnswers: {[key: string]: Answer[]}): CumulativeResult[] => {
-    const userResults = new Map<string, CumulativeResult>();
-
-    // 按日期排序
-    const sortedDates = Object.keys(dailyAnswers).sort((a: string, b: string) => 
-      new Date(a).getTime() - new Date(b).getTime()
-    );
-    
-    sortedDates.forEach(date => {
-      const answers = dailyAnswers[date] || [];
-      answers.forEach((answer: Answer) => {
-        const userId = answer.userId;
-        // 直接使用服务端计算好的收益数据
-        const profitAmount = answer.profitAmount || 0;
-        const profitPercent = answer.profitPercent || 0;
-
-        if (!userResults.has(userId.toString())) {
-          userResults.set(userId.toString(), {
-            userId,
-            totalProfitAmount: 0,
-            totalProfitPercent: 0
-          });
-        }
-
-        const currentResult = userResults.get(userId.toString())!;
-        currentResult.totalProfitAmount += profitAmount;
-        currentResult.totalProfitPercent += profitPercent;
-        userResults.set(userId.toString(), currentResult);
-      });
-    });
-
-    return Array.from(userResults.values());
-  };
 
   const items = [
     {
@@ -314,9 +281,9 @@ export default function ExamAdmin() {
       key: 'cumulative',
       label: '累计结果',
       children: <CumulativeResults
-        results={calculateCumulativeResults(dailyAnswers)}
-        columns={resultColumns}
-      />,
+      results={calculateCumulativeResults(Object.values(dailyAnswers).flat())}
+      columns={resultColumns}
+    />,
     },
   ];
 

+ 33 - 18
src/server/socket/services/exam.service.ts

@@ -4,6 +4,7 @@ import { redisService } from './redis.service';
 import { AppDataSource } from '@/server/data-source';
 import { SubmissionRecords } from '@/server/modules/submission/submission-records.entity';
 import { ClassroomData } from '@/server/modules/classroom/classroom-data.entity';
+import { calculateCumulativeResults } from '@/share/utils/calculateCumulativeResults';
 import debug from 'debug';
 import type { Answer } from '@/client/mobile/components/Exam/types';
 
@@ -186,7 +187,7 @@ export class ExamService {
   }
 
   /**
-   * 保存答题结果到提交记录表
+   * 保存答题结果到提交记录表(保存累计结果)
    */
   private async saveAnswersToSubmissionRecords(roomId: string, questionId?: string): Promise<void> {
     try {
@@ -208,23 +209,26 @@ export class ExamService {
       });
       const code = classroomData?.code || null;
 
-      for (const answer of answers) {
-        // 转换Redis中的答案数据为提交记录实体
+      // 使用共享工具函数计算累计结果
+      const cumulativeResults = calculateCumulativeResults(answers);
+
+      // 为每个用户创建一条累计结果记录
+      for (const cumulativeResult of cumulativeResults) {
         const submissionRecord = new SubmissionRecords();
         submissionRecord.classroomNo = roomId;
-        submissionRecord.userId = answer.userId || null;
-        submissionRecord.score = this.calculateScore(answer);
-        submissionRecord.code = code; // 使用从ClassroomData获取的code
-        submissionRecord.trainingDate = answer.date ? new Date(answer.date) : null;
-        submissionRecord.mark = null; // 标记字段,可根据需要设置
-        submissionRecord.status = 1; // 状态:1-正常
-        submissionRecord.holdingStock = answer.holdingStock || null;
-        submissionRecord.holdingCash = answer.holdingCash || null;
-        submissionRecord.price = Number(answer.price);
-        submissionRecord.profitAmount = answer.profitAmount || 0;
-        submissionRecord.profitPercent = answer.profitPercent || 0;
-        submissionRecord.totalProfitAmount = answer.totalProfitAmount || 0;
-        submissionRecord.totalProfitPercent = answer.totalProfitPercent || 0;
+        submissionRecord.userId = cumulativeResult.userId;
+        submissionRecord.score = this.calculateScoreFromCumulative(cumulativeResult);
+        submissionRecord.code = code;
+        submissionRecord.trainingDate = new Date(); // 使用当前日期作为训练日期
+        submissionRecord.mark = null;
+        submissionRecord.status = 1;
+        submissionRecord.holdingStock = null; // 累计结果不保存具体的持股信息
+        submissionRecord.holdingCash = null;  // 累计结果不保存具体的持币信息
+        submissionRecord.price = null;        // 累计结果不保存具体的价格
+        submissionRecord.profitAmount = null; // 累计结果不保存单日收益
+        submissionRecord.profitPercent = null; // 累计结果不保存单日收益率
+        submissionRecord.totalProfitAmount = cumulativeResult.totalProfitAmount;
+        submissionRecord.totalProfitPercent = cumulativeResult.totalProfitPercent;
 
         recordsToSave.push(submissionRecord);
       }
@@ -232,10 +236,10 @@ export class ExamService {
       // 批量保存到数据库
       if (recordsToSave.length > 0) {
         await submissionRecordsRepository.save(recordsToSave);
-        log(`成功保存 ${recordsToSave.length} 条答题记录到数据库,房间: ${roomId}`);
+        log(`成功保存 ${recordsToSave.length} 条累计结果记录到数据库,房间: ${roomId}`);
       }
     } catch (error) {
-      log('保存答题记录到数据库失败:', error);
+      log('保存累计结果记录到数据库失败:', error);
       // 不抛出错误,避免影响正常的清理操作
     }
   }
@@ -251,4 +255,15 @@ export class ExamService {
     }
     return null;
   }
+
+  /**
+   * 根据累计结果计算得分
+   */
+  private calculateScoreFromCumulative(result: { totalProfitAmount: number; totalProfitPercent: number }): number | null {
+    // 根据累计收益率计算得分,收益率越高得分越高
+    if (result.totalProfitPercent !== undefined && result.totalProfitPercent !== null) {
+      return Math.max(0, Math.min(100, 50 + (result.totalProfitPercent * 2)));
+    }
+    return null;
+  }
 }

+ 42 - 0
src/share/utils/calculateCumulativeResults.ts

@@ -0,0 +1,42 @@
+import type { Answer, CumulativeResult } from '@/client/mobile/components/Exam/types';
+
+/**
+ * 计算累计结果(按用户ID分组计算累计收益)
+ * @param answers 答题数据数组
+ * @returns 累计结果数组
+ */
+export function calculateCumulativeResults(answers: Answer[]): CumulativeResult[] {
+  const userResults = new Map<string, CumulativeResult>();
+
+  // 按日期排序
+  const sortedDates = [...new Set(answers.map(a => a.date))].sort((a: string, b: string) => 
+    new Date(a).getTime() - new Date(b).getTime()
+  );
+  
+  sortedDates.forEach(date => {
+    const dateAnswers = answers.filter(answer => answer.date === date);
+    dateAnswers.forEach((answer: Answer) => {
+      const userId = answer.userId?.toString();
+      if (!userId) return;
+      
+      // 直接使用服务端计算好的收益数据
+      const profitAmount = answer.profitAmount || 0;
+      const profitPercent = answer.profitPercent || 0;
+
+      if (!userResults.has(userId)) {
+        userResults.set(userId, {
+          userId: parseInt(userId),
+          totalProfitAmount: 0,
+          totalProfitPercent: 0
+        });
+      }
+
+      const currentResult = userResults.get(userId)!;
+      currentResult.totalProfitAmount += profitAmount;
+      currentResult.totalProfitPercent += profitPercent;
+      userResults.set(userId, currentResult);
+    });
+  });
+
+  return Array.from(userResults.values());
+}