This commit is contained in:
sjk
2025-11-17 14:09:17 +08:00
commit 31e46c5bf6
479 changed files with 109324 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
class DailyStats {
final int wordsLearned;
final int studyTimeMinutes;
DailyStats({
required this.wordsLearned,
required this.studyTimeMinutes,
});
factory DailyStats.fromJson(Map<String, dynamic> json) {
return DailyStats(
wordsLearned: (json['wordsLearned'] as num?)?.toInt() ?? 0,
studyTimeMinutes: (json['studyTimeMinutes'] as num?)?.toInt() ?? 0,
);
}
}

View File

@@ -0,0 +1,141 @@
import 'package:json_annotation/json_annotation.dart';
part 'learning_session_model.g.dart';
/// 学习难度
enum StudyDifficulty {
@JsonValue('forgot')
forgot, // 完全忘记
@JsonValue('hard')
hard, // 困难
@JsonValue('good')
good, // 一般
@JsonValue('easy')
easy, // 容易
@JsonValue('perfect')
perfect, // 完美
}
/// 学习会话
@JsonSerializable()
class LearningSession {
@JsonKey(fromJson: _idFromJson)
final String id;
@JsonKey(name: 'user_id', fromJson: _userIdFromJson)
final String userId;
@JsonKey(name: 'book_id')
final String bookId;
@JsonKey(name: 'daily_goal')
final int dailyGoal;
@JsonKey(name: 'new_words_count')
final int newWordsCount;
@JsonKey(name: 'review_count')
final int reviewCount;
@JsonKey(name: 'mastered_count')
final int masteredCount;
@JsonKey(name: 'started_at')
final DateTime startedAt;
@JsonKey(name: 'completed_at')
final DateTime? completedAt;
const LearningSession({
required this.id,
required this.userId,
required this.bookId,
required this.dailyGoal,
this.newWordsCount = 0,
this.reviewCount = 0,
this.masteredCount = 0,
required this.startedAt,
this.completedAt,
});
factory LearningSession.fromJson(Map<String, dynamic> json) =>
_$LearningSessionFromJson(json);
Map<String, dynamic> toJson() => _$LearningSessionToJson(this);
static String _idFromJson(dynamic value) => value.toString();
static String _userIdFromJson(dynamic value) => value.toString();
}
/// 今日学习任务
@JsonSerializable()
class DailyLearningTasks {
@JsonKey(name: 'newWords')
final List<int> newWords; // 新单词ID列表
@JsonKey(name: 'reviewWords', fromJson: _reviewWordsFromJson)
final List<dynamic> reviewWords; // 复习单词进度列表
@JsonKey(name: 'masteredCount')
final int masteredCount; // 已掌握数量
@JsonKey(name: 'totalWords')
final int totalWords; // 总单词数
final double progress; // 整体进度百分比
const DailyLearningTasks({
required this.newWords,
required this.reviewWords,
required this.masteredCount,
required this.totalWords,
required this.progress,
});
factory DailyLearningTasks.fromJson(Map<String, dynamic> json) =>
_$DailyLearningTasksFromJson(json);
Map<String, dynamic> toJson() => _$DailyLearningTasksToJson(this);
/// 待学习总数
int get totalTasks => newWords.length + reviewWords.length;
/// 是否完成
bool get isCompleted => totalTasks == 0;
static List<dynamic> _reviewWordsFromJson(dynamic value) {
if (value == null) return [];
if (value is List) return value;
return [];
}
}
/// 学习统计
@JsonSerializable()
class LearningStatistics {
final int todayNewWords; // 今日新学单词数
final int todayReview; // 今日复习单词数
final int todayMastered; // 今日掌握单词数
final int totalLearned; // 总学习单词数
final int totalMastered; // 总掌握单词数
final double avgProficiency; // 平均熟练度
final int streakDays; // 连续学习天数
const LearningStatistics({
required this.todayNewWords,
required this.todayReview,
required this.todayMastered,
required this.totalLearned,
required this.totalMastered,
required this.avgProficiency,
required this.streakDays,
});
factory LearningStatistics.fromJson(Map<String, dynamic> json) =>
_$LearningStatisticsFromJson(json);
Map<String, dynamic> toJson() => _$LearningStatisticsToJson(this);
}
/// 学习结果
@JsonSerializable()
class StudyResult {
final String wordId;
final StudyDifficulty difficulty;
final int studyTime; // 学习时长(毫秒)
const StudyResult({
required this.wordId,
required this.difficulty,
required this.studyTime,
});
factory StudyResult.fromJson(Map<String, dynamic> json) =>
_$StudyResultFromJson(json);
Map<String, dynamic> toJson() => _$StudyResultToJson(this);
}

View File

@@ -0,0 +1,98 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'learning_session_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LearningSession _$LearningSessionFromJson(Map<String, dynamic> json) =>
LearningSession(
id: LearningSession._idFromJson(json['id']),
userId: LearningSession._userIdFromJson(json['user_id']),
bookId: json['book_id'] as String,
dailyGoal: (json['daily_goal'] as num).toInt(),
newWordsCount: (json['new_words_count'] as num?)?.toInt() ?? 0,
reviewCount: (json['review_count'] as num?)?.toInt() ?? 0,
masteredCount: (json['mastered_count'] as num?)?.toInt() ?? 0,
startedAt: DateTime.parse(json['started_at'] as String),
completedAt: json['completed_at'] == null
? null
: DateTime.parse(json['completed_at'] as String),
);
Map<String, dynamic> _$LearningSessionToJson(LearningSession instance) =>
<String, dynamic>{
'id': instance.id,
'user_id': instance.userId,
'book_id': instance.bookId,
'daily_goal': instance.dailyGoal,
'new_words_count': instance.newWordsCount,
'review_count': instance.reviewCount,
'mastered_count': instance.masteredCount,
'started_at': instance.startedAt.toIso8601String(),
'completed_at': instance.completedAt?.toIso8601String(),
};
DailyLearningTasks _$DailyLearningTasksFromJson(Map<String, dynamic> json) =>
DailyLearningTasks(
newWords: (json['newWords'] as List<dynamic>)
.map((e) => (e as num).toInt())
.toList(),
reviewWords: DailyLearningTasks._reviewWordsFromJson(json['reviewWords']),
masteredCount: (json['masteredCount'] as num).toInt(),
totalWords: (json['totalWords'] as num).toInt(),
progress: (json['progress'] as num).toDouble(),
);
Map<String, dynamic> _$DailyLearningTasksToJson(DailyLearningTasks instance) =>
<String, dynamic>{
'newWords': instance.newWords,
'reviewWords': instance.reviewWords,
'masteredCount': instance.masteredCount,
'totalWords': instance.totalWords,
'progress': instance.progress,
};
LearningStatistics _$LearningStatisticsFromJson(Map<String, dynamic> json) =>
LearningStatistics(
todayNewWords: (json['todayNewWords'] as num).toInt(),
todayReview: (json['todayReview'] as num).toInt(),
todayMastered: (json['todayMastered'] as num).toInt(),
totalLearned: (json['totalLearned'] as num).toInt(),
totalMastered: (json['totalMastered'] as num).toInt(),
avgProficiency: (json['avgProficiency'] as num).toDouble(),
streakDays: (json['streakDays'] as num).toInt(),
);
Map<String, dynamic> _$LearningStatisticsToJson(LearningStatistics instance) =>
<String, dynamic>{
'todayNewWords': instance.todayNewWords,
'todayReview': instance.todayReview,
'todayMastered': instance.todayMastered,
'totalLearned': instance.totalLearned,
'totalMastered': instance.totalMastered,
'avgProficiency': instance.avgProficiency,
'streakDays': instance.streakDays,
};
StudyResult _$StudyResultFromJson(Map<String, dynamic> json) => StudyResult(
wordId: json['wordId'] as String,
difficulty: $enumDecode(_$StudyDifficultyEnumMap, json['difficulty']),
studyTime: (json['studyTime'] as num).toInt(),
);
Map<String, dynamic> _$StudyResultToJson(StudyResult instance) =>
<String, dynamic>{
'wordId': instance.wordId,
'difficulty': _$StudyDifficultyEnumMap[instance.difficulty]!,
'studyTime': instance.studyTime,
};
const _$StudyDifficultyEnumMap = {
StudyDifficulty.forgot: 'forgot',
StudyDifficulty.hard: 'hard',
StudyDifficulty.good: 'good',
StudyDifficulty.easy: 'easy',
StudyDifficulty.perfect: 'perfect',
};

View File

@@ -0,0 +1,713 @@
/// 学习统计数据
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,
}

View File

@@ -0,0 +1,53 @@
/// 复习模式
enum ReviewMode {
adaptive, // 智能适应
sequential, // 顺序复习
random, // 随机复习
difficulty, // 按难度复习
}
/// 复习结果
class ReviewResult {
final String wordId;
final bool isCorrect;
final DateTime timestamp;
final Duration reviewTime;
ReviewResult({
required this.wordId,
required this.isCorrect,
required this.timestamp,
required this.reviewTime,
});
}
/// 复习会话
class ReviewSession {
final String id;
final DateTime startTime;
final int targetCount;
final ReviewMode mode;
DateTime? endTime;
Map<String, ReviewResult> results = {};
ReviewSession({
required this.id,
required this.startTime,
required this.targetCount,
required this.mode,
this.endTime,
});
double get accuracy {
if (results.isEmpty) return 0.0;
final correctCount = results.values.where((r) => r.isCorrect).length;
return correctCount / results.length;
}
Duration get totalTime {
final end = endTime ?? DateTime.now();
return end.difference(startTime);
}
bool get isCompleted => results.length >= targetCount;
}

View File

@@ -0,0 +1,134 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import 'vocabulary_book_model.dart';
part 'study_plan_model.freezed.dart';
part 'study_plan_model.g.dart';
/// 学习计划模型
@freezed
class StudyPlan with _$StudyPlan {
const factory StudyPlan({
required String id,
required String name,
String? description,
required String userId,
required StudyPlanType type,
required StudyPlanStatus status,
required DateTime startDate,
required DateTime endDate,
required int dailyTarget,
@Default([]) List<String> vocabularyBookIds,
@Default([]) List<VocabularyBook> vocabularyBooks,
@Default(0) int totalWords,
@Default(0) int completedWords,
@Default(0) int currentStreak,
@Default(0) int maxStreak,
@Default([]) List<StudyPlanMilestone> milestones,
@Default({}) Map<String, int> dailyProgress,
required DateTime createdAt,
required DateTime updatedAt,
}) = _StudyPlan;
factory StudyPlan.fromJson(Map<String, dynamic> json) => _$StudyPlanFromJson(json);
}
/// 学习计划类型
enum StudyPlanType {
@JsonValue('daily')
daily,
@JsonValue('weekly')
weekly,
@JsonValue('monthly')
monthly,
@JsonValue('custom')
custom,
@JsonValue('exam_prep')
examPrep,
}
/// 学习计划状态
enum StudyPlanStatus {
@JsonValue('active')
active,
@JsonValue('paused')
paused,
@JsonValue('completed')
completed,
@JsonValue('cancelled')
cancelled,
}
/// 学习计划里程碑
@freezed
class StudyPlanMilestone with _$StudyPlanMilestone {
const factory StudyPlanMilestone({
required String id,
required String title,
String? description,
required int targetWords,
required DateTime targetDate,
@Default(false) bool isCompleted,
DateTime? completedAt,
@Default(0) int currentProgress,
}) = _StudyPlanMilestone;
factory StudyPlanMilestone.fromJson(Map<String, dynamic> json) => _$StudyPlanMilestoneFromJson(json);
}
/// 学习计划统计
@freezed
class StudyPlanStats with _$StudyPlanStats {
const factory StudyPlanStats({
required String planId,
@Default(0) int totalDays,
@Default(0) int studiedDays,
@Default(0) int totalWords,
@Default(0) int learnedWords,
@Default(0) int reviewedWords,
@Default(0) int currentStreak,
@Default(0) int maxStreak,
@Default(0.0) double completionRate,
@Default(0.0) double averageDailyWords,
@Default(0) int remainingDays,
@Default(0) int remainingWords,
DateTime? lastStudyDate,
@Default([]) List<DailyStudyRecord> dailyRecords,
}) = _StudyPlanStats;
factory StudyPlanStats.fromJson(Map<String, dynamic> json) => _$StudyPlanStatsFromJson(json);
}
/// 每日学习记录
@freezed
class DailyStudyRecord with _$DailyStudyRecord {
const factory DailyStudyRecord({
required DateTime date,
@Default(0) int wordsLearned,
@Default(0) int wordsReviewed,
@Default(0) int studyTimeMinutes,
@Default(false) bool targetAchieved,
@Default([]) List<String> vocabularyBookIds,
}) = _DailyStudyRecord;
factory DailyStudyRecord.fromJson(Map<String, dynamic> json) => _$DailyStudyRecordFromJson(json);
}
/// 学习计划模板
@freezed
class StudyPlanTemplate with _$StudyPlanTemplate {
const factory StudyPlanTemplate({
required String id,
required String name,
required String description,
required StudyPlanType type,
required int durationDays,
required int dailyTarget,
@Default([]) List<String> recommendedBookIds,
@Default([]) List<StudyPlanMilestone> milestones,
@Default(0) int difficulty,
@Default([]) List<String> tags,
@Default(false) bool isPopular,
}) = _StudyPlanTemplate;
factory StudyPlanTemplate.fromJson(Map<String, dynamic> json) => _$StudyPlanTemplateFromJson(json);
}

View File

@@ -0,0 +1,398 @@
import 'package:json_annotation/json_annotation.dart';
import 'word_model.dart';
part 'study_session_model.g.dart';
/// 学习模式
enum StudyMode {
@JsonValue('new_words')
newWords, // 学习新单词
@JsonValue('review')
review, // 复习单词
@JsonValue('mixed')
mixed, // 混合模式
@JsonValue('test')
test, // 测试模式
@JsonValue('quick_review')
quickReview, // 快速复习
}
/// 练习类型
enum ExerciseType {
@JsonValue('word_meaning')
wordMeaning, // 单词释义
@JsonValue('meaning_word')
meaningWord, // 释义选单词
@JsonValue('spelling')
spelling, // 拼写练习
@JsonValue('listening')
listening, // 听力练习
@JsonValue('sentence_completion')
sentenceCompletion, // 句子填空
@JsonValue('synonym_antonym')
synonymAntonym, // 同义词反义词
@JsonValue('image_word')
imageWord, // 图片识词
}
/// 答题结果
enum AnswerResult {
@JsonValue('correct')
correct,
@JsonValue('wrong')
wrong,
@JsonValue('skipped')
skipped,
}
/// 学习会话
@JsonSerializable()
class StudySession {
/// 会话ID
final String id;
/// 用户ID
final String userId;
/// 词汇书ID
final String? vocabularyBookId;
/// 学习模式
final StudyMode mode;
/// 目标单词数
final int targetWordCount;
/// 实际学习单词数
final int actualWordCount;
/// 正确答题数
final int correctAnswers;
/// 错误答题数
final int wrongAnswers;
/// 跳过答题数
final int skippedAnswers;
/// 学习时长(秒)
final int durationSeconds;
/// 准确率
final double accuracy;
/// 获得经验值
final int experienceGained;
/// 获得积分
final int pointsGained;
/// 是否完成
final bool isCompleted;
/// 开始时间
final DateTime startedAt;
/// 结束时间
final DateTime? endedAt;
/// 创建时间
final DateTime createdAt;
const StudySession({
required this.id,
required this.userId,
this.vocabularyBookId,
required this.mode,
required this.targetWordCount,
this.actualWordCount = 0,
this.correctAnswers = 0,
this.wrongAnswers = 0,
this.skippedAnswers = 0,
this.durationSeconds = 0,
this.accuracy = 0.0,
this.experienceGained = 0,
this.pointsGained = 0,
this.isCompleted = false,
required this.startedAt,
this.endedAt,
required this.createdAt,
});
factory StudySession.fromJson(Map<String, dynamic> json) => _$StudySessionFromJson(json);
Map<String, dynamic> toJson() => _$StudySessionToJson(this);
StudySession copyWith({
String? id,
String? userId,
String? vocabularyBookId,
StudyMode? mode,
int? targetWordCount,
int? actualWordCount,
int? correctAnswers,
int? wrongAnswers,
int? skippedAnswers,
int? durationSeconds,
double? accuracy,
int? experienceGained,
int? pointsGained,
bool? isCompleted,
DateTime? startedAt,
DateTime? endedAt,
DateTime? createdAt,
}) {
return StudySession(
id: id ?? this.id,
userId: userId ?? this.userId,
vocabularyBookId: vocabularyBookId ?? this.vocabularyBookId,
mode: mode ?? this.mode,
targetWordCount: targetWordCount ?? this.targetWordCount,
actualWordCount: actualWordCount ?? this.actualWordCount,
correctAnswers: correctAnswers ?? this.correctAnswers,
wrongAnswers: wrongAnswers ?? this.wrongAnswers,
skippedAnswers: skippedAnswers ?? this.skippedAnswers,
durationSeconds: durationSeconds ?? this.durationSeconds,
accuracy: accuracy ?? this.accuracy,
experienceGained: experienceGained ?? this.experienceGained,
pointsGained: pointsGained ?? this.pointsGained,
isCompleted: isCompleted ?? this.isCompleted,
startedAt: startedAt ?? this.startedAt,
endedAt: endedAt ?? this.endedAt,
createdAt: createdAt ?? this.createdAt,
);
}
/// 总答题数
int get totalAnswers => correctAnswers + wrongAnswers + skippedAnswers;
/// 计算准确率
double calculateAccuracy() {
if (totalAnswers == 0) return 0.0;
return correctAnswers / totalAnswers;
}
/// 学习时长(分钟)
double get durationMinutes => durationSeconds / 60.0;
}
/// 单词练习记录
@JsonSerializable()
class WordExerciseRecord {
/// 记录ID
final String id;
/// 学习会话ID
final String sessionId;
/// 单词ID
final String wordId;
/// 练习类型
final ExerciseType exerciseType;
/// 用户答案
final String userAnswer;
/// 正确答案
final String correctAnswer;
/// 答题结果
final AnswerResult result;
/// 答题时间(秒)
final int responseTimeSeconds;
/// 提示次数
final int hintCount;
/// 单词信息
final Word? word;
/// 答题时间
final DateTime answeredAt;
const WordExerciseRecord({
required this.id,
required this.sessionId,
required this.wordId,
required this.exerciseType,
required this.userAnswer,
required this.correctAnswer,
required this.result,
required this.responseTimeSeconds,
this.hintCount = 0,
this.word,
required this.answeredAt,
});
factory WordExerciseRecord.fromJson(Map<String, dynamic> json) => _$WordExerciseRecordFromJson(json);
Map<String, dynamic> toJson() => _$WordExerciseRecordToJson(this);
WordExerciseRecord copyWith({
String? id,
String? sessionId,
String? wordId,
ExerciseType? exerciseType,
String? userAnswer,
String? correctAnswer,
AnswerResult? result,
int? responseTimeSeconds,
int? hintCount,
Word? word,
DateTime? answeredAt,
}) {
return WordExerciseRecord(
id: id ?? this.id,
sessionId: sessionId ?? this.sessionId,
wordId: wordId ?? this.wordId,
exerciseType: exerciseType ?? this.exerciseType,
userAnswer: userAnswer ?? this.userAnswer,
correctAnswer: correctAnswer ?? this.correctAnswer,
result: result ?? this.result,
responseTimeSeconds: responseTimeSeconds ?? this.responseTimeSeconds,
hintCount: hintCount ?? this.hintCount,
word: word ?? this.word,
answeredAt: answeredAt ?? this.answeredAt,
);
}
/// 是否正确
bool get isCorrect => result == AnswerResult.correct;
/// 是否错误
bool get isWrong => result == AnswerResult.wrong;
/// 是否跳过
bool get isSkipped => result == AnswerResult.skipped;
/// 答题时间(分钟)
double get responseTimeMinutes => responseTimeSeconds / 60.0;
}
/// 学习统计
@JsonSerializable()
class StudyStatistics {
/// 统计ID
final String id;
/// 用户ID
@JsonKey(name: 'user_id')
final String userId;
/// 统计日期
final DateTime date;
/// 学习会话数
@JsonKey(name: 'session_count')
final int sessionCount;
/// 学习单词数
@JsonKey(name: 'words_studied')
final int wordsStudied;
/// 新学单词数
@JsonKey(name: 'new_words_learned')
final int newWordsLearned;
/// 复习单词数
@JsonKey(name: 'words_reviewed')
final int wordsReviewed;
/// 掌握单词数
@JsonKey(name: 'words_mastered')
final int wordsMastered;
/// 学习时长(秒)
@JsonKey(name: 'total_study_time_seconds')
final int totalStudyTimeSeconds;
/// 正确答题数
@JsonKey(name: 'correct_answers')
final int correctAnswers;
/// 错误答题数
@JsonKey(name: 'wrong_answers')
final int wrongAnswers;
/// 平均准确率
@JsonKey(name: 'average_accuracy')
final double averageAccuracy;
/// 获得经验值
@JsonKey(name: 'experience_gained')
final int experienceGained;
/// 获得积分
@JsonKey(name: 'points_gained')
final int pointsGained;
/// 连续学习天数
@JsonKey(name: 'streak_days')
final int streakDays;
const StudyStatistics({
required this.id,
required this.userId,
required this.date,
this.sessionCount = 0,
this.wordsStudied = 0,
this.newWordsLearned = 0,
this.wordsReviewed = 0,
this.wordsMastered = 0,
this.totalStudyTimeSeconds = 0,
this.correctAnswers = 0,
this.wrongAnswers = 0,
this.averageAccuracy = 0.0,
this.experienceGained = 0,
this.pointsGained = 0,
this.streakDays = 0,
});
factory StudyStatistics.fromJson(Map<String, dynamic> json) => _$StudyStatisticsFromJson(json);
Map<String, dynamic> toJson() => _$StudyStatisticsToJson(this);
StudyStatistics copyWith({
String? id,
String? userId,
DateTime? date,
int? sessionCount,
int? wordsStudied,
int? newWordsLearned,
int? wordsReviewed,
int? wordsMastered,
int? totalStudyTimeSeconds,
int? correctAnswers,
int? wrongAnswers,
double? averageAccuracy,
int? experienceGained,
int? pointsGained,
int? streakDays,
}) {
return StudyStatistics(
id: id ?? this.id,
userId: userId ?? this.userId,
date: date ?? this.date,
sessionCount: sessionCount ?? this.sessionCount,
wordsStudied: wordsStudied ?? this.wordsStudied,
newWordsLearned: newWordsLearned ?? this.newWordsLearned,
wordsReviewed: wordsReviewed ?? this.wordsReviewed,
wordsMastered: wordsMastered ?? this.wordsMastered,
totalStudyTimeSeconds: totalStudyTimeSeconds ?? this.totalStudyTimeSeconds,
correctAnswers: correctAnswers ?? this.correctAnswers,
wrongAnswers: wrongAnswers ?? this.wrongAnswers,
averageAccuracy: averageAccuracy ?? this.averageAccuracy,
experienceGained: experienceGained ?? this.experienceGained,
pointsGained: pointsGained ?? this.pointsGained,
streakDays: streakDays ?? this.streakDays,
);
}
/// 总答题数
int get totalAnswers => correctAnswers + wrongAnswers;
/// 学习时长(分钟)
double get totalStudyTimeMinutes => totalStudyTimeSeconds / 60.0;
/// 学习时长(小时)
double get totalStudyTimeHours => totalStudyTimeSeconds / 3600.0;
}

View File

@@ -0,0 +1,145 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'study_session_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
StudySession _$StudySessionFromJson(Map<String, dynamic> json) => StudySession(
id: json['id'] as String,
userId: json['userId'] as String,
vocabularyBookId: json['vocabularyBookId'] as String?,
mode: $enumDecode(_$StudyModeEnumMap, json['mode']),
targetWordCount: (json['targetWordCount'] as num).toInt(),
actualWordCount: (json['actualWordCount'] as num?)?.toInt() ?? 0,
correctAnswers: (json['correctAnswers'] as num?)?.toInt() ?? 0,
wrongAnswers: (json['wrongAnswers'] as num?)?.toInt() ?? 0,
skippedAnswers: (json['skippedAnswers'] as num?)?.toInt() ?? 0,
durationSeconds: (json['durationSeconds'] as num?)?.toInt() ?? 0,
accuracy: (json['accuracy'] as num?)?.toDouble() ?? 0.0,
experienceGained: (json['experienceGained'] as num?)?.toInt() ?? 0,
pointsGained: (json['pointsGained'] as num?)?.toInt() ?? 0,
isCompleted: json['isCompleted'] as bool? ?? false,
startedAt: DateTime.parse(json['startedAt'] as String),
endedAt: json['endedAt'] == null
? null
: DateTime.parse(json['endedAt'] as String),
createdAt: DateTime.parse(json['createdAt'] as String),
);
Map<String, dynamic> _$StudySessionToJson(StudySession instance) =>
<String, dynamic>{
'id': instance.id,
'userId': instance.userId,
'vocabularyBookId': instance.vocabularyBookId,
'mode': _$StudyModeEnumMap[instance.mode]!,
'targetWordCount': instance.targetWordCount,
'actualWordCount': instance.actualWordCount,
'correctAnswers': instance.correctAnswers,
'wrongAnswers': instance.wrongAnswers,
'skippedAnswers': instance.skippedAnswers,
'durationSeconds': instance.durationSeconds,
'accuracy': instance.accuracy,
'experienceGained': instance.experienceGained,
'pointsGained': instance.pointsGained,
'isCompleted': instance.isCompleted,
'startedAt': instance.startedAt.toIso8601String(),
'endedAt': instance.endedAt?.toIso8601String(),
'createdAt': instance.createdAt.toIso8601String(),
};
const _$StudyModeEnumMap = {
StudyMode.newWords: 'new_words',
StudyMode.review: 'review',
StudyMode.mixed: 'mixed',
StudyMode.test: 'test',
StudyMode.quickReview: 'quick_review',
};
WordExerciseRecord _$WordExerciseRecordFromJson(Map<String, dynamic> json) =>
WordExerciseRecord(
id: json['id'] as String,
sessionId: json['sessionId'] as String,
wordId: json['wordId'] as String,
exerciseType: $enumDecode(_$ExerciseTypeEnumMap, json['exerciseType']),
userAnswer: json['userAnswer'] as String,
correctAnswer: json['correctAnswer'] as String,
result: $enumDecode(_$AnswerResultEnumMap, json['result']),
responseTimeSeconds: (json['responseTimeSeconds'] as num).toInt(),
hintCount: (json['hintCount'] as num?)?.toInt() ?? 0,
word: json['word'] == null
? null
: Word.fromJson(json['word'] as Map<String, dynamic>),
answeredAt: DateTime.parse(json['answeredAt'] as String),
);
Map<String, dynamic> _$WordExerciseRecordToJson(WordExerciseRecord instance) =>
<String, dynamic>{
'id': instance.id,
'sessionId': instance.sessionId,
'wordId': instance.wordId,
'exerciseType': _$ExerciseTypeEnumMap[instance.exerciseType]!,
'userAnswer': instance.userAnswer,
'correctAnswer': instance.correctAnswer,
'result': _$AnswerResultEnumMap[instance.result]!,
'responseTimeSeconds': instance.responseTimeSeconds,
'hintCount': instance.hintCount,
'word': instance.word,
'answeredAt': instance.answeredAt.toIso8601String(),
};
const _$ExerciseTypeEnumMap = {
ExerciseType.wordMeaning: 'word_meaning',
ExerciseType.meaningWord: 'meaning_word',
ExerciseType.spelling: 'spelling',
ExerciseType.listening: 'listening',
ExerciseType.sentenceCompletion: 'sentence_completion',
ExerciseType.synonymAntonym: 'synonym_antonym',
ExerciseType.imageWord: 'image_word',
};
const _$AnswerResultEnumMap = {
AnswerResult.correct: 'correct',
AnswerResult.wrong: 'wrong',
AnswerResult.skipped: 'skipped',
};
StudyStatistics _$StudyStatisticsFromJson(Map<String, dynamic> json) =>
StudyStatistics(
id: json['id'] as String,
userId: json['user_id'] as String,
date: DateTime.parse(json['date'] as String),
sessionCount: (json['session_count'] as num?)?.toInt() ?? 0,
wordsStudied: (json['words_studied'] as num?)?.toInt() ?? 0,
newWordsLearned: (json['new_words_learned'] as num?)?.toInt() ?? 0,
wordsReviewed: (json['words_reviewed'] as num?)?.toInt() ?? 0,
wordsMastered: (json['words_mastered'] as num?)?.toInt() ?? 0,
totalStudyTimeSeconds:
(json['total_study_time_seconds'] as num?)?.toInt() ?? 0,
correctAnswers: (json['correct_answers'] as num?)?.toInt() ?? 0,
wrongAnswers: (json['wrong_answers'] as num?)?.toInt() ?? 0,
averageAccuracy: (json['average_accuracy'] as num?)?.toDouble() ?? 0.0,
experienceGained: (json['experience_gained'] as num?)?.toInt() ?? 0,
pointsGained: (json['points_gained'] as num?)?.toInt() ?? 0,
streakDays: (json['streak_days'] as num?)?.toInt() ?? 0,
);
Map<String, dynamic> _$StudyStatisticsToJson(StudyStatistics instance) =>
<String, dynamic>{
'id': instance.id,
'user_id': instance.userId,
'date': instance.date.toIso8601String(),
'session_count': instance.sessionCount,
'words_studied': instance.wordsStudied,
'new_words_learned': instance.newWordsLearned,
'words_reviewed': instance.wordsReviewed,
'words_mastered': instance.wordsMastered,
'total_study_time_seconds': instance.totalStudyTimeSeconds,
'correct_answers': instance.correctAnswers,
'wrong_answers': instance.wrongAnswers,
'average_accuracy': instance.averageAccuracy,
'experience_gained': instance.experienceGained,
'points_gained': instance.pointsGained,
'streak_days': instance.streakDays,
};

View File

@@ -0,0 +1,409 @@
import 'package:json_annotation/json_annotation.dart';
/// 词汇书主分类
enum VocabularyBookMainCategory {
@JsonValue('academic_stage')
academicStage, // 学段基础词汇
@JsonValue('domestic_test')
domesticTest, // 国内应试类词汇
@JsonValue('international_test')
internationalTest, // 出国考试类词汇
@JsonValue('professional')
professional, // 职业与专业类词汇
@JsonValue('functional')
functional, // 功能型词库
}
/// 学段基础词汇子分类
enum AcademicStageCategory {
@JsonValue('primary_school')
primarySchool, // 小学英语词汇
@JsonValue('junior_high')
juniorHigh, // 初中英语词汇
@JsonValue('senior_high')
seniorHigh, // 高中英语词汇
@JsonValue('university')
university, // 大学英语教材词汇
}
/// 国内应试类词汇子分类
enum DomesticTestCategory {
@JsonValue('cet4')
cet4, // 大学四级
@JsonValue('cet6')
cet6, // 大学六级
@JsonValue('postgraduate')
postgraduate, // 考研英语核心词汇
@JsonValue('tem4')
tem4, // 专四
@JsonValue('tem8')
tem8, // 专八
@JsonValue('adult_bachelor')
adultBachelor, // 专升本
@JsonValue('doctoral')
doctoral, // 考博词汇
@JsonValue('pets')
pets, // 全国等级考试英语词汇
@JsonValue('self_study')
selfStudy, // 成人本科/自考英语词汇
}
/// 出国考试类词汇子分类
enum InternationalTestCategory {
@JsonValue('ielts_academic')
ieltsAcademic, // 雅思学术类
@JsonValue('ielts_general')
ieltsGeneral, // 雅思培训类
@JsonValue('toefl_ibt')
toeflIbt, // 托福iBT
@JsonValue('toeic')
toeic, // 托业
@JsonValue('gre')
gre, // GRE
@JsonValue('gmat')
gmat, // GMAT
@JsonValue('sat')
sat, // SAT
@JsonValue('ssat')
ssat, // SSAT
@JsonValue('act')
act, // ACT
@JsonValue('cambridge_ket')
cambridgeKet, // 剑桥KET
@JsonValue('cambridge_pet')
cambridgePet, // 剑桥PET
@JsonValue('cambridge_fce')
cambridgeFce, // 剑桥FCE
@JsonValue('cambridge_cae')
cambridgeCae, // 剑桥CAE
@JsonValue('cambridge_cpe')
cambridgeCpe, // 剑桥CPE
}
/// 职业与专业类词汇子分类
enum ProfessionalCategory {
@JsonValue('business_english')
businessEnglish, // 商务英语
@JsonValue('bec_preliminary')
becPreliminary, // BEC初级
@JsonValue('bec_vantage')
becVantage, // BEC中级
@JsonValue('bec_higher')
becHigher, // BEC高级
@JsonValue('mba')
mba, // MBA
@JsonValue('finance')
finance, // 金融
@JsonValue('accounting')
accounting, // 会计
@JsonValue('economics')
economics, // 经济学
@JsonValue('cpa')
cpa, // CPA
@JsonValue('cfa')
cfa, // CFA
@JsonValue('medical')
medical, // 医学英语
@JsonValue('legal')
legal, // 法律英语
@JsonValue('engineering')
engineering, // 工程英语
@JsonValue('computer_science')
computerScience, // 计算机科学
@JsonValue('artificial_intelligence')
artificialIntelligence, // 人工智能
@JsonValue('software_engineering')
softwareEngineering, // 软件工程
@JsonValue('academic_english')
academicEnglish, // 学术英语(EAP)
@JsonValue('aviation')
aviation, // 航空
@JsonValue('tourism')
tourism, // 旅游
@JsonValue('media')
media, // 新闻传媒
}
/// 功能型词库子分类
enum FunctionalCategory {
@JsonValue('roots_affixes')
rootsAffixes, // 词根词缀词汇
@JsonValue('synonyms_antonyms')
synonymsAntonyms, // 同义词/反义词
@JsonValue('daily_spoken_collocations')
dailySpokenCollocations, // 日常口语常用搭配库
@JsonValue('academic_spoken_collocations')
academicSpokenCollocations, // 学术口语常用搭配库
@JsonValue('academic_writing_collocations')
academicWritingCollocations, // 学术写作常用搭配库
@JsonValue('daily_life_english')
dailyLifeEnglish, // 日常生活英语
@JsonValue('travel_english')
travelEnglish, // 旅游英语
@JsonValue('dining_english')
diningEnglish, // 点餐英语
@JsonValue('shopping_english')
shoppingEnglish, // 购物英语
@JsonValue('transportation_english')
transportationEnglish, // 出行英语
@JsonValue('housing_english')
housingEnglish, // 租房英语
}
/// 词汇书分类工具类
class VocabularyBookCategoryHelper {
/// 获取主分类的中文名称
static String getMainCategoryName(VocabularyBookMainCategory category) {
switch (category) {
case VocabularyBookMainCategory.academicStage:
return '学段基础词汇';
case VocabularyBookMainCategory.domesticTest:
return '国内应试类词汇';
case VocabularyBookMainCategory.internationalTest:
return '出国考试类词汇';
case VocabularyBookMainCategory.professional:
return '职业与专业类词汇';
case VocabularyBookMainCategory.functional:
return '功能型词库';
}
}
/// 获取学段基础词汇子分类的中文名称
static String getAcademicStageCategoryName(AcademicStageCategory category) {
switch (category) {
case AcademicStageCategory.primarySchool:
return '小学英语词汇';
case AcademicStageCategory.juniorHigh:
return '初中英语词汇';
case AcademicStageCategory.seniorHigh:
return '高中英语词汇';
case AcademicStageCategory.university:
return '大学英语教材词汇';
}
}
/// 获取国内应试类词汇子分类的中文名称
static String getDomesticTestCategoryName(DomesticTestCategory category) {
switch (category) {
case DomesticTestCategory.cet4:
return '大学四级(CET-4)';
case DomesticTestCategory.cet6:
return '大学六级(CET-6)';
case DomesticTestCategory.postgraduate:
return '考研英语核心词汇';
case DomesticTestCategory.tem4:
return '专四(TEM-4)';
case DomesticTestCategory.tem8:
return '专八(TEM-8)';
case DomesticTestCategory.adultBachelor:
return '专升本词汇';
case DomesticTestCategory.doctoral:
return '考博词汇';
case DomesticTestCategory.pets:
return '全国等级考试英语词汇';
case DomesticTestCategory.selfStudy:
return '成人本科/自考英语词汇';
}
}
/// 获取出国考试类词汇子分类的中文名称
static String getInternationalTestCategoryName(InternationalTestCategory category) {
switch (category) {
case InternationalTestCategory.ieltsAcademic:
return '雅思学术类(IELTS Academic)';
case InternationalTestCategory.ieltsGeneral:
return '雅思培训类(IELTS General)';
case InternationalTestCategory.toeflIbt:
return '托福(TOEFL iBT)';
case InternationalTestCategory.toeic:
return '托业(TOEIC)';
case InternationalTestCategory.gre:
return 'GRE词汇';
case InternationalTestCategory.gmat:
return 'GMAT词汇';
case InternationalTestCategory.sat:
return 'SAT词汇';
case InternationalTestCategory.ssat:
return 'SSAT词汇';
case InternationalTestCategory.act:
return 'ACT词汇';
case InternationalTestCategory.cambridgeKet:
return '剑桥KET';
case InternationalTestCategory.cambridgePet:
return '剑桥PET';
case InternationalTestCategory.cambridgeFce:
return '剑桥FCE';
case InternationalTestCategory.cambridgeCae:
return '剑桥CAE';
case InternationalTestCategory.cambridgeCpe:
return '剑桥CPE';
}
}
/// 获取职业与专业类词汇子分类的中文名称
static String getProfessionalCategoryName(ProfessionalCategory category) {
switch (category) {
case ProfessionalCategory.businessEnglish:
return '商务英语';
case ProfessionalCategory.becPreliminary:
return 'BEC初级';
case ProfessionalCategory.becVantage:
return 'BEC中级';
case ProfessionalCategory.becHigher:
return 'BEC高级';
case ProfessionalCategory.mba:
return 'MBA词汇';
case ProfessionalCategory.finance:
return '金融词汇';
case ProfessionalCategory.accounting:
return '会计词汇';
case ProfessionalCategory.economics:
return '经济学词汇';
case ProfessionalCategory.cpa:
return 'CPA词汇';
case ProfessionalCategory.cfa:
return 'CFA词汇';
case ProfessionalCategory.medical:
return '医学英语';
case ProfessionalCategory.legal:
return '法律英语';
case ProfessionalCategory.engineering:
return '工程英语';
case ProfessionalCategory.computerScience:
return '计算机科学';
case ProfessionalCategory.artificialIntelligence:
return '人工智能';
case ProfessionalCategory.softwareEngineering:
return '软件工程';
case ProfessionalCategory.academicEnglish:
return '学术英语(EAP)';
case ProfessionalCategory.aviation:
return '航空英语';
case ProfessionalCategory.tourism:
return '旅游英语';
case ProfessionalCategory.media:
return '新闻传媒英语';
}
}
/// 获取功能型词库子分类的中文名称
static String getFunctionalCategoryName(FunctionalCategory category) {
switch (category) {
case FunctionalCategory.rootsAffixes:
return '词根词缀词汇';
case FunctionalCategory.synonymsAntonyms:
return '同义词/反义词';
case FunctionalCategory.dailySpokenCollocations:
return '日常口语常用搭配库';
case FunctionalCategory.academicSpokenCollocations:
return '学术口语常用搭配库';
case FunctionalCategory.academicWritingCollocations:
return '学术写作常用搭配库';
case FunctionalCategory.dailyLifeEnglish:
return '日常生活英语';
case FunctionalCategory.travelEnglish:
return '旅游英语';
case FunctionalCategory.diningEnglish:
return '点餐英语';
case FunctionalCategory.shoppingEnglish:
return '购物英语';
case FunctionalCategory.transportationEnglish:
return '出行英语';
case FunctionalCategory.housingEnglish:
return '租房英语';
}
}
/// 根据主分类获取对应的子分类列表
static List<String> getSubCategories(VocabularyBookMainCategory mainCategory) {
switch (mainCategory) {
case VocabularyBookMainCategory.academicStage:
return AcademicStageCategory.values.map((e) => getAcademicStageCategoryName(e)).toList();
case VocabularyBookMainCategory.domesticTest:
return DomesticTestCategory.values.map((e) => getDomesticTestCategoryName(e)).toList();
case VocabularyBookMainCategory.internationalTest:
return InternationalTestCategory.values.map((e) => getInternationalTestCategoryName(e)).toList();
case VocabularyBookMainCategory.professional:
return ProfessionalCategory.values.map((e) => getProfessionalCategoryName(e)).toList();
case VocabularyBookMainCategory.functional:
return FunctionalCategory.values.map((e) => getFunctionalCategoryName(e)).toList();
}
}
/// 从中文名称映射到主分类枚举
static VocabularyBookMainCategory? getMainCategoryFromName(String? categoryName) {
if (categoryName == null) return null;
switch (categoryName) {
case '学段基础词汇':
return VocabularyBookMainCategory.academicStage;
case '国内应试类词汇':
return VocabularyBookMainCategory.domesticTest;
case '出国考试类词汇':
return VocabularyBookMainCategory.internationalTest;
case '职业与专业类词汇':
return VocabularyBookMainCategory.professional;
case '功能型词库':
return VocabularyBookMainCategory.functional;
default:
return null;
}
}
}

View File

@@ -0,0 +1,440 @@
import 'vocabulary_book_model.dart';
import 'vocabulary_book_category.dart';
/// 词汇书工厂类,用于生成各种分类的词汇书数据
class VocabularyBookFactory {
/// 获取推荐词汇书
static List<VocabularyBook> getRecommendedBooks({int limit = 8}) {
final allBooks = <VocabularyBook>[];
// 添加学段基础词汇
allBooks.addAll(getAcademicStageBooks().take(2));
// 添加国内考试词汇
allBooks.addAll(getDomesticTestBooks().take(2));
// 添加国际考试词汇
allBooks.addAll(getInternationalTestBooks().take(2));
// 添加职业专业词汇
allBooks.addAll(getProfessionalBooks().take(1));
// 添加功能型词汇
allBooks.addAll(getFunctionalBooks().take(1));
return allBooks.take(limit).toList();
}
/// 获取学段基础词汇书
static List<VocabularyBook> getAcademicStageBooks() {
return [
// 小学词汇
VocabularyBook(
id: 'primary_basic',
name: '小学英语基础词汇',
description: '小学阶段必备英语词汇,涵盖日常生活和学习场景',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.beginner,
mainCategory: VocabularyBookMainCategory.academicStage,
subCategory: 'primary',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/primary_vocab.jpg',
totalWords: 800,
tags: ['小学', '基础', '日常'],
targetLevels: ['小学1-3年级', '小学4-6年级'],
estimatedDays: 40,
dailyWordCount: 20,
downloadCount: 25000,
rating: 4.9,
reviewCount: 2500,
createdAt: DateTime.now().subtract(const Duration(days: 500)),
updatedAt: DateTime.now().subtract(const Duration(days: 10)),
),
// 初中词汇
VocabularyBook(
id: 'middle_school_core',
name: '初中英语核心词汇',
description: '初中阶段核心词汇,为中考打下坚实基础',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.elementary,
mainCategory: VocabularyBookMainCategory.academicStage,
subCategory: 'middle_school',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/middle_vocab.jpg',
totalWords: 1600,
tags: ['初中', '中考', '核心'],
targetLevels: ['初一', '初二', '初三'],
estimatedDays: 60,
dailyWordCount: 30,
downloadCount: 35000,
rating: 4.8,
reviewCount: 3500,
createdAt: DateTime.now().subtract(const Duration(days: 450)),
updatedAt: DateTime.now().subtract(const Duration(days: 15)),
),
// 高中词汇
VocabularyBook(
id: 'high_school_essential',
name: '高中英语必备词汇',
description: '高中阶段必备词汇,涵盖高考重点词汇',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.intermediate,
mainCategory: VocabularyBookMainCategory.academicStage,
subCategory: 'high_school',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/high_vocab.jpg',
totalWords: 3500,
tags: ['高中', '高考', '必备'],
targetLevels: ['高一', '高二', '高三'],
estimatedDays: 100,
dailyWordCount: 35,
downloadCount: 45000,
rating: 4.7,
reviewCount: 4500,
createdAt: DateTime.now().subtract(const Duration(days: 400)),
updatedAt: DateTime.now().subtract(const Duration(days: 20)),
),
// 大学词汇
VocabularyBook(
id: 'college_advanced',
name: '大学英语进阶词汇',
description: '大学阶段进阶词汇,提升学术英语水平',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.advanced,
mainCategory: VocabularyBookMainCategory.academicStage,
subCategory: 'college',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/college_vocab.jpg',
totalWords: 5000,
tags: ['大学', '学术', '进阶'],
targetLevels: ['大一', '大二', '大三', '大四'],
estimatedDays: 120,
dailyWordCount: 40,
downloadCount: 30000,
rating: 4.6,
reviewCount: 3000,
createdAt: DateTime.now().subtract(const Duration(days: 350)),
updatedAt: DateTime.now().subtract(const Duration(days: 25)),
),
];
}
/// 获取国内考试词汇书
static List<VocabularyBook> getDomesticTestBooks() {
return [
// 四级词汇
VocabularyBook(
id: 'cet4_core',
name: '大学英语四级词汇',
description: '四级考试核心词汇,系统化备考',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.intermediate,
mainCategory: VocabularyBookMainCategory.domesticTest,
subCategory: 'cet4',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/cet4_vocab.jpg',
totalWords: 4000,
tags: ['四级', '考试', '核心'],
targetLevels: ['四级425+', '四级500+'],
estimatedDays: 80,
dailyWordCount: 50,
downloadCount: 50000,
rating: 4.8,
reviewCount: 5000,
createdAt: DateTime.now().subtract(const Duration(days: 300)),
updatedAt: DateTime.now().subtract(const Duration(days: 5)),
),
// 六级词汇
VocabularyBook(
id: 'cet6_advanced',
name: '大学英语六级词汇',
description: '六级考试高级词汇,突破高分',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.intermediate,
mainCategory: VocabularyBookMainCategory.domesticTest,
subCategory: 'cet6',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/cet6_vocab.jpg',
totalWords: 2500,
tags: ['六级', '高级', '突破'],
targetLevels: ['六级425+', '六级500+'],
estimatedDays: 60,
dailyWordCount: 45,
downloadCount: 40000,
rating: 4.7,
reviewCount: 4000,
createdAt: DateTime.now().subtract(const Duration(days: 280)),
updatedAt: DateTime.now().subtract(const Duration(days: 8)),
),
// 考研词汇
VocabularyBook(
id: 'postgraduate_essential',
name: '考研英语核心词汇',
description: '考研英语必备词汇,全面覆盖考点',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.advanced,
mainCategory: VocabularyBookMainCategory.domesticTest,
subCategory: 'postgraduate',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/postgrad_vocab.jpg',
totalWords: 5500,
tags: ['考研', '核心', '全面'],
targetLevels: ['考研英语一', '考研英语二'],
estimatedDays: 120,
dailyWordCount: 45,
downloadCount: 60000,
rating: 4.9,
reviewCount: 6000,
createdAt: DateTime.now().subtract(const Duration(days: 250)),
updatedAt: DateTime.now().subtract(const Duration(days: 3)),
),
// 专四词汇
VocabularyBook(
id: 'tem4_professional',
name: '英语专业四级词汇',
description: '专四考试专业词汇,英语专业必备',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.advanced,
mainCategory: VocabularyBookMainCategory.domesticTest,
subCategory: 'tem4',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/tem4_vocab.jpg',
totalWords: 8000,
tags: ['专四', '专业', '英语'],
targetLevels: ['英语专业大二'],
estimatedDays: 150,
dailyWordCount: 55,
downloadCount: 25000,
rating: 4.6,
reviewCount: 2500,
createdAt: DateTime.now().subtract(const Duration(days: 200)),
updatedAt: DateTime.now().subtract(const Duration(days: 12)),
),
];
}
/// 获取国际考试词汇书
static List<VocabularyBook> getInternationalTestBooks() {
return [
// 托福词汇
VocabularyBook(
id: 'toefl_core',
name: '托福核心词汇',
description: '托福考试必备核心词汇,涵盖学术和日常用语',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.advanced,
mainCategory: VocabularyBookMainCategory.internationalTest,
subCategory: 'toefl',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/toefl_vocab.jpg',
totalWords: 5000,
tags: ['托福', '学术', '高级'],
targetLevels: ['托福80+', '托福100+'],
estimatedDays: 100,
dailyWordCount: 50,
downloadCount: 35000,
rating: 4.8,
reviewCount: 3500,
createdAt: DateTime.now().subtract(const Duration(days: 365)),
updatedAt: DateTime.now().subtract(const Duration(days: 30)),
),
// 雅思词汇
VocabularyBook(
id: 'ielts_essential',
name: '雅思核心词汇',
description: '雅思考试核心词汇,按话题分类整理',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.intermediate,
mainCategory: VocabularyBookMainCategory.internationalTest,
subCategory: 'ielts',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/ielts_vocab.jpg',
totalWords: 4000,
tags: ['雅思', '分类', '中级'],
targetLevels: ['雅思6.0', '雅思7.0'],
estimatedDays: 80,
dailyWordCount: 50,
downloadCount: 42000,
rating: 4.7,
reviewCount: 4200,
createdAt: DateTime.now().subtract(const Duration(days: 300)),
updatedAt: DateTime.now().subtract(const Duration(days: 15)),
),
// GRE词汇
VocabularyBook(
id: 'gre_advanced',
name: 'GRE高级词汇',
description: 'GRE考试高难度词汇学术研究必备',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.expert,
mainCategory: VocabularyBookMainCategory.internationalTest,
subCategory: 'gre',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/gre_vocab.jpg',
totalWords: 6000,
tags: ['GRE', '高难度', '学术'],
targetLevels: ['GRE 320+', 'GRE 330+'],
estimatedDays: 150,
dailyWordCount: 40,
downloadCount: 20000,
rating: 4.5,
reviewCount: 2000,
createdAt: DateTime.now().subtract(const Duration(days: 180)),
updatedAt: DateTime.now().subtract(const Duration(days: 7)),
),
];
}
/// 获取职业专业词汇书
static List<VocabularyBook> getProfessionalBooks() {
return [
// 商务英语
VocabularyBook(
id: 'business_english',
name: '商务英语词汇',
description: '商务场景专业词汇,职场必备',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.intermediate,
mainCategory: VocabularyBookMainCategory.professional,
subCategory: 'business',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/business_vocab.jpg',
totalWords: 2000,
tags: ['商务', '职场', '专业'],
targetLevels: ['职场新人', '商务精英'],
estimatedDays: 50,
dailyWordCount: 40,
downloadCount: 18000,
rating: 4.6,
reviewCount: 1800,
createdAt: DateTime.now().subtract(const Duration(days: 120)),
updatedAt: DateTime.now().subtract(const Duration(days: 5)),
),
// IT技术词汇
VocabularyBook(
id: 'it_technical',
name: 'IT技术词汇',
description: '信息技术专业词汇,程序员必备',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.intermediate,
mainCategory: VocabularyBookMainCategory.professional,
subCategory: 'it',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/it_vocab.jpg',
totalWords: 1500,
tags: ['IT', '技术', '程序员'],
targetLevels: ['初级程序员', '高级工程师'],
estimatedDays: 40,
dailyWordCount: 35,
downloadCount: 15000,
rating: 4.7,
reviewCount: 1500,
createdAt: DateTime.now().subtract(const Duration(days: 100)),
updatedAt: DateTime.now().subtract(const Duration(days: 8)),
),
];
}
/// 获取功能型词汇书
static List<VocabularyBook> getFunctionalBooks() {
return [
// 词根词缀
VocabularyBook(
id: 'roots_affixes',
name: '词根词缀大全',
description: '掌握词根词缀,快速扩展词汇量',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.intermediate,
mainCategory: VocabularyBookMainCategory.functional,
subCategory: 'roots_affixes',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/roots_vocab.jpg',
totalWords: 800,
tags: ['词根', '词缀', '技巧'],
targetLevels: ['中级学习者', '高级学习者'],
estimatedDays: 30,
dailyWordCount: 25,
downloadCount: 22000,
rating: 4.8,
reviewCount: 2200,
createdAt: DateTime.now().subtract(const Duration(days: 90)),
updatedAt: DateTime.now().subtract(const Duration(days: 3)),
),
// 日常口语
VocabularyBook(
id: 'daily_speaking',
name: '日常口语词汇',
description: '日常交流必备词汇,提升口语表达',
type: VocabularyBookType.system,
difficulty: VocabularyBookDifficulty.elementary,
mainCategory: VocabularyBookMainCategory.functional,
subCategory: 'speaking',
coverImageUrl: 'https://ai-public.mastergo.com/ai/img_res/speaking_vocab.jpg',
totalWords: 1200,
tags: ['口语', '日常', '交流'],
targetLevels: ['初级', '中级'],
estimatedDays: 35,
dailyWordCount: 35,
downloadCount: 28000,
rating: 4.9,
reviewCount: 2800,
createdAt: DateTime.now().subtract(const Duration(days: 80)),
updatedAt: DateTime.now().subtract(const Duration(days: 2)),
),
];
}
/// 根据主分类获取词汇书
static List<VocabularyBook> getBooksByMainCategory(VocabularyBookMainCategory category) {
switch (category) {
case VocabularyBookMainCategory.academicStage:
return getAcademicStageBooks();
case VocabularyBookMainCategory.domesticTest:
return getDomesticTestBooks();
case VocabularyBookMainCategory.internationalTest:
return getInternationalTestBooks();
case VocabularyBookMainCategory.professional:
return getProfessionalBooks();
case VocabularyBookMainCategory.functional:
return getFunctionalBooks();
}
}
/// 根据子分类获取词汇书
static List<VocabularyBook> getBooksBySubCategory(String subCategory) {
final allBooks = <VocabularyBook>[];
allBooks.addAll(getAcademicStageBooks());
allBooks.addAll(getDomesticTestBooks());
allBooks.addAll(getInternationalTestBooks());
allBooks.addAll(getProfessionalBooks());
allBooks.addAll(getFunctionalBooks());
return allBooks.where((book) => book.subCategory == subCategory).toList();
}
/// 根据难度获取词汇书
static List<VocabularyBook> getBooksByDifficulty(VocabularyBookDifficulty difficulty) {
final allBooks = <VocabularyBook>[];
allBooks.addAll(getAcademicStageBooks());
allBooks.addAll(getDomesticTestBooks());
allBooks.addAll(getInternationalTestBooks());
allBooks.addAll(getProfessionalBooks());
allBooks.addAll(getFunctionalBooks());
return allBooks.where((book) => book.difficulty == difficulty).toList();
}
/// 搜索词汇书
static List<VocabularyBook> searchBooks(String query) {
final allBooks = <VocabularyBook>[];
allBooks.addAll(getAcademicStageBooks());
allBooks.addAll(getDomesticTestBooks());
allBooks.addAll(getInternationalTestBooks());
allBooks.addAll(getProfessionalBooks());
allBooks.addAll(getFunctionalBooks());
final lowerQuery = query.toLowerCase();
return allBooks.where((book) {
return book.name.toLowerCase().contains(lowerQuery) ||
book.description?.toLowerCase().contains(lowerQuery) == true ||
book.tags.any((tag) => tag.toLowerCase().contains(lowerQuery));
}).toList();
}
}

View File

@@ -0,0 +1,388 @@
import 'package:json_annotation/json_annotation.dart';
import 'word_model.dart';
import 'vocabulary_book_category.dart';
part 'vocabulary_book_model.g.dart';
/// 词汇书类型
enum VocabularyBookType {
@JsonValue('system')
system, // 系统词汇书
@JsonValue('custom')
custom, // 用户自定义词汇书
@JsonValue('shared')
shared, // 共享词汇书
}
/// 词汇书难度
enum VocabularyBookDifficulty {
@JsonValue('beginner')
beginner,
@JsonValue('elementary')
elementary,
@JsonValue('intermediate')
intermediate,
@JsonValue('advanced')
advanced,
@JsonValue('expert')
expert,
}
/// 词汇书模型
@JsonSerializable()
class VocabularyBook {
/// 词汇书ID
final String id;
/// 词汇书名称
final String name;
/// 词汇书描述
final String? description;
/// 词汇书类型
@JsonKey(defaultValue: VocabularyBookType.system)
final VocabularyBookType type;
/// 难度等级
@JsonKey(name: 'level')
final VocabularyBookDifficulty difficulty;
/// 封面图片URL
@JsonKey(name: 'cover_image')
final String? coverImageUrl;
/// 单词总数
@JsonKey(name: 'total_words')
final int totalWords;
/// 创建者ID
@JsonKey(name: 'creator_id')
final String? creatorId;
/// 创建者名称
@JsonKey(name: 'creator_name')
final String? creatorName;
/// 是否公开
@JsonKey(name: 'is_public', defaultValue: false)
final bool isPublic;
/// 标签列表
@JsonKey(defaultValue: [])
final List<String> tags;
/// 分类
final String? category;
/// 主分类
@JsonKey(name: 'main_category')
final VocabularyBookMainCategory? mainCategory;
/// 子分类JSON字符串根据主分类解析为对应的子分类枚举
@JsonKey(name: 'sub_category')
final String? subCategory;
/// 适用等级
@JsonKey(name: 'target_levels', defaultValue: [])
final List<String> targetLevels;
/// 预计学习天数
@JsonKey(name: 'estimated_days', defaultValue: 30)
final int estimatedDays;
/// 每日学习单词数
@JsonKey(name: 'daily_word_count', defaultValue: 20)
final int dailyWordCount;
/// 下载次数
@JsonKey(name: 'download_count', defaultValue: 0)
final int downloadCount;
/// 评分 (1-5)
@JsonKey(defaultValue: 0.0)
final double rating;
/// 评价数量
@JsonKey(name: 'review_count', defaultValue: 0)
final int reviewCount;
/// 创建时间
@JsonKey(name: 'created_at')
final DateTime createdAt;
/// 更新时间
@JsonKey(name: 'updated_at')
final DateTime updatedAt;
const VocabularyBook({
required this.id,
required this.name,
this.description,
required this.type,
required this.difficulty,
this.coverImageUrl,
required this.totalWords,
this.creatorId,
this.creatorName,
this.isPublic = false,
this.tags = const [],
this.category,
this.mainCategory,
this.subCategory,
this.targetLevels = const [],
this.estimatedDays = 30,
this.dailyWordCount = 20,
this.downloadCount = 0,
this.rating = 0.0,
this.reviewCount = 0,
required this.createdAt,
required this.updatedAt,
});
factory VocabularyBook.fromJson(Map<String, dynamic> json) {
final book = _$VocabularyBookFromJson(json);
// 如果有category字符串但没有mainCategory则从category映射
if (book.category != null && book.mainCategory == null) {
return book.copyWith(
mainCategory: VocabularyBookCategoryHelper.getMainCategoryFromName(book.category),
);
}
return book;
}
Map<String, dynamic> toJson() => _$VocabularyBookToJson(this);
VocabularyBook copyWith({
String? id,
String? name,
String? description,
VocabularyBookType? type,
VocabularyBookDifficulty? difficulty,
String? coverImageUrl,
int? totalWords,
String? creatorId,
String? creatorName,
bool? isPublic,
List<String>? tags,
String? category,
VocabularyBookMainCategory? mainCategory,
String? subCategory,
List<String>? targetLevels,
int? estimatedDays,
int? dailyWordCount,
int? downloadCount,
double? rating,
int? reviewCount,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return VocabularyBook(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
type: type ?? this.type,
difficulty: difficulty ?? this.difficulty,
coverImageUrl: coverImageUrl ?? this.coverImageUrl,
totalWords: totalWords ?? this.totalWords,
creatorId: creatorId ?? this.creatorId,
creatorName: creatorName ?? this.creatorName,
isPublic: isPublic ?? this.isPublic,
tags: tags ?? this.tags,
category: category ?? this.category,
mainCategory: mainCategory ?? this.mainCategory,
subCategory: subCategory ?? this.subCategory,
targetLevels: targetLevels ?? this.targetLevels,
estimatedDays: estimatedDays ?? this.estimatedDays,
dailyWordCount: dailyWordCount ?? this.dailyWordCount,
downloadCount: downloadCount ?? this.downloadCount,
rating: rating ?? this.rating,
reviewCount: reviewCount ?? this.reviewCount,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}
/// 用户词汇书学习进度
@JsonSerializable()
class UserVocabularyBookProgress {
/// 进度ID
@JsonKey(fromJson: _idFromJson)
final String id;
/// 用户ID
@JsonKey(name: 'user_id', fromJson: _userIdFromJson)
final String userId;
/// 词汇书ID
@JsonKey(name: 'book_id')
final String vocabularyBookId;
/// 已学习单词数
@JsonKey(name: 'learned_words')
final int learnedWords;
/// 已掌握单词数
@JsonKey(name: 'mastered_words')
final int masteredWords;
/// 学习进度百分比 (0-100)
@JsonKey(name: 'progress_percentage')
final double progressPercentage;
/// 连续学习天数
@JsonKey(name: 'streak_days')
final int streakDays;
/// 总学习天数
@JsonKey(name: 'total_study_days')
final int totalStudyDays;
/// 平均每日学习单词数
@JsonKey(name: 'average_daily_words')
final double averageDailyWords;
/// 预计完成时间
@JsonKey(name: 'estimated_completion_date')
final DateTime? estimatedCompletionDate;
/// 是否已完成
@JsonKey(name: 'is_completed')
final bool isCompleted;
/// 完成时间
@JsonKey(name: 'completed_at')
final DateTime? completedAt;
/// 开始学习时间
@JsonKey(name: 'started_at')
final DateTime startedAt;
/// 最后学习时间
@JsonKey(name: 'last_studied_at')
final DateTime? lastStudiedAt;
const UserVocabularyBookProgress({
required this.id,
required this.userId,
required this.vocabularyBookId,
this.learnedWords = 0,
this.masteredWords = 0,
this.progressPercentage = 0.0,
this.streakDays = 0,
this.totalStudyDays = 0,
this.averageDailyWords = 0.0,
this.estimatedCompletionDate,
this.isCompleted = false,
this.completedAt,
required this.startedAt,
this.lastStudiedAt,
});
factory UserVocabularyBookProgress.fromJson(Map<String, dynamic> json) => _$UserVocabularyBookProgressFromJson(json);
Map<String, dynamic> toJson() => _$UserVocabularyBookProgressToJson(this);
/// 类型转换方法
static String _idFromJson(dynamic value) => value?.toString() ?? '0';
static String _userIdFromJson(dynamic value) => value?.toString() ?? '0';
UserVocabularyBookProgress copyWith({
String? id,
String? userId,
String? vocabularyBookId,
int? learnedWords,
int? masteredWords,
double? progressPercentage,
int? streakDays,
int? totalStudyDays,
double? averageDailyWords,
DateTime? estimatedCompletionDate,
bool? isCompleted,
DateTime? completedAt,
DateTime? startedAt,
DateTime? lastStudiedAt,
}) {
return UserVocabularyBookProgress(
id: id ?? this.id,
userId: userId ?? this.userId,
vocabularyBookId: vocabularyBookId ?? this.vocabularyBookId,
learnedWords: learnedWords ?? this.learnedWords,
masteredWords: masteredWords ?? this.masteredWords,
progressPercentage: progressPercentage ?? this.progressPercentage,
streakDays: streakDays ?? this.streakDays,
totalStudyDays: totalStudyDays ?? this.totalStudyDays,
averageDailyWords: averageDailyWords ?? this.averageDailyWords,
estimatedCompletionDate: estimatedCompletionDate ?? this.estimatedCompletionDate,
isCompleted: isCompleted ?? this.isCompleted,
completedAt: completedAt ?? this.completedAt,
startedAt: startedAt ?? this.startedAt,
lastStudiedAt: lastStudiedAt ?? this.lastStudiedAt,
);
}
}
/// 词汇书单词关联
@JsonSerializable()
class VocabularyBookWord {
/// 关联ID
@JsonKey(name: 'id', fromJson: _idFromJson)
final String id;
/// 词汇书ID
@JsonKey(name: 'book_id')
final String vocabularyBookId;
/// 单词ID
@JsonKey(name: 'vocabulary_id')
final String wordId;
/// 在词汇书中的顺序
@JsonKey(name: 'sort_order', defaultValue: 0)
final int order;
/// 单词信息
final Word? word;
/// 添加时间
@JsonKey(name: 'created_at')
final DateTime addedAt;
const VocabularyBookWord({
required this.id,
required this.vocabularyBookId,
required this.wordId,
required this.order,
this.word,
required this.addedAt,
});
/// 处理id字段的类型转换后端可能返回int或string
static String _idFromJson(dynamic value) {
if (value is int) {
return value.toString();
}
return value.toString();
}
factory VocabularyBookWord.fromJson(Map<String, dynamic> json) => _$VocabularyBookWordFromJson(json);
Map<String, dynamic> toJson() => _$VocabularyBookWordToJson(this);
VocabularyBookWord copyWith({
String? id,
String? vocabularyBookId,
String? wordId,
int? order,
Word? word,
DateTime? addedAt,
}) {
return VocabularyBookWord(
id: id ?? this.id,
vocabularyBookId: vocabularyBookId ?? this.vocabularyBookId,
wordId: wordId ?? this.wordId,
order: order ?? this.order,
word: word ?? this.word,
addedAt: addedAt ?? this.addedAt,
);
}
}

View File

@@ -0,0 +1,158 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'vocabulary_book_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
VocabularyBook _$VocabularyBookFromJson(Map<String, dynamic> json) =>
VocabularyBook(
id: json['id'] as String,
name: json['name'] as String,
description: json['description'] as String?,
type: $enumDecodeNullable(_$VocabularyBookTypeEnumMap, json['type']) ??
VocabularyBookType.system,
difficulty: $enumDecode(_$VocabularyBookDifficultyEnumMap, json['level']),
coverImageUrl: json['cover_image'] as String?,
totalWords: (json['total_words'] as num).toInt(),
creatorId: json['creator_id'] as String?,
creatorName: json['creator_name'] as String?,
isPublic: json['is_public'] as bool? ?? false,
tags:
(json['tags'] as List<dynamic>?)?.map((e) => e as String).toList() ??
[],
category: json['category'] as String?,
mainCategory: $enumDecodeNullable(
_$VocabularyBookMainCategoryEnumMap, json['main_category']),
subCategory: json['sub_category'] as String?,
targetLevels: (json['target_levels'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
estimatedDays: (json['estimated_days'] as num?)?.toInt() ?? 30,
dailyWordCount: (json['daily_word_count'] as num?)?.toInt() ?? 20,
downloadCount: (json['download_count'] as num?)?.toInt() ?? 0,
rating: (json['rating'] as num?)?.toDouble() ?? 0.0,
reviewCount: (json['review_count'] as num?)?.toInt() ?? 0,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
Map<String, dynamic> _$VocabularyBookToJson(VocabularyBook instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'description': instance.description,
'type': _$VocabularyBookTypeEnumMap[instance.type]!,
'level': _$VocabularyBookDifficultyEnumMap[instance.difficulty]!,
'cover_image': instance.coverImageUrl,
'total_words': instance.totalWords,
'creator_id': instance.creatorId,
'creator_name': instance.creatorName,
'is_public': instance.isPublic,
'tags': instance.tags,
'category': instance.category,
'main_category':
_$VocabularyBookMainCategoryEnumMap[instance.mainCategory],
'sub_category': instance.subCategory,
'target_levels': instance.targetLevels,
'estimated_days': instance.estimatedDays,
'daily_word_count': instance.dailyWordCount,
'download_count': instance.downloadCount,
'rating': instance.rating,
'review_count': instance.reviewCount,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
};
const _$VocabularyBookTypeEnumMap = {
VocabularyBookType.system: 'system',
VocabularyBookType.custom: 'custom',
VocabularyBookType.shared: 'shared',
};
const _$VocabularyBookDifficultyEnumMap = {
VocabularyBookDifficulty.beginner: 'beginner',
VocabularyBookDifficulty.elementary: 'elementary',
VocabularyBookDifficulty.intermediate: 'intermediate',
VocabularyBookDifficulty.advanced: 'advanced',
VocabularyBookDifficulty.expert: 'expert',
};
const _$VocabularyBookMainCategoryEnumMap = {
VocabularyBookMainCategory.academicStage: 'academic_stage',
VocabularyBookMainCategory.domesticTest: 'domestic_test',
VocabularyBookMainCategory.internationalTest: 'international_test',
VocabularyBookMainCategory.professional: 'professional',
VocabularyBookMainCategory.functional: 'functional',
};
UserVocabularyBookProgress _$UserVocabularyBookProgressFromJson(
Map<String, dynamic> json) =>
UserVocabularyBookProgress(
id: UserVocabularyBookProgress._idFromJson(json['id']),
userId: UserVocabularyBookProgress._userIdFromJson(json['user_id']),
vocabularyBookId: json['book_id'] as String,
learnedWords: (json['learned_words'] as num?)?.toInt() ?? 0,
masteredWords: (json['mastered_words'] as num?)?.toInt() ?? 0,
progressPercentage:
(json['progress_percentage'] as num?)?.toDouble() ?? 0.0,
streakDays: (json['streak_days'] as num?)?.toInt() ?? 0,
totalStudyDays: (json['total_study_days'] as num?)?.toInt() ?? 0,
averageDailyWords:
(json['average_daily_words'] as num?)?.toDouble() ?? 0.0,
estimatedCompletionDate: json['estimated_completion_date'] == null
? null
: DateTime.parse(json['estimated_completion_date'] as String),
isCompleted: json['is_completed'] as bool? ?? false,
completedAt: json['completed_at'] == null
? null
: DateTime.parse(json['completed_at'] as String),
startedAt: DateTime.parse(json['started_at'] as String),
lastStudiedAt: json['last_studied_at'] == null
? null
: DateTime.parse(json['last_studied_at'] as String),
);
Map<String, dynamic> _$UserVocabularyBookProgressToJson(
UserVocabularyBookProgress instance) =>
<String, dynamic>{
'id': instance.id,
'user_id': instance.userId,
'book_id': instance.vocabularyBookId,
'learned_words': instance.learnedWords,
'mastered_words': instance.masteredWords,
'progress_percentage': instance.progressPercentage,
'streak_days': instance.streakDays,
'total_study_days': instance.totalStudyDays,
'average_daily_words': instance.averageDailyWords,
'estimated_completion_date':
instance.estimatedCompletionDate?.toIso8601String(),
'is_completed': instance.isCompleted,
'completed_at': instance.completedAt?.toIso8601String(),
'started_at': instance.startedAt.toIso8601String(),
'last_studied_at': instance.lastStudiedAt?.toIso8601String(),
};
VocabularyBookWord _$VocabularyBookWordFromJson(Map<String, dynamic> json) =>
VocabularyBookWord(
id: VocabularyBookWord._idFromJson(json['id']),
vocabularyBookId: json['book_id'] as String,
wordId: json['vocabulary_id'] as String,
order: (json['sort_order'] as num?)?.toInt() ?? 0,
word: json['word'] == null
? null
: Word.fromJson(json['word'] as Map<String, dynamic>),
addedAt: DateTime.parse(json['created_at'] as String),
);
Map<String, dynamic> _$VocabularyBookWordToJson(VocabularyBookWord instance) =>
<String, dynamic>{
'id': instance.id,
'book_id': instance.vocabularyBookId,
'vocabulary_id': instance.wordId,
'sort_order': instance.order,
'word': instance.word,
'created_at': instance.addedAt.toIso8601String(),
};

View File

@@ -0,0 +1,205 @@
import 'word_model.dart';
/// 生词本模型
class WordBook {
final String id;
final String name;
final String? description;
final String userId;
final List<String> wordIds;
final List<Word> words;
final WordBookType type;
final int wordCount;
final DateTime createdAt;
final DateTime updatedAt;
WordBook({
required this.id,
required this.name,
this.description,
required this.userId,
List<String>? wordIds,
List<Word>? words,
this.type = WordBookType.personal,
this.wordCount = 0,
required this.createdAt,
required this.updatedAt,
}) : wordIds = wordIds ?? [],
words = words ?? [];
factory WordBook.fromJson(Map<String, dynamic> json) {
return WordBook(
id: json['id'] as String,
name: json['name'] as String,
description: json['description'] as String?,
userId: json['user_id'] as String,
wordIds: (json['word_ids'] as List<dynamic>?)?.map((e) => e as String).toList(),
words: (json['words'] as List<dynamic>?)?.map((e) => Word.fromJson(e as Map<String, dynamic>)).toList(),
type: WordBookType.values.firstWhere(
(e) => e.toString().split('.').last == json['type'],
orElse: () => WordBookType.personal,
),
wordCount: json['word_count'] as int? ?? 0,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'description': description,
'user_id': userId,
'word_ids': wordIds,
'words': words.map((e) => e.toJson()).toList(),
'type': type.toString().split('.').last,
'word_count': wordCount,
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
}
WordBook copyWith({
String? id,
String? name,
String? description,
String? userId,
List<String>? wordIds,
List<Word>? words,
WordBookType? type,
int? wordCount,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return WordBook(
id: id ?? this.id,
name: name ?? this.name,
description: description ?? this.description,
userId: userId ?? this.userId,
wordIds: wordIds ?? this.wordIds,
words: words ?? this.words,
type: type ?? this.type,
wordCount: wordCount ?? this.wordCount,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}
/// 生词本类型
enum WordBookType {
personal,
shared,
system,
}
/// 生词本条目
class WordBookEntry {
final String id;
final String wordBookId;
final String wordId;
final Word word;
final String? note;
final List<String> tags;
final int reviewCount;
final int correctCount;
final DateTime? lastReviewAt;
final DateTime addedAt;
WordBookEntry({
required this.id,
required this.wordBookId,
required this.wordId,
required this.word,
this.note,
List<String>? tags,
this.reviewCount = 0,
this.correctCount = 0,
this.lastReviewAt,
required this.addedAt,
}) : tags = tags ?? [];
factory WordBookEntry.fromJson(Map<String, dynamic> json) {
return WordBookEntry(
id: json['id'] as String,
wordBookId: json['word_book_id'] as String,
wordId: json['word_id'] as String,
word: Word.fromJson(json['word'] as Map<String, dynamic>),
note: json['note'] as String?,
tags: (json['tags'] as List<dynamic>?)?.map((e) => e as String).toList(),
reviewCount: json['review_count'] as int? ?? 0,
correctCount: json['correct_count'] as int? ?? 0,
lastReviewAt: json['last_review_at'] != null ? DateTime.parse(json['last_review_at'] as String) : null,
addedAt: DateTime.parse(json['added_at'] as String),
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'word_book_id': wordBookId,
'word_id': wordId,
'word': word.toJson(),
'note': note,
'tags': tags,
'review_count': reviewCount,
'correct_count': correctCount,
'last_review_at': lastReviewAt?.toIso8601String(),
'added_at': addedAt.toIso8601String(),
};
}
}
/// 生词本统计
class WordBookStats {
final String wordBookId;
final int totalWords;
final int masteredWords;
final int reviewingWords;
final int newWords;
final double masteryRate;
final DateTime? lastStudyAt;
final int studyDays;
final int totalReviews;
WordBookStats({
required this.wordBookId,
this.totalWords = 0,
this.masteredWords = 0,
this.reviewingWords = 0,
this.newWords = 0,
this.masteryRate = 0,
this.lastStudyAt,
this.studyDays = 0,
this.totalReviews = 0,
});
factory WordBookStats.fromJson(Map<String, dynamic> json) {
return WordBookStats(
wordBookId: json['word_book_id'] as String,
totalWords: json['total_words'] as int? ?? 0,
masteredWords: json['mastered_words'] as int? ?? 0,
reviewingWords: json['reviewing_words'] as int? ?? 0,
newWords: json['new_words'] as int? ?? 0,
masteryRate: (json['mastery_rate'] as num?)?.toDouble() ?? 0,
lastStudyAt: json['last_study_at'] != null ? DateTime.parse(json['last_study_at'] as String) : null,
studyDays: json['study_days'] as int? ?? 0,
totalReviews: json['total_reviews'] as int? ?? 0,
);
}
Map<String, dynamic> toJson() {
return {
'word_book_id': wordBookId,
'total_words': totalWords,
'mastered_words': masteredWords,
'reviewing_words': reviewingWords,
'new_words': newWords,
'mastery_rate': masteryRate,
'last_study_at': lastStudyAt?.toIso8601String(),
'study_days': studyDays,
'total_reviews': totalReviews,
};
}
}

View File

@@ -0,0 +1,25 @@
class WordBookStats {
final int totalWords;
final int masteredWords;
final int learningWords;
final int reviewingWords;
final double masteryRate;
WordBookStats({
required this.totalWords,
required this.masteredWords,
required this.learningWords,
required this.reviewingWords,
required this.masteryRate,
});
factory WordBookStats.fromJson(Map<String, dynamic> json) {
return WordBookStats(
totalWords: (json['totalWords'] as num?)?.toInt() ?? 0,
masteredWords: (json['masteredWords'] as num?)?.toInt() ?? 0,
learningWords: (json['learningWords'] as num?)?.toInt() ?? 0,
reviewingWords: (json['reviewingWords'] as num?)?.toInt() ?? 0,
masteryRate: (json['masteryRate'] as num?)?.toDouble() ?? 0.0,
);
}
}

View File

@@ -0,0 +1,519 @@
import 'package:json_annotation/json_annotation.dart';
part 'word_model.g.dart';
/// 单词难度等级
enum WordDifficulty {
@JsonValue('beginner')
beginner,
@JsonValue('elementary')
elementary,
@JsonValue('intermediate')
intermediate,
@JsonValue('advanced')
advanced,
@JsonValue('expert')
expert,
}
/// 单词类型
enum WordType {
@JsonValue('noun')
noun,
@JsonValue('verb')
verb,
@JsonValue('adjective')
adjective,
@JsonValue('adverb')
adverb,
@JsonValue('preposition')
preposition,
@JsonValue('conjunction')
conjunction,
@JsonValue('interjection')
interjection,
@JsonValue('pronoun')
pronoun,
@JsonValue('article')
article,
@JsonValue('phrase')
phrase,
}
/// 学习状态
enum LearningStatus {
@JsonValue('new')
newWord,
@JsonValue('learning')
learning,
@JsonValue('reviewing')
reviewing,
@JsonValue('mastered')
mastered,
@JsonValue('forgotten')
forgotten,
}
/// 单词模型
@JsonSerializable()
class Word {
/// 单词ID
@JsonKey(name: 'id', fromJson: _idFromJson)
final String id;
/// 处理id字段的类型转换后端返回int64
static String _idFromJson(dynamic value) {
if (value is int) {
return value.toString();
}
return value.toString();
}
/// 单词
final String word;
/// 音标
final String? phonetic;
/// 音频URL
@JsonKey(name: 'audio_url')
final String? audioUrl;
/// 词性和释义列表
@JsonKey(defaultValue: [])
final List<WordDefinition> definitions;
/// 例句列表
@JsonKey(defaultValue: [])
final List<WordExample> examples;
/// 同义词
@JsonKey(defaultValue: [])
final List<String> synonyms;
/// 反义词
@JsonKey(defaultValue: [])
final List<String> antonyms;
/// 词根词缀
final WordEtymology? etymology;
/// 难度等级
@JsonKey(name: 'difficulty', fromJson: _difficultyFromJson)
final WordDifficulty difficulty;
/// 频率等级 (1-5)
@JsonKey(defaultValue: 0)
final int frequency;
/// 图片URL
@JsonKey(name: 'image_url')
final String? imageUrl;
/// 记忆技巧
@JsonKey(name: 'memory_tip')
final String? memoryTip;
/// 创建时间
@JsonKey(name: 'created_at')
final DateTime createdAt;
/// 更新时间
@JsonKey(name: 'updated_at')
final DateTime updatedAt;
const Word({
required this.id,
required this.word,
this.phonetic,
this.audioUrl,
required this.definitions,
this.examples = const [],
this.synonyms = const [],
this.antonyms = const [],
this.etymology,
required this.difficulty,
required this.frequency,
this.imageUrl,
this.memoryTip,
required this.createdAt,
required this.updatedAt,
});
factory Word.fromJson(Map<String, dynamic> json) => _$WordFromJson(json);
Map<String, dynamic> toJson() => _$WordToJson(this);
/// 处理difficulty字段后端可能为null、空字符串或其他值
static WordDifficulty _difficultyFromJson(dynamic value) {
if (value == null || value == '') return WordDifficulty.beginner;
final stringValue = value.toString().toLowerCase();
switch (stringValue) {
case 'beginner':
case '1':
return WordDifficulty.beginner;
case 'elementary':
case '2':
return WordDifficulty.elementary;
case 'intermediate':
case '3':
return WordDifficulty.intermediate;
case 'advanced':
case '4':
return WordDifficulty.advanced;
case 'expert':
case '5':
return WordDifficulty.expert;
default:
return WordDifficulty.beginner; // 默认为初级
}
}
Word copyWith({
String? id,
String? word,
String? phonetic,
String? audioUrl,
List<WordDefinition>? definitions,
List<WordExample>? examples,
List<String>? synonyms,
List<String>? antonyms,
WordEtymology? etymology,
WordDifficulty? difficulty,
int? frequency,
String? imageUrl,
String? memoryTip,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return Word(
id: id ?? this.id,
word: word ?? this.word,
phonetic: phonetic ?? this.phonetic,
audioUrl: audioUrl ?? this.audioUrl,
definitions: definitions ?? this.definitions,
examples: examples ?? this.examples,
synonyms: synonyms ?? this.synonyms,
antonyms: antonyms ?? this.antonyms,
etymology: etymology ?? this.etymology,
difficulty: difficulty ?? this.difficulty,
frequency: frequency ?? this.frequency,
imageUrl: imageUrl ?? this.imageUrl,
memoryTip: memoryTip ?? this.memoryTip,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}
/// 单词释义
@JsonSerializable()
class WordDefinition {
/// 词性
@JsonKey(name: 'type', fromJson: _typeFromJson)
final WordType type;
/// 释义
final String definition;
/// 中文翻译
@JsonKey(name: 'translation', fromJson: _translationFromJson)
final String translation;
/// 使用频率 (1-5)
@JsonKey(defaultValue: 3)
final int frequency;
const WordDefinition({
required this.type,
required this.definition,
required this.translation,
this.frequency = 3,
});
/// 处理type字段后端可能为null或空字符串
static WordType _typeFromJson(dynamic value) {
if (value == null || value == '') return WordType.noun;
final stringValue = value.toString().toLowerCase();
switch (stringValue) {
case 'noun':
case 'n':
return WordType.noun;
case 'verb':
case 'v':
return WordType.verb;
case 'adjective':
case 'adj':
return WordType.adjective;
case 'adverb':
case 'adv':
return WordType.adverb;
case 'preposition':
case 'prep':
return WordType.preposition;
case 'conjunction':
case 'conj':
return WordType.conjunction;
case 'interjection':
case 'interj':
return WordType.interjection;
case 'pronoun':
case 'pron':
return WordType.pronoun;
case 'article':
case 'art':
return WordType.article;
case 'phrase':
return WordType.phrase;
default:
return WordType.noun; // 默认为名词
}
}
/// 处理translation字段后端可能为null
static String _translationFromJson(dynamic value) {
if (value == null) return '';
return value.toString();
}
factory WordDefinition.fromJson(Map<String, dynamic> json) => _$WordDefinitionFromJson(json);
Map<String, dynamic> toJson() => _$WordDefinitionToJson(this);
WordDefinition copyWith({
WordType? type,
String? definition,
String? translation,
int? frequency,
}) {
return WordDefinition(
type: type ?? this.type,
definition: definition ?? this.definition,
translation: translation ?? this.translation,
frequency: frequency ?? this.frequency,
);
}
}
/// 单词例句
@JsonSerializable()
class WordExample {
/// 例句
@JsonKey(name: 'example', fromJson: _sentenceFromJson)
final String sentence;
/// 中文翻译
@JsonKey(name: 'translation', fromJson: _exampleTranslationFromJson)
final String translation;
/// 音频URL
@JsonKey(name: 'audio_url')
final String? audioUrl;
/// 来源
final String? source;
const WordExample({
required this.sentence,
required this.translation,
this.audioUrl,
this.source,
});
/// 处理example字段映射到sentence
static String _sentenceFromJson(dynamic value) {
if (value == null) return '';
return value.toString();
}
/// 处理translation字段后端可能为null
static String _exampleTranslationFromJson(dynamic value) {
if (value == null) return '';
return value.toString();
}
factory WordExample.fromJson(Map<String, dynamic> json) => _$WordExampleFromJson(json);
Map<String, dynamic> toJson() => _$WordExampleToJson(this);
WordExample copyWith({
String? sentence,
String? translation,
String? audioUrl,
String? source,
}) {
return WordExample(
sentence: sentence ?? this.sentence,
translation: translation ?? this.translation,
audioUrl: audioUrl ?? this.audioUrl,
source: source ?? this.source,
);
}
}
/// 词根词缀
@JsonSerializable()
class WordEtymology {
/// 词根
final List<String> roots;
/// 前缀
final List<String> prefixes;
/// 后缀
final List<String> suffixes;
/// 词源说明
final String? origin;
const WordEtymology({
this.roots = const [],
this.prefixes = const [],
this.suffixes = const [],
this.origin,
});
factory WordEtymology.fromJson(Map<String, dynamic> json) => _$WordEtymologyFromJson(json);
Map<String, dynamic> toJson() => _$WordEtymologyToJson(this);
WordEtymology copyWith({
List<String>? roots,
List<String>? prefixes,
List<String>? suffixes,
String? origin,
}) {
return WordEtymology(
roots: roots ?? this.roots,
prefixes: prefixes ?? this.prefixes,
suffixes: suffixes ?? this.suffixes,
origin: origin ?? this.origin,
);
}
}
/// 用户单词学习记录
@JsonSerializable()
class UserWordProgress {
/// 记录ID
@JsonKey(fromJson: _idFromJson)
final String id;
/// 用户ID
@JsonKey(name: 'user_id', fromJson: _userIdFromJson)
final String userId;
/// 单词ID
@JsonKey(name: 'vocabulary_id', fromJson: _wordIdFromJson)
final String wordId;
/// 学习状态
final LearningStatus status;
/// 学习次数
@JsonKey(name: 'study_count')
final int studyCount;
/// 正确次数
@JsonKey(name: 'correct_count')
final int correctCount;
/// 错误次数
@JsonKey(name: 'wrong_count')
final int wrongCount;
/// 熟练度 (0-100)
final int proficiency;
/// 下次复习时间
@JsonKey(name: 'next_review_at')
final DateTime? nextReviewAt;
/// 复习间隔 (天)
@JsonKey(name: 'review_interval')
final int reviewInterval;
/// 首次学习时间
@JsonKey(name: 'first_studied_at')
final DateTime firstStudiedAt;
/// 最后学习时间
@JsonKey(name: 'last_studied_at')
final DateTime lastStudiedAt;
/// 掌握时间
@JsonKey(name: 'mastered_at')
final DateTime? masteredAt;
const UserWordProgress({
required this.id,
required this.userId,
required this.wordId,
required this.status,
this.studyCount = 0,
this.correctCount = 0,
this.wrongCount = 0,
this.proficiency = 0,
this.nextReviewAt,
this.reviewInterval = 1,
required this.firstStudiedAt,
required this.lastStudiedAt,
this.masteredAt,
});
factory UserWordProgress.fromJson(Map<String, dynamic> json) => _$UserWordProgressFromJson(json);
Map<String, dynamic> toJson() => _$UserWordProgressToJson(this);
UserWordProgress copyWith({
String? id,
String? userId,
String? wordId,
LearningStatus? status,
int? studyCount,
int? correctCount,
int? wrongCount,
int? proficiency,
DateTime? nextReviewAt,
int? reviewInterval,
DateTime? firstStudiedAt,
DateTime? lastStudiedAt,
DateTime? masteredAt,
}) {
return UserWordProgress(
id: id ?? this.id,
userId: userId ?? this.userId,
wordId: wordId ?? this.wordId,
status: status ?? this.status,
studyCount: studyCount ?? this.studyCount,
correctCount: correctCount ?? this.correctCount,
wrongCount: wrongCount ?? this.wrongCount,
proficiency: proficiency ?? this.proficiency,
nextReviewAt: nextReviewAt ?? this.nextReviewAt,
reviewInterval: reviewInterval ?? this.reviewInterval,
firstStudiedAt: firstStudiedAt ?? this.firstStudiedAt,
lastStudiedAt: lastStudiedAt ?? this.lastStudiedAt,
masteredAt: masteredAt ?? this.masteredAt,
);
}
/// 计算学习准确率
double get accuracy {
if (studyCount == 0) return 0.0;
return correctCount / studyCount;
}
/// 是否需要复习
bool get needsReview {
if (nextReviewAt == null) return false;
return DateTime.now().isAfter(nextReviewAt!);
}
/// 是否为新单词
bool get isNew => status == LearningStatus.newWord;
/// 是否已掌握
bool get isMastered => status == LearningStatus.mastered;
/// 类型转换方法
static String _idFromJson(dynamic value) => value.toString();
static String _userIdFromJson(dynamic value) => value.toString();
static String _wordIdFromJson(dynamic value) => value.toString();
}

View File

@@ -0,0 +1,179 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'word_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
Word _$WordFromJson(Map<String, dynamic> json) => Word(
id: Word._idFromJson(json['id']),
word: json['word'] as String,
phonetic: json['phonetic'] as String?,
audioUrl: json['audio_url'] as String?,
definitions: (json['definitions'] as List<dynamic>?)
?.map((e) => WordDefinition.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
examples: (json['examples'] as List<dynamic>?)
?.map((e) => WordExample.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
synonyms: (json['synonyms'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
antonyms: (json['antonyms'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
[],
etymology: json['etymology'] == null
? null
: WordEtymology.fromJson(json['etymology'] as Map<String, dynamic>),
difficulty: Word._difficultyFromJson(json['difficulty']),
frequency: (json['frequency'] as num?)?.toInt() ?? 0,
imageUrl: json['image_url'] as String?,
memoryTip: json['memory_tip'] as String?,
createdAt: DateTime.parse(json['created_at'] as String),
updatedAt: DateTime.parse(json['updated_at'] as String),
);
Map<String, dynamic> _$WordToJson(Word instance) => <String, dynamic>{
'id': instance.id,
'word': instance.word,
'phonetic': instance.phonetic,
'audio_url': instance.audioUrl,
'definitions': instance.definitions,
'examples': instance.examples,
'synonyms': instance.synonyms,
'antonyms': instance.antonyms,
'etymology': instance.etymology,
'difficulty': _$WordDifficultyEnumMap[instance.difficulty]!,
'frequency': instance.frequency,
'image_url': instance.imageUrl,
'memory_tip': instance.memoryTip,
'created_at': instance.createdAt.toIso8601String(),
'updated_at': instance.updatedAt.toIso8601String(),
};
const _$WordDifficultyEnumMap = {
WordDifficulty.beginner: 'beginner',
WordDifficulty.elementary: 'elementary',
WordDifficulty.intermediate: 'intermediate',
WordDifficulty.advanced: 'advanced',
WordDifficulty.expert: 'expert',
};
WordDefinition _$WordDefinitionFromJson(Map<String, dynamic> json) =>
WordDefinition(
type: WordDefinition._typeFromJson(json['type']),
definition: json['definition'] as String,
translation: WordDefinition._translationFromJson(json['translation']),
frequency: (json['frequency'] as num?)?.toInt() ?? 3,
);
Map<String, dynamic> _$WordDefinitionToJson(WordDefinition instance) =>
<String, dynamic>{
'type': _$WordTypeEnumMap[instance.type]!,
'definition': instance.definition,
'translation': instance.translation,
'frequency': instance.frequency,
};
const _$WordTypeEnumMap = {
WordType.noun: 'noun',
WordType.verb: 'verb',
WordType.adjective: 'adjective',
WordType.adverb: 'adverb',
WordType.preposition: 'preposition',
WordType.conjunction: 'conjunction',
WordType.interjection: 'interjection',
WordType.pronoun: 'pronoun',
WordType.article: 'article',
WordType.phrase: 'phrase',
};
WordExample _$WordExampleFromJson(Map<String, dynamic> json) => WordExample(
sentence: WordExample._sentenceFromJson(json['example']),
translation: WordExample._exampleTranslationFromJson(json['translation']),
audioUrl: json['audio_url'] as String?,
source: json['source'] as String?,
);
Map<String, dynamic> _$WordExampleToJson(WordExample instance) =>
<String, dynamic>{
'example': instance.sentence,
'translation': instance.translation,
'audio_url': instance.audioUrl,
'source': instance.source,
};
WordEtymology _$WordEtymologyFromJson(Map<String, dynamic> json) =>
WordEtymology(
roots:
(json['roots'] as List<dynamic>?)?.map((e) => e as String).toList() ??
const [],
prefixes: (json['prefixes'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
suffixes: (json['suffixes'] as List<dynamic>?)
?.map((e) => e as String)
.toList() ??
const [],
origin: json['origin'] as String?,
);
Map<String, dynamic> _$WordEtymologyToJson(WordEtymology instance) =>
<String, dynamic>{
'roots': instance.roots,
'prefixes': instance.prefixes,
'suffixes': instance.suffixes,
'origin': instance.origin,
};
UserWordProgress _$UserWordProgressFromJson(Map<String, dynamic> json) =>
UserWordProgress(
id: UserWordProgress._idFromJson(json['id']),
userId: UserWordProgress._userIdFromJson(json['user_id']),
wordId: UserWordProgress._wordIdFromJson(json['vocabulary_id']),
status: $enumDecode(_$LearningStatusEnumMap, json['status']),
studyCount: (json['study_count'] as num?)?.toInt() ?? 0,
correctCount: (json['correct_count'] as num?)?.toInt() ?? 0,
wrongCount: (json['wrong_count'] as num?)?.toInt() ?? 0,
proficiency: (json['proficiency'] as num?)?.toInt() ?? 0,
nextReviewAt: json['next_review_at'] == null
? null
: DateTime.parse(json['next_review_at'] as String),
reviewInterval: (json['review_interval'] as num?)?.toInt() ?? 1,
firstStudiedAt: DateTime.parse(json['first_studied_at'] as String),
lastStudiedAt: DateTime.parse(json['last_studied_at'] as String),
masteredAt: json['mastered_at'] == null
? null
: DateTime.parse(json['mastered_at'] as String),
);
Map<String, dynamic> _$UserWordProgressToJson(UserWordProgress instance) =>
<String, dynamic>{
'id': instance.id,
'user_id': instance.userId,
'vocabulary_id': instance.wordId,
'status': _$LearningStatusEnumMap[instance.status]!,
'study_count': instance.studyCount,
'correct_count': instance.correctCount,
'wrong_count': instance.wrongCount,
'proficiency': instance.proficiency,
'next_review_at': instance.nextReviewAt?.toIso8601String(),
'review_interval': instance.reviewInterval,
'first_studied_at': instance.firstStudiedAt.toIso8601String(),
'last_studied_at': instance.lastStudiedAt.toIso8601String(),
'mastered_at': instance.masteredAt?.toIso8601String(),
};
const _$LearningStatusEnumMap = {
LearningStatus.newWord: 'new',
LearningStatus.learning: 'learning',
LearningStatus.reviewing: 'reviewing',
LearningStatus.mastered: 'mastered',
LearningStatus.forgotten: 'forgotten',
};