init
This commit is contained in:
16
client/lib/features/vocabulary/models/daily_stats_model.dart
Normal file
16
client/lib/features/vocabulary/models/daily_stats_model.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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',
|
||||
};
|
||||
713
client/lib/features/vocabulary/models/learning_stats_model.dart
Normal file
713
client/lib/features/vocabulary/models/learning_stats_model.dart
Normal 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,
|
||||
}
|
||||
53
client/lib/features/vocabulary/models/review_models.dart
Normal file
53
client/lib/features/vocabulary/models/review_models.dart
Normal 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;
|
||||
}
|
||||
134
client/lib/features/vocabulary/models/study_plan_model.dart
Normal file
134
client/lib/features/vocabulary/models/study_plan_model.dart
Normal 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);
|
||||
}
|
||||
398
client/lib/features/vocabulary/models/study_session_model.dart
Normal file
398
client/lib/features/vocabulary/models/study_session_model.dart
Normal 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;
|
||||
}
|
||||
145
client/lib/features/vocabulary/models/study_session_model.g.dart
Normal file
145
client/lib/features/vocabulary/models/study_session_model.g.dart
Normal 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,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
388
client/lib/features/vocabulary/models/vocabulary_book_model.dart
Normal file
388
client/lib/features/vocabulary/models/vocabulary_book_model.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
205
client/lib/features/vocabulary/models/word_book_model.dart
Normal file
205
client/lib/features/vocabulary/models/word_book_model.dart
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
519
client/lib/features/vocabulary/models/word_model.dart
Normal file
519
client/lib/features/vocabulary/models/word_model.dart
Normal 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();
|
||||
}
|
||||
179
client/lib/features/vocabulary/models/word_model.g.dart
Normal file
179
client/lib/features/vocabulary/models/word_model.g.dart
Normal 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',
|
||||
};
|
||||
Reference in New Issue
Block a user