Files
ai_english/client/lib/features/vocabulary/models/learning_stats_model.dart
2025-11-17 14:09:17 +08:00

713 lines
19 KiB
Dart

/// 学习统计数据
class LearningStats {
/// 用户ID
final String userId;
/// 总学习天数
final int totalStudyDays;
/// 连续学习天数
final int currentStreak;
/// 最长连续学习天数
final int maxStreak;
/// 总学习单词数
final int totalWordsLearned;
/// 总复习单词数
final int totalWordsReviewed;
/// 总学习时间(分钟)
final int totalStudyTimeMinutes;
/// 平均每日学习单词数
final double averageDailyWords;
/// 平均每日学习时间(分钟)
final double averageDailyMinutes;
/// 学习准确率
final double accuracyRate;
/// 完成的词汇书数量
final int completedBooks;
/// 当前学习的词汇书数量
final int currentBooks;
/// 掌握的单词数
final int masteredWords;
/// 学习中的单词数
final int learningWords;
/// 需要复习的单词数
final int reviewWords;
/// 本周学习统计
final WeeklyStats weeklyStats;
/// 本月学习统计
final MonthlyStats monthlyStats;
/// 学习等级
final int level;
/// 当前等级经验值
final int currentExp;
/// 升级所需经验值
final int nextLevelExp;
/// 最后学习时间
final DateTime? lastStudyTime;
/// 创建时间
final DateTime createdAt;
/// 更新时间
final DateTime updatedAt;
/// 每日学习记录
final List<DailyStudyRecord> dailyRecords;
/// 学习成就
final List<Achievement> achievements;
/// 排行榜信息
final Leaderboard? leaderboard;
const LearningStats({
required this.userId,
required this.totalStudyDays,
required this.currentStreak,
required this.maxStreak,
required this.totalWordsLearned,
required this.totalWordsReviewed,
required this.totalStudyTimeMinutes,
required this.averageDailyWords,
required this.averageDailyMinutes,
required this.accuracyRate,
required this.completedBooks,
required this.currentBooks,
required this.masteredWords,
required this.learningWords,
required this.reviewWords,
required this.weeklyStats,
required this.monthlyStats,
required this.level,
required this.currentExp,
required this.nextLevelExp,
this.lastStudyTime,
required this.createdAt,
required this.updatedAt,
required this.dailyRecords,
required this.achievements,
this.leaderboard,
});
factory LearningStats.fromJson(Map<String, dynamic> json) {
return LearningStats(
userId: json['userId'] as String,
totalStudyDays: json['totalStudyDays'] as int,
currentStreak: json['currentStreak'] as int,
maxStreak: json['maxStreak'] as int,
totalWordsLearned: json['totalWordsLearned'] as int,
totalWordsReviewed: json['totalWordsReviewed'] as int,
totalStudyTimeMinutes: json['totalStudyTimeMinutes'] as int,
averageDailyWords: (json['averageDailyWords'] as num).toDouble(),
averageDailyMinutes: (json['averageDailyMinutes'] as num).toDouble(),
accuracyRate: (json['accuracyRate'] as num).toDouble(),
completedBooks: json['completedBooks'] as int,
currentBooks: json['currentBooks'] as int,
masteredWords: json['masteredWords'] as int,
learningWords: json['learningWords'] as int,
reviewWords: json['reviewWords'] as int,
weeklyStats: WeeklyStats.fromJson(json['weeklyStats'] as Map<String, dynamic>),
monthlyStats: MonthlyStats.fromJson(json['monthlyStats'] as Map<String, dynamic>),
level: json['level'] as int,
currentExp: json['currentExp'] as int,
nextLevelExp: json['nextLevelExp'] as int,
lastStudyTime: json['lastStudyTime'] != null
? DateTime.parse(json['lastStudyTime'] as String)
: null,
createdAt: DateTime.parse(json['createdAt'] as String),
updatedAt: DateTime.parse(json['updatedAt'] as String),
dailyRecords: (json['dailyRecords'] as List<dynamic>?)
?.map((e) => DailyStudyRecord.fromJson(e as Map<String, dynamic>))
.toList() ?? [],
achievements: (json['achievements'] as List<dynamic>?)
?.map((e) => Achievement.fromJson(e as Map<String, dynamic>))
.toList() ?? [],
leaderboard: json['leaderboard'] != null
? Leaderboard.fromJson(json['leaderboard'] as Map<String, dynamic>)
: null,
);
}
Map<String, dynamic> toJson() {
return {
'userId': userId,
'totalStudyDays': totalStudyDays,
'currentStreak': currentStreak,
'maxStreak': maxStreak,
'totalWordsLearned': totalWordsLearned,
'totalWordsReviewed': totalWordsReviewed,
'totalStudyTimeMinutes': totalStudyTimeMinutes,
'averageDailyWords': averageDailyWords,
'averageDailyMinutes': averageDailyMinutes,
'accuracyRate': accuracyRate,
'completedBooks': completedBooks,
'currentBooks': currentBooks,
'masteredWords': masteredWords,
'learningWords': learningWords,
'reviewWords': reviewWords,
'weeklyStats': weeklyStats.toJson(),
'monthlyStats': monthlyStats.toJson(),
'level': level,
'currentExp': currentExp,
'nextLevelExp': nextLevelExp,
'lastStudyTime': lastStudyTime?.toIso8601String(),
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
'dailyRecords': dailyRecords.map((e) => e.toJson()).toList(),
'achievements': achievements.map((e) => e.toJson()).toList(),
'leaderboard': leaderboard?.toJson(),
};
}
LearningStats copyWith({
String? userId,
int? totalStudyDays,
int? currentStreak,
int? maxStreak,
int? totalWordsLearned,
int? totalWordsReviewed,
int? totalStudyTimeMinutes,
double? averageDailyWords,
double? averageDailyMinutes,
double? accuracyRate,
int? completedBooks,
int? currentBooks,
int? masteredWords,
int? learningWords,
int? reviewWords,
WeeklyStats? weeklyStats,
MonthlyStats? monthlyStats,
int? level,
int? currentExp,
int? nextLevelExp,
DateTime? lastStudyTime,
DateTime? createdAt,
DateTime? updatedAt,
List<DailyStudyRecord>? dailyRecords,
List<Achievement>? achievements,
Leaderboard? leaderboard,
}) {
return LearningStats(
userId: userId ?? this.userId,
totalStudyDays: totalStudyDays ?? this.totalStudyDays,
currentStreak: currentStreak ?? this.currentStreak,
maxStreak: maxStreak ?? this.maxStreak,
totalWordsLearned: totalWordsLearned ?? this.totalWordsLearned,
totalWordsReviewed: totalWordsReviewed ?? this.totalWordsReviewed,
totalStudyTimeMinutes: totalStudyTimeMinutes ?? this.totalStudyTimeMinutes,
averageDailyWords: averageDailyWords ?? this.averageDailyWords,
averageDailyMinutes: averageDailyMinutes ?? this.averageDailyMinutes,
accuracyRate: accuracyRate ?? this.accuracyRate,
completedBooks: completedBooks ?? this.completedBooks,
currentBooks: currentBooks ?? this.currentBooks,
masteredWords: masteredWords ?? this.masteredWords,
learningWords: learningWords ?? this.learningWords,
reviewWords: reviewWords ?? this.reviewWords,
weeklyStats: weeklyStats ?? this.weeklyStats,
monthlyStats: monthlyStats ?? this.monthlyStats,
level: level ?? this.level,
currentExp: currentExp ?? this.currentExp,
nextLevelExp: nextLevelExp ?? this.nextLevelExp,
lastStudyTime: lastStudyTime ?? this.lastStudyTime,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
dailyRecords: dailyRecords ?? this.dailyRecords,
achievements: achievements ?? this.achievements,
leaderboard: leaderboard ?? this.leaderboard,
);
}
/// 获取学习进度百分比
double get progressPercentage {
if (nextLevelExp == 0) return 0.0;
return currentExp / nextLevelExp;
}
/// 获取总学习时间(小时)
double get totalStudyHours {
return totalStudyTimeMinutes / 60.0;
}
/// 获取平均每日学习时间(小时)
double get averageDailyHours {
return averageDailyMinutes / 60.0;
}
}
/// 周学习统计
class WeeklyStats {
/// 本周学习天数
final int studyDays;
/// 本周学习单词数
final int wordsLearned;
/// 本周复习单词数
final int wordsReviewed;
/// 本周学习时间(分钟)
final int studyTimeMinutes;
/// 本周准确率
final double accuracyRate;
/// 每日学习记录
final List<DailyStudyRecord> dailyRecords;
const WeeklyStats({
required this.studyDays,
required this.wordsLearned,
required this.wordsReviewed,
required this.studyTimeMinutes,
required this.accuracyRate,
required this.dailyRecords,
});
factory WeeklyStats.fromJson(Map<String, dynamic> json) {
return WeeklyStats(
studyDays: json['studyDays'] as int,
wordsLearned: json['wordsLearned'] as int,
wordsReviewed: json['wordsReviewed'] as int,
studyTimeMinutes: json['studyTimeMinutes'] as int,
accuracyRate: (json['accuracyRate'] as num).toDouble(),
dailyRecords: (json['dailyRecords'] as List<dynamic>?)
?.map((e) => DailyStudyRecord.fromJson(e as Map<String, dynamic>))
.toList() ?? [],
);
}
Map<String, dynamic> toJson() {
return {
'studyDays': studyDays,
'wordsLearned': wordsLearned,
'wordsReviewed': wordsReviewed,
'studyTimeMinutes': studyTimeMinutes,
'accuracyRate': accuracyRate,
'dailyRecords': dailyRecords.map((e) => e.toJson()).toList(),
};
}
/// 获取本周学习时间(小时)
double get studyHours {
return studyTimeMinutes / 60.0;
}
}
/// 月学习统计
class MonthlyStats {
/// 本月学习天数
final int studyDays;
/// 本月学习单词数
final int wordsLearned;
/// 本月复习单词数
final int wordsReviewed;
/// 本月学习时间(分钟)
final int studyTimeMinutes;
/// 本月准确率
final double accuracyRate;
/// 本月完成的词汇书数
final int completedBooks;
/// 周统计记录
final List<WeeklyStats> weeklyRecords;
const MonthlyStats({
required this.studyDays,
required this.wordsLearned,
required this.wordsReviewed,
required this.studyTimeMinutes,
required this.accuracyRate,
required this.completedBooks,
required this.weeklyRecords,
});
factory MonthlyStats.fromJson(Map<String, dynamic> json) {
return MonthlyStats(
studyDays: json['studyDays'] as int,
wordsLearned: json['wordsLearned'] as int,
wordsReviewed: json['wordsReviewed'] as int,
studyTimeMinutes: json['studyTimeMinutes'] as int,
accuracyRate: (json['accuracyRate'] as num).toDouble(),
completedBooks: json['completedBooks'] as int,
weeklyRecords: (json['weeklyRecords'] as List<dynamic>?)
?.map((e) => WeeklyStats.fromJson(e as Map<String, dynamic>))
.toList() ?? [],
);
}
Map<String, dynamic> toJson() {
return {
'studyDays': studyDays,
'wordsLearned': wordsLearned,
'wordsReviewed': wordsReviewed,
'studyTimeMinutes': studyTimeMinutes,
'accuracyRate': accuracyRate,
'completedBooks': completedBooks,
'weeklyRecords': weeklyRecords.map((e) => e.toJson()).toList(),
};
}
/// 获取本月学习时间(小时)
double get studyHours {
return studyTimeMinutes / 60.0;
}
}
/// 每日学习记录
class DailyStudyRecord {
/// 日期
final DateTime date;
/// 学习单词数
final int wordsLearned;
/// 复习单词数
final int wordsReviewed;
/// 学习时间(分钟)
final int studyTimeMinutes;
/// 准确率
final double accuracyRate;
/// 完成的测试数
final int testsCompleted;
/// 获得的经验值
final int expGained;
/// 学习的词汇书ID列表
final List<String> vocabularyBookIds;
const DailyStudyRecord({
required this.date,
required this.wordsLearned,
required this.wordsReviewed,
required this.studyTimeMinutes,
required this.accuracyRate,
required this.testsCompleted,
required this.expGained,
required this.vocabularyBookIds,
});
factory DailyStudyRecord.fromJson(Map<String, dynamic> json) {
// 支持两种命名格式:驼峰命名和蛇形命名
return DailyStudyRecord(
date: DateTime.parse(json['date'] as String),
wordsLearned: (json['wordsLearned'] ?? json['words_learned'] ?? json['new_words_learned'] ?? 0) as int,
wordsReviewed: (json['wordsReviewed'] ?? json['words_reviewed'] ?? 0) as int,
studyTimeMinutes: (json['studyTimeMinutes'] ?? json['total_study_time_seconds'] != null
? (json['total_study_time_seconds'] as int) ~/ 60
: 0) as int,
accuracyRate: ((json['accuracyRate'] ?? json['average_accuracy'] ?? 0) as num).toDouble(),
testsCompleted: (json['testsCompleted'] ?? json['session_count'] ?? 0) as int,
expGained: (json['expGained'] ?? json['experience_gained'] ?? 0) as int,
vocabularyBookIds: (json['vocabularyBookIds'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ?? [],
);
}
Map<String, dynamic> toJson() {
return {
'date': date.toIso8601String(),
'wordsLearned': wordsLearned,
'wordsReviewed': wordsReviewed,
'studyTimeMinutes': studyTimeMinutes,
'accuracyRate': accuracyRate,
'testsCompleted': testsCompleted,
'expGained': expGained,
'vocabularyBookIds': vocabularyBookIds,
};
}
/// 获取学习时间(小时)
double get studyHours {
return studyTimeMinutes / 60.0;
}
}
/// 学习成就
class Achievement {
/// 成就ID
final String id;
/// 成就名称
final String name;
/// 成就描述
final String description;
/// 成就图标
final String icon;
/// 成就类型
final AchievementType type;
/// 是否已解锁
final bool isUnlocked;
/// 解锁时间
final DateTime? unlockedAt;
/// 进度值
final int progress;
/// 目标值
final int target;
/// 奖励经验值
final int rewardExp;
const Achievement({
required this.id,
required this.name,
required this.description,
required this.icon,
required this.type,
required this.isUnlocked,
this.unlockedAt,
required this.progress,
required this.target,
required this.rewardExp,
});
factory Achievement.fromJson(Map<String, dynamic> json) {
return Achievement(
id: json['id'] as String,
name: json['name'] as String,
description: json['description'] as String,
icon: json['icon'] as String,
type: AchievementType.values.firstWhere(
(e) => e.name == json['type'],
orElse: () => AchievementType.special,
),
isUnlocked: json['isUnlocked'] as bool,
unlockedAt: json['unlockedAt'] != null
? DateTime.parse(json['unlockedAt'] as String)
: null,
progress: json['progress'] as int,
target: json['target'] as int,
rewardExp: json['rewardExp'] as int,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'description': description,
'icon': icon,
'type': type.name,
'isUnlocked': isUnlocked,
'unlockedAt': unlockedAt?.toIso8601String(),
'progress': progress,
'target': target,
'rewardExp': rewardExp,
};
}
/// 复制并修改部分属性
Achievement copyWith({
String? id,
String? name,
String? description,
String? icon,
AchievementType? type,
bool? isUnlocked,
DateTime? unlockedAt,
int? progress,
int? target,
int? rewardExp,
}) {
return Achievement(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
icon: icon ?? this.icon,
type: type ?? this.type,
isUnlocked: isUnlocked ?? this.isUnlocked,
unlockedAt: unlockedAt ?? this.unlockedAt,
progress: progress ?? this.progress,
target: target ?? this.target,
rewardExp: rewardExp ?? this.rewardExp,
);
}
/// 获取进度百分比
double get progressPercentage {
if (target == 0) return 0.0;
return (progress / target).clamp(0.0, 1.0);
}
}
/// 成就类型
enum AchievementType {
/// 学习天数
studyDays,
/// 学习单词数
wordsLearned,
/// 连续学习
streak,
/// 完成词汇书
booksCompleted,
/// 测试成绩
testScore,
/// 学习时间
studyTime,
/// 特殊成就
special,
}
/// 学习排行榜
class Leaderboard {
/// 排行榜类型
final LeaderboardType type;
/// 时间范围
final LeaderboardPeriod period;
/// 排行榜条目
final List<LeaderboardEntry> entries;
/// 用户排名
final int? userRank;
/// 更新时间
final DateTime updatedAt;
const Leaderboard({
required this.type,
required this.period,
required this.entries,
this.userRank,
required this.updatedAt,
});
factory Leaderboard.fromJson(Map<String, dynamic> json) {
return Leaderboard(
type: LeaderboardType.values.firstWhere(
(e) => e.name == json['type'],
orElse: () => LeaderboardType.wordsLearned,
),
period: LeaderboardPeriod.values.firstWhere(
(e) => e.name == json['period'],
orElse: () => LeaderboardPeriod.weekly,
),
entries: (json['entries'] as List<dynamic>?)
?.map((e) => LeaderboardEntry.fromJson(e as Map<String, dynamic>))
.toList() ?? [],
userRank: json['userRank'] as int?,
updatedAt: DateTime.parse(json['updatedAt'] as String),
);
}
Map<String, dynamic> toJson() {
return {
'type': type.name,
'period': period.name,
'entries': entries.map((e) => e.toJson()).toList(),
'userRank': userRank,
'updatedAt': updatedAt.toIso8601String(),
};
}
}
/// 排行榜条目
class LeaderboardEntry {
/// 排名
final int rank;
/// 用户ID
final String userId;
/// 用户名
final String username;
/// 用户头像
final String? avatar;
/// 分数
final int score;
/// 等级
final int level;
const LeaderboardEntry({
required this.rank,
required this.userId,
required this.username,
this.avatar,
required this.score,
required this.level,
});
factory LeaderboardEntry.fromJson(Map<String, dynamic> json) {
return LeaderboardEntry(
rank: json['rank'] as int,
userId: json['userId'] as String,
username: json['username'] as String,
avatar: json['avatar'] as String?,
score: json['score'] as int,
level: json['level'] as int,
);
}
Map<String, dynamic> toJson() {
return {
'rank': rank,
'userId': userId,
'username': username,
'avatar': avatar,
'score': score,
'level': level,
};
}
}
/// 排行榜类型
enum LeaderboardType {
/// 学习单词数
wordsLearned,
/// 连续学习天数
streak,
/// 学习时间
studyTime,
/// 测试分数
testScore,
}
/// 排行榜时间范围
enum LeaderboardPeriod {
/// 每日
daily,
/// 每周
weekly,
/// 每月
monthly,
/// 全部时间
allTime,
}