From 03f3e14f6e83f01f17292ddeaafaf410c1f39fe7 Mon Sep 17 00:00:00 2001 From: yanlongqi Date: Mon, 1 Dec 2025 21:27:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=85=B1=E4=BA=AB=E8=AF=95?= =?UTF-8?q?=E5=8D=B7=E5=8F=82=E4=B8=8E=E4=BA=BA=E6=95=B0=E7=BB=9F=E8=AE=A1?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 功能说明: - 后端:GetExamList 接口新增 participant_count 字段 - 前端:试卷卡片显示"X 人参与"标签 - 统计逻辑:计算原始试卷和所有分享副本的不同用户数 实现细节: - 自动识别原始试卷和分享副本 - 统计所有已完成考试的不同用户 - 使用团队图标(TeamOutlined)展示参与人数 修改文件: - internal/handlers/exam_handler.go: 添加参与人数统计逻辑 - web/src/types/exam.ts: 更新类型定义 - web/src/pages/ExamManagement.tsx: 显示参与人数标签 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- internal/handlers/exam_handler.go | 44 ++++++++++++++++++++++++++++--- web/src/pages/ExamManagement.tsx | 12 ++++++++- web/src/types/exam.ts | 8 ++++++ 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/internal/handlers/exam_handler.go b/internal/handlers/exam_handler.go index bdc3aac..16982df 100644 --- a/internal/handlers/exam_handler.go +++ b/internal/handlers/exam_handler.go @@ -169,10 +169,11 @@ func GetExamList(c *gin.Context) { type ExamWithStats struct { models.Exam QuestionCount int `json:"question_count"` - AttemptCount int `json:"attempt_count"` // 考试次数 - BestScore float64 `json:"best_score"` // 最高分 + AttemptCount int `json:"attempt_count"` // 考试次数(当前用户) + BestScore float64 `json:"best_score"` // 最高分(当前用户) HasInProgressExam bool `json:"has_in_progress_exam"` // 是否有进行中的考试 InProgressRecordID uint `json:"in_progress_record_id,omitempty"` // 进行中的考试记录ID + ParticipantCount int `json:"participant_count"` // 共享试卷的参与人数(所有用户) } result := make([]ExamWithStats, 0, len(exams)) @@ -184,12 +185,12 @@ func GetExamList(c *gin.Context) { QuestionCount: len(questionIDs), } - // 查询该试卷的考试记录统计 + // 查询该试卷的考试记录统计(当前用户) var count int64 db.Model(&models.ExamRecord{}).Where("exam_id = ? AND user_id = ?", exam.ID, userID).Count(&count) stats.AttemptCount = int(count) - // 查询最高分 + // 查询最高分(当前用户) var record models.ExamRecord if err := db.Where("exam_id = ? AND user_id = ?", exam.ID, userID). Order("score DESC"). @@ -206,6 +207,41 @@ func GetExamList(c *gin.Context) { stats.InProgressRecordID = inProgressRecord.ID } + // 计算共享试卷的参与人数 + // 获取所有相关试卷ID(包括原始试卷和所有分享副本) + var relatedExamIDs []uint + relatedExamIDs = append(relatedExamIDs, exam.ID) + + // 如果这是被分享的试卷,找到原始试卷 + if exam.IsShared && exam.SharedByID != nil { + var originalExam models.Exam + if err := db.Where("user_id = ? AND question_ids = ? AND deleted_at IS NULL", + *exam.SharedByID, exam.QuestionIDs).First(&originalExam).Error; err == nil { + relatedExamIDs = append(relatedExamIDs, originalExam.ID) + } + } + + // 查找所有基于该试卷的分享副本 + var sharedExams []models.Exam + sharedByID := exam.UserID + if exam.IsShared && exam.SharedByID != nil { + sharedByID = *exam.SharedByID + } + if err := db.Where("shared_by_id = ? AND question_ids = ? AND deleted_at IS NULL", + sharedByID, exam.QuestionIDs).Find(&sharedExams).Error; err == nil { + for _, se := range sharedExams { + relatedExamIDs = append(relatedExamIDs, se.ID) + } + } + + // 统计所有相关试卷的已完成考试的不同用户数 + var participantCount int64 + db.Model(&models.ExamRecord{}). + Where("exam_id IN ? AND status = ?", relatedExamIDs, "graded"). + Distinct("user_id"). + Count(&participantCount) + stats.ParticipantCount = int(participantCount) + result = append(result, stats) } } diff --git a/web/src/pages/ExamManagement.tsx b/web/src/pages/ExamManagement.tsx index eacd6ea..a0313f9 100644 --- a/web/src/pages/ExamManagement.tsx +++ b/web/src/pages/ExamManagement.tsx @@ -30,7 +30,8 @@ import { PrinterOutlined, ArrowLeftOutlined, ShareAltOutlined, - UserOutlined + UserOutlined, + TeamOutlined } from '@ant-design/icons' import * as examApi from '../api/exam' import styles from './ExamManagement.module.less' @@ -46,6 +47,7 @@ interface ExamListItem { best_score: number has_in_progress_exam: boolean in_progress_record_id?: number + participant_count: number // 共享试卷的参与人数 created_at: string is_shared?: boolean shared_by?: { @@ -396,6 +398,14 @@ const ExamManagement: React.FC = () => { 考试次数 {exam.attempt_count} + {exam.participant_count > 0 && ( +
+ + + {exam.participant_count} 人参与 + +
+ )} {exam.has_in_progress_exam && (
diff --git a/web/src/types/exam.ts b/web/src/types/exam.ts index 5dd1f46..877f2df 100644 --- a/web/src/types/exam.ts +++ b/web/src/types/exam.ts @@ -98,6 +98,14 @@ export type ExamListResponse = Array<{ best_score: number has_in_progress_exam: boolean in_progress_record_id?: number + participant_count: number // 共享试卷的参与人数 + is_shared: boolean // 是否为分享的试卷 + shared_by_id?: number // 分享人ID + shared_by?: { // 分享人信息 + id: number + username: string + nickname: string + } created_at: string }>