AnCao/internal/handlers/wrong_question_handler.go
yanlongqi 6446508954 实现完整的错题本功能模块
后端实现:
- 创建错题数据模型和数据库表结构
- 实现错题记录、查询、统计、标记和清空API
- 答题错误时自动记录到错题本
- 支持重复错误累计次数和更新时间

前端实现:
- 创建错题本页面,支持查看、筛选和管理错题
- 实现错题统计展示(总数、已掌握、待掌握)
- 支持标记已掌握、清空错题本和重做题目
- 在首页和个人中心添加错题本入口
- 完整的响应式设计适配移动端和PC端

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 13:44:51 +08:00

234 lines
5.6 KiB
Go

package handlers
import (
"ankao/internal/database"
"ankao/internal/models"
"encoding/json"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// GetWrongQuestions 获取错题列表
func GetWrongQuestions(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "未登录",
})
return
}
db := database.GetDB()
var wrongQuestions []models.WrongQuestion
// 查询参数
isMastered := c.Query("is_mastered") // "true" 或 "false"
questionType := c.Query("type") // 题型筛选
query := db.Where("user_id = ?", userID).Preload("PracticeQuestion")
// 筛选是否已掌握
if isMastered == "true" {
query = query.Where("is_mastered = ?", true)
} else if isMastered == "false" {
query = query.Where("is_mastered = ?", false)
}
// 按最后错误时间倒序
if err := query.Order("last_wrong_time DESC").Find(&wrongQuestions).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "查询失败",
})
return
}
// 转换为DTO
dtos := make([]models.WrongQuestionDTO, 0, len(wrongQuestions))
for _, wq := range wrongQuestions {
// 题型筛选
if questionType != "" && mapBackendToFrontendType(wq.PracticeQuestion.Type) != questionType {
continue
}
// 解析答案
var wrongAnswer, correctAnswer interface{}
json.Unmarshal([]byte(wq.WrongAnswer), &wrongAnswer)
json.Unmarshal([]byte(wq.CorrectAnswer), &correctAnswer)
dto := models.WrongQuestionDTO{
ID: wq.ID,
QuestionID: wq.QuestionID,
Question: convertToDTO(wq.PracticeQuestion),
WrongAnswer: wrongAnswer,
CorrectAnswer: correctAnswer,
WrongCount: wq.WrongCount,
LastWrongTime: wq.LastWrongTime,
IsMastered: wq.IsMastered,
}
dtos = append(dtos, dto)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": dtos,
"total": len(dtos),
})
}
// GetWrongQuestionStats 获取错题统计
func GetWrongQuestionStats(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "未登录",
})
return
}
db := database.GetDB()
var wrongQuestions []models.WrongQuestion
if err := db.Where("user_id = ?", userID).Preload("PracticeQuestion").Find(&wrongQuestions).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "查询失败",
})
return
}
stats := models.WrongQuestionStats{
TotalWrong: len(wrongQuestions),
Mastered: 0,
NotMastered: 0,
TypeStats: make(map[string]int),
CategoryStats: make(map[string]int),
}
for _, wq := range wrongQuestions {
if wq.IsMastered {
stats.Mastered++
} else {
stats.NotMastered++
}
// 统计题型
frontendType := mapBackendToFrontendType(wq.PracticeQuestion.Type)
stats.TypeStats[frontendType]++
// 统计分类
stats.CategoryStats[wq.PracticeQuestion.TypeName]++
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"data": stats,
})
}
// MarkWrongQuestionMastered 标记错题为已掌握
func MarkWrongQuestionMastered(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "未登录",
})
return
}
wrongQuestionID := c.Param("id")
db := database.GetDB()
var wrongQuestion models.WrongQuestion
if err := db.Where("id = ? AND user_id = ?", wrongQuestionID, userID).First(&wrongQuestion).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{
"success": false,
"message": "错题不存在",
})
return
}
wrongQuestion.IsMastered = true
if err := db.Save(&wrongQuestion).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "更新失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "已标记为掌握",
})
}
// ClearWrongQuestions 清空错题本
func ClearWrongQuestions(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{
"success": false,
"message": "未登录",
})
return
}
db := database.GetDB()
// 删除用户所有错题记录
if err := db.Where("user_id = ?", userID).Delete(&models.WrongQuestion{}).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"success": false,
"message": "清空失败",
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "错题本已清空",
})
}
// recordWrongQuestion 记录错题(内部函数,在答题错误时调用)
func recordWrongQuestion(userID, questionID uint, userAnswer, correctAnswer interface{}) error {
db := database.GetDB()
// 将答案序列化为JSON
wrongAnswerJSON, _ := json.Marshal(userAnswer)
correctAnswerJSON, _ := json.Marshal(correctAnswer)
// 查找是否已存在该错题
var existingWrong models.WrongQuestion
result := db.Where("user_id = ? AND question_id = ?", userID, questionID).First(&existingWrong)
if result.Error == nil {
// 已存在,更新错误次数和时间
existingWrong.WrongCount++
existingWrong.LastWrongTime = time.Now()
existingWrong.WrongAnswer = string(wrongAnswerJSON)
existingWrong.CorrectAnswer = string(correctAnswerJSON)
existingWrong.IsMastered = false // 重新标记为未掌握
return db.Save(&existingWrong).Error
}
// 不存在,创建新记录
newWrong := models.WrongQuestion{
UserID: userID,
QuestionID: questionID,
WrongAnswer: string(wrongAnswerJSON),
CorrectAnswer: string(correctAnswerJSON),
WrongCount: 1,
LastWrongTime: time.Now(),
IsMastered: false,
}
return db.Create(&newWrong).Error
}