AnCao/internal/models/wrong_question.go
yanlongqi 2fbeb23947 优化错题本功能和UI设计
1. 错题本系统重构:
   - 新增错题服务层 (wrong_question_service.go)
   - 实现智能推荐算法(基于掌握度和错误次数)
   - 添加掌握度追踪机制(连续答对6次标记为已掌握)
   - 支持错题筛选和排序功能
   - 新增错题统计趋势分析

2. UI优化:
   - 美化错题本界面,采用毛玻璃卡片设计
   - 添加四宫格统计卡片(错题总数、已掌握、未掌握、掌握率)
   - 优化筛选和操作按钮布局
   - 使用条状进度条显示掌握度
   - 改进响应式设计,优化移动端体验

3. 功能完善:
   - 修复判断题答案显示问题
   - 当掌握率100%时禁用"开始练习"按钮
   - 删除测试文件和 nul 文件
   - 更新文档 (CLAUDE.md)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 04:20:42 +08:00

199 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package models
import (
"database/sql/driver"
"encoding/json"
"time"
"gorm.io/gorm"
)
// WrongQuestion 错题记录
type WrongQuestion struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `gorm:"index;not null" json:"user_id"`
QuestionID uint `gorm:"index;not null" json:"question_id"`
FirstWrongTime time.Time `json:"first_wrong_time"`
LastWrongTime time.Time `json:"last_wrong_time"`
TotalWrongCount int `gorm:"default:1" json:"total_wrong_count"`
MasteryLevel int `gorm:"default:0" json:"mastery_level"` // 0-100
ConsecutiveCorrect int `gorm:"default:0" json:"consecutive_correct"`
IsMastered bool `gorm:"default:false" json:"is_mastered"`
Tags QuestionTags `gorm:"type:text" json:"tags"` // JSON 存储标签
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// 关联
PracticeQuestion *PracticeQuestion `gorm:"foreignKey:QuestionID;references:ID" json:"question,omitempty"`
History []WrongQuestionHistory `gorm:"foreignKey:WrongQuestionID" json:"history,omitempty"`
}
// WrongQuestionHistory 错误历史记录
type WrongQuestionHistory struct {
ID uint `gorm:"primarykey" json:"id"`
WrongQuestionID uint `gorm:"index;not null" json:"wrong_question_id"`
UserAnswer string `gorm:"type:text;not null" json:"user_answer"` // JSON 存储
CorrectAnswer string `gorm:"type:text;not null" json:"correct_answer"` // JSON 存储
AnsweredAt time.Time `gorm:"index" json:"answered_at"`
TimeSpent int `json:"time_spent"` // 答题用时(秒)
IsCorrect bool `json:"is_correct"`
}
// WrongQuestionTag 错题标签
type WrongQuestionTag struct {
ID uint `gorm:"primarykey" json:"id"`
UserID uint `gorm:"index;not null" json:"user_id"`
Name string `gorm:"size:50;not null" json:"name"`
Color string `gorm:"size:20;default:'#1890ff'" json:"color"`
Description string `gorm:"size:200" json:"description"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// QuestionTags 标签列表(用于 JSON 存储)
type QuestionTags []string
// Scan 从数据库读取
func (t *QuestionTags) Scan(value interface{}) error {
if value == nil {
*t = []string{}
return nil
}
bytes, ok := value.([]byte)
if !ok {
str, ok := value.(string)
if !ok {
*t = []string{}
return nil
}
bytes = []byte(str)
}
if len(bytes) == 0 || string(bytes) == "null" {
*t = []string{}
return nil
}
return json.Unmarshal(bytes, t)
}
// Value 写入数据库
func (t QuestionTags) Value() (driver.Value, error) {
if len(t) == 0 {
return "[]", nil
}
bytes, err := json.Marshal(t)
return string(bytes), err
}
// TableName 指定表名
func (WrongQuestion) TableName() string {
return "wrong_questions"
}
// TableName 指定表名
func (WrongQuestionHistory) TableName() string {
return "wrong_question_history"
}
// TableName 指定表名
func (WrongQuestionTag) TableName() string {
return "wrong_question_tags"
}
// WrongQuestionDTO 错题数据传输对象
type WrongQuestionDTO struct {
ID uint `json:"id"`
QuestionID uint `json:"question_id"`
Question *PracticeQuestionDTO `json:"question"`
FirstWrongTime time.Time `json:"first_wrong_time"`
LastWrongTime time.Time `json:"last_wrong_time"`
TotalWrongCount int `json:"total_wrong_count"`
MasteryLevel int `json:"mastery_level"`
ConsecutiveCorrect int `json:"consecutive_correct"`
IsMastered bool `json:"is_mastered"`
Tags []string `json:"tags"`
RecentHistory []WrongQuestionHistoryDTO `json:"recent_history,omitempty"` // 最近3次历史
}
// WrongQuestionHistoryDTO 错误历史 DTO
type WrongQuestionHistoryDTO struct {
ID uint `json:"id"`
UserAnswer interface{} `json:"user_answer"`
CorrectAnswer interface{} `json:"correct_answer"`
AnsweredAt time.Time `json:"answered_at"`
TimeSpent int `json:"time_spent"`
IsCorrect bool `json:"is_correct"`
}
// WrongQuestionStats 错题统计
type WrongQuestionStats struct {
TotalWrong int `json:"total_wrong"` // 总错题数
Mastered int `json:"mastered"` // 已掌握数
NotMastered int `json:"not_mastered"` // 未掌握数
NeedReview int `json:"need_review"` // 需要复习数
TypeStats map[string]int `json:"type_stats"` // 按题型统计
CategoryStats map[string]int `json:"category_stats"` // 按分类统计
MasteryLevelDist map[string]int `json:"mastery_level_dist"` // 掌握度分布
TagStats map[string]int `json:"tag_stats"` // 按标签统计
TrendData []TrendPoint `json:"trend_data"` // 错题趋势
}
// TrendPoint 趋势数据点
type TrendPoint struct {
Date string `json:"date"`
Count int `json:"count"`
}
// RecordWrongAnswer 记录错误答案
func (wq *WrongQuestion) RecordWrongAnswer() {
now := time.Now()
if wq.FirstWrongTime.IsZero() {
wq.FirstWrongTime = now
}
wq.LastWrongTime = now
wq.TotalWrongCount++
wq.ConsecutiveCorrect = 0 // 重置连续答对次数
wq.IsMastered = false // 重新标记为未掌握
wq.MasteryLevel = 0 // 重置掌握度
}
// RecordCorrectAnswer 记录正确答案
func (wq *WrongQuestion) RecordCorrectAnswer() {
wq.ConsecutiveCorrect++
// 根据连续答对次数更新掌握度每答对一次增加16.67%
// 连续答对6次即达到100%
wq.MasteryLevel = (wq.ConsecutiveCorrect * 100) / 6
if wq.MasteryLevel > 100 {
wq.MasteryLevel = 100
}
// 连续答对6次标记为已掌握
if wq.ConsecutiveCorrect >= 6 {
wq.IsMastered = true
wq.MasteryLevel = 100
}
}
// AddTag 添加标签
func (wq *WrongQuestion) AddTag(tag string) {
for _, t := range wq.Tags {
if t == tag {
return // 标签已存在
}
}
wq.Tags = append(wq.Tags, tag)
}
// RemoveTag 移除标签
func (wq *WrongQuestion) RemoveTag(tag string) {
newTags := []string{}
for _, t := range wq.Tags {
if t != tag {
newTags = append(newTags, t)
}
}
wq.Tags = newTags
}