519 lines
12 KiB
Dart
519 lines
12 KiB
Dart
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();
|
||
} |