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>
199 lines
6.6 KiB
Go
199 lines
6.6 KiB
Go
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
|
||
}
|