添加共享试卷参与人数统计功能
功能说明: - 后端: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 <noreply@anthropic.com>
This commit is contained in:
parent
3704d52a26
commit
03f3e14f6e
@ -169,10 +169,11 @@ func GetExamList(c *gin.Context) {
|
|||||||
type ExamWithStats struct {
|
type ExamWithStats struct {
|
||||||
models.Exam
|
models.Exam
|
||||||
QuestionCount int `json:"question_count"`
|
QuestionCount int `json:"question_count"`
|
||||||
AttemptCount int `json:"attempt_count"` // 考试次数
|
AttemptCount int `json:"attempt_count"` // 考试次数(当前用户)
|
||||||
BestScore float64 `json:"best_score"` // 最高分
|
BestScore float64 `json:"best_score"` // 最高分(当前用户)
|
||||||
HasInProgressExam bool `json:"has_in_progress_exam"` // 是否有进行中的考试
|
HasInProgressExam bool `json:"has_in_progress_exam"` // 是否有进行中的考试
|
||||||
InProgressRecordID uint `json:"in_progress_record_id,omitempty"` // 进行中的考试记录ID
|
InProgressRecordID uint `json:"in_progress_record_id,omitempty"` // 进行中的考试记录ID
|
||||||
|
ParticipantCount int `json:"participant_count"` // 共享试卷的参与人数(所有用户)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]ExamWithStats, 0, len(exams))
|
result := make([]ExamWithStats, 0, len(exams))
|
||||||
@ -184,12 +185,12 @@ func GetExamList(c *gin.Context) {
|
|||||||
QuestionCount: len(questionIDs),
|
QuestionCount: len(questionIDs),
|
||||||
}
|
}
|
||||||
|
|
||||||
// 查询该试卷的考试记录统计
|
// 查询该试卷的考试记录统计(当前用户)
|
||||||
var count int64
|
var count int64
|
||||||
db.Model(&models.ExamRecord{}).Where("exam_id = ? AND user_id = ?", exam.ID, userID).Count(&count)
|
db.Model(&models.ExamRecord{}).Where("exam_id = ? AND user_id = ?", exam.ID, userID).Count(&count)
|
||||||
stats.AttemptCount = int(count)
|
stats.AttemptCount = int(count)
|
||||||
|
|
||||||
// 查询最高分
|
// 查询最高分(当前用户)
|
||||||
var record models.ExamRecord
|
var record models.ExamRecord
|
||||||
if err := db.Where("exam_id = ? AND user_id = ?", exam.ID, userID).
|
if err := db.Where("exam_id = ? AND user_id = ?", exam.ID, userID).
|
||||||
Order("score DESC").
|
Order("score DESC").
|
||||||
@ -206,6 +207,41 @@ func GetExamList(c *gin.Context) {
|
|||||||
stats.InProgressRecordID = inProgressRecord.ID
|
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)
|
result = append(result, stats)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,8 @@ import {
|
|||||||
PrinterOutlined,
|
PrinterOutlined,
|
||||||
ArrowLeftOutlined,
|
ArrowLeftOutlined,
|
||||||
ShareAltOutlined,
|
ShareAltOutlined,
|
||||||
UserOutlined
|
UserOutlined,
|
||||||
|
TeamOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import * as examApi from '../api/exam'
|
import * as examApi from '../api/exam'
|
||||||
import styles from './ExamManagement.module.less'
|
import styles from './ExamManagement.module.less'
|
||||||
@ -46,6 +47,7 @@ interface ExamListItem {
|
|||||||
best_score: number
|
best_score: number
|
||||||
has_in_progress_exam: boolean
|
has_in_progress_exam: boolean
|
||||||
in_progress_record_id?: number
|
in_progress_record_id?: number
|
||||||
|
participant_count: number // 共享试卷的参与人数
|
||||||
created_at: string
|
created_at: string
|
||||||
is_shared?: boolean
|
is_shared?: boolean
|
||||||
shared_by?: {
|
shared_by?: {
|
||||||
@ -396,6 +398,14 @@ const ExamManagement: React.FC = () => {
|
|||||||
<span>考试次数 {exam.attempt_count}</span>
|
<span>考试次数 {exam.attempt_count}</span>
|
||||||
</Tag>
|
</Tag>
|
||||||
</div>
|
</div>
|
||||||
|
{exam.participant_count > 0 && (
|
||||||
|
<div className={styles.statItem}>
|
||||||
|
<Tag color="blue" className={styles.participantTag}>
|
||||||
|
<TeamOutlined />
|
||||||
|
<span>{exam.participant_count} 人参与</span>
|
||||||
|
</Tag>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{exam.has_in_progress_exam && (
|
{exam.has_in_progress_exam && (
|
||||||
<div className={styles.statItem}>
|
<div className={styles.statItem}>
|
||||||
<Tag color="processing" className={styles.progressTag}>
|
<Tag color="processing" className={styles.progressTag}>
|
||||||
|
|||||||
@ -98,6 +98,14 @@ export type ExamListResponse = Array<{
|
|||||||
best_score: number
|
best_score: number
|
||||||
has_in_progress_exam: boolean
|
has_in_progress_exam: boolean
|
||||||
in_progress_record_id?: number
|
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
|
created_at: string
|
||||||
}>
|
}>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user