**新增功能**: - 新增删除单个错题的接口 (DELETE /api/wrong-questions/:id) - 错题列表每个项添加删除按钮 - 支持单独删除不想要的错题记录 **技术实现**: - 后端: 实现DeleteWrongQuestion处理器,确保用户只能删除自己的错题 - 前端: 添加deleteWrongQuestion API 调用 - UI: 在List.Item中添加actions属性,显示删除按钮 - 删除时显示二次确认弹窗 **注意事项**: - 删除需要确认,防止误操作 - 删除成功后自动刷新列表 - 仅显示操作用户自己的错题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
304 lines
7.2 KiB
Go
304 lines
7.2 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
|
|
}
|
|
|
|
// GetRandomWrongQuestion 获取随机错题进行练习
|
|
func GetRandomWrongQuestion(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 wrongQuestion models.WrongQuestion
|
|
|
|
// 随机获取一个错题
|
|
if err := db.Where("user_id = ?", userID).Order("RANDOM()").Preload("PracticeQuestion").First(&wrongQuestion).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"success": false,
|
|
"message": "暂无错题",
|
|
})
|
|
return
|
|
}
|
|
|
|
// 转换为DTO返回
|
|
dto := convertToDTO(wrongQuestion.PracticeQuestion)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"data": dto,
|
|
})
|
|
}
|
|
|
|
// DeleteWrongQuestion 删除单个错题
|
|
func DeleteWrongQuestion(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()
|
|
|
|
// 删除错题(确保只能删除自己的错题)
|
|
result := db.Where("id = ? AND user_id = ?", wrongQuestionID, userID).Delete(&models.WrongQuestion{})
|
|
if result.Error != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{
|
|
"success": false,
|
|
"message": "删除失败",
|
|
})
|
|
return
|
|
}
|
|
|
|
if result.RowsAffected == 0 {
|
|
c.JSON(http.StatusNotFound, gin.H{
|
|
"success": false,
|
|
"message": "错题不存在",
|
|
})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": true,
|
|
"message": "已删除",
|
|
})
|
|
}
|