init
This commit is contained in:
260
client/lib/shared/models/api_response.dart
Normal file
260
client/lib/shared/models/api_response.dart
Normal file
@@ -0,0 +1,260 @@
|
||||
/// API响应基础模型
|
||||
class ApiResponse<T> {
|
||||
final bool success;
|
||||
final String message;
|
||||
final T? data;
|
||||
final String? error;
|
||||
final int? code;
|
||||
final Map<String, dynamic>? meta;
|
||||
|
||||
const ApiResponse({
|
||||
required this.success,
|
||||
required this.message,
|
||||
this.data,
|
||||
this.error,
|
||||
this.code,
|
||||
this.meta,
|
||||
});
|
||||
|
||||
factory ApiResponse.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
T Function(dynamic)? fromJsonT,
|
||||
) {
|
||||
return ApiResponse<T>(
|
||||
success: json['success'] as bool? ?? false,
|
||||
message: json['message'] as String? ?? '',
|
||||
data: json['data'] != null && fromJsonT != null
|
||||
? fromJsonT(json['data'])
|
||||
: json['data'] as T?,
|
||||
error: json['error'] as String?,
|
||||
code: json['code'] as int?,
|
||||
meta: json['meta'] as Map<String, dynamic>?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'success': success,
|
||||
'message': message,
|
||||
'data': data,
|
||||
'error': error,
|
||||
'code': code,
|
||||
'meta': meta,
|
||||
};
|
||||
}
|
||||
|
||||
/// 成功响应
|
||||
factory ApiResponse.success({
|
||||
required String message,
|
||||
T? data,
|
||||
Map<String, dynamic>? meta,
|
||||
}) {
|
||||
return ApiResponse<T>(
|
||||
success: true,
|
||||
message: message,
|
||||
data: data,
|
||||
meta: meta,
|
||||
);
|
||||
}
|
||||
|
||||
/// 失败响应
|
||||
factory ApiResponse.failure({
|
||||
required String message,
|
||||
String? error,
|
||||
int? code,
|
||||
Map<String, dynamic>? meta,
|
||||
}) {
|
||||
return ApiResponse<T>(
|
||||
success: false,
|
||||
message: message,
|
||||
error: error,
|
||||
code: code,
|
||||
meta: meta,
|
||||
);
|
||||
}
|
||||
|
||||
/// 是否成功
|
||||
bool get isSuccess => success;
|
||||
|
||||
/// 是否失败
|
||||
bool get isFailure => !success;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ApiResponse(success: $success, message: $message, data: $data, error: $error)';
|
||||
}
|
||||
}
|
||||
|
||||
/// 分页响应模型
|
||||
class PaginatedResponse<T> {
|
||||
final List<T> data;
|
||||
final PaginationMeta pagination;
|
||||
|
||||
const PaginatedResponse({
|
||||
required this.data,
|
||||
required this.pagination,
|
||||
});
|
||||
|
||||
factory PaginatedResponse.fromJson(
|
||||
Map<String, dynamic> json,
|
||||
T Function(Map<String, dynamic>) fromJsonT,
|
||||
) {
|
||||
return PaginatedResponse<T>(
|
||||
data: (json['data'] as List)
|
||||
.map((e) => fromJsonT(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
pagination: PaginationMeta.fromJson(json['pagination'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'data': data,
|
||||
'pagination': pagination.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 分页元数据模型
|
||||
class PaginationMeta {
|
||||
final int currentPage;
|
||||
final int totalPages;
|
||||
final int totalItems;
|
||||
final int itemsPerPage;
|
||||
final bool hasNextPage;
|
||||
final bool hasPreviousPage;
|
||||
|
||||
const PaginationMeta({
|
||||
required this.currentPage,
|
||||
required this.totalPages,
|
||||
required this.totalItems,
|
||||
required this.itemsPerPage,
|
||||
required this.hasNextPage,
|
||||
required this.hasPreviousPage,
|
||||
});
|
||||
|
||||
factory PaginationMeta.fromJson(Map<String, dynamic> json) {
|
||||
return PaginationMeta(
|
||||
currentPage: json['current_page'] as int,
|
||||
totalPages: json['total_pages'] as int,
|
||||
totalItems: json['total_items'] as int,
|
||||
itemsPerPage: json['items_per_page'] as int,
|
||||
hasNextPage: json['has_next_page'] as bool,
|
||||
hasPreviousPage: json['has_previous_page'] as bool,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'current_page': currentPage,
|
||||
'total_pages': totalPages,
|
||||
'total_items': totalItems,
|
||||
'items_per_page': itemsPerPage,
|
||||
'has_next_page': hasNextPage,
|
||||
'has_previous_page': hasPreviousPage,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 认证响应模型
|
||||
class AuthResponse {
|
||||
final String accessToken;
|
||||
final String refreshToken;
|
||||
final String tokenType;
|
||||
final int expiresIn;
|
||||
final Map<String, dynamic>? user;
|
||||
|
||||
const AuthResponse({
|
||||
required this.accessToken,
|
||||
required this.refreshToken,
|
||||
required this.tokenType,
|
||||
required this.expiresIn,
|
||||
this.user,
|
||||
});
|
||||
|
||||
factory AuthResponse.fromJson(Map<String, dynamic> json) {
|
||||
return AuthResponse(
|
||||
accessToken: json['access_token'] as String,
|
||||
refreshToken: json['refresh_token'] as String,
|
||||
tokenType: json['token_type'] as String? ?? 'Bearer',
|
||||
expiresIn: json['expires_in'] as int,
|
||||
user: json['user'] as Map<String, dynamic>?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'access_token': accessToken,
|
||||
'refresh_token': refreshToken,
|
||||
'token_type': tokenType,
|
||||
'expires_in': expiresIn,
|
||||
'user': user,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 错误响应模型
|
||||
class ErrorResponse {
|
||||
final String message;
|
||||
final String? code;
|
||||
final List<ValidationError>? validationErrors;
|
||||
final Map<String, dynamic>? details;
|
||||
|
||||
const ErrorResponse({
|
||||
required this.message,
|
||||
this.code,
|
||||
this.validationErrors,
|
||||
this.details,
|
||||
});
|
||||
|
||||
factory ErrorResponse.fromJson(Map<String, dynamic> json) {
|
||||
return ErrorResponse(
|
||||
message: json['message'] as String,
|
||||
code: json['code'] as String?,
|
||||
validationErrors: json['validation_errors'] != null
|
||||
? (json['validation_errors'] as List)
|
||||
.map((e) => ValidationError.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: null,
|
||||
details: json['details'] as Map<String, dynamic>?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'message': message,
|
||||
'code': code,
|
||||
'validation_errors': validationErrors?.map((e) => e.toJson()).toList(),
|
||||
'details': details,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 验证错误模型
|
||||
class ValidationError {
|
||||
final String field;
|
||||
final String message;
|
||||
final String? code;
|
||||
|
||||
const ValidationError({
|
||||
required this.field,
|
||||
required this.message,
|
||||
this.code,
|
||||
});
|
||||
|
||||
factory ValidationError.fromJson(Map<String, dynamic> json) {
|
||||
return ValidationError(
|
||||
field: json['field'] as String,
|
||||
message: json['message'] as String,
|
||||
code: json['code'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'field': field,
|
||||
'message': message,
|
||||
'code': code,
|
||||
};
|
||||
}
|
||||
}
|
||||
90
client/lib/shared/models/notification_model.dart
Normal file
90
client/lib/shared/models/notification_model.dart
Normal file
@@ -0,0 +1,90 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'notification_model.g.dart';
|
||||
|
||||
/// 通知模型
|
||||
@JsonSerializable()
|
||||
class NotificationModel {
|
||||
final int id;
|
||||
|
||||
@JsonKey(name: 'user_id')
|
||||
final int userId;
|
||||
|
||||
final String type;
|
||||
final String title;
|
||||
final String content;
|
||||
final String? link;
|
||||
|
||||
@JsonKey(name: 'is_read')
|
||||
final bool isRead;
|
||||
|
||||
final int priority;
|
||||
|
||||
@JsonKey(name: 'created_at')
|
||||
final DateTime createdAt;
|
||||
|
||||
@JsonKey(name: 'read_at')
|
||||
final DateTime? readAt;
|
||||
|
||||
const NotificationModel({
|
||||
required this.id,
|
||||
required this.userId,
|
||||
required this.type,
|
||||
required this.title,
|
||||
required this.content,
|
||||
this.link,
|
||||
required this.isRead,
|
||||
required this.priority,
|
||||
required this.createdAt,
|
||||
this.readAt,
|
||||
});
|
||||
|
||||
factory NotificationModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$NotificationModelFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$NotificationModelToJson(this);
|
||||
|
||||
/// 通知类型
|
||||
String get typeLabel {
|
||||
switch (type) {
|
||||
case 'system':
|
||||
return '系统通知';
|
||||
case 'learning':
|
||||
return '学习提醒';
|
||||
case 'achievement':
|
||||
return '成就通知';
|
||||
default:
|
||||
return '通知';
|
||||
}
|
||||
}
|
||||
|
||||
/// 优先级
|
||||
String get priorityLabel {
|
||||
switch (priority) {
|
||||
case 0:
|
||||
return '普通';
|
||||
case 1:
|
||||
return '重要';
|
||||
case 2:
|
||||
return '紧急';
|
||||
default:
|
||||
return '普通';
|
||||
}
|
||||
}
|
||||
|
||||
/// 格式化时间
|
||||
String get timeAgo {
|
||||
final now = DateTime.now();
|
||||
final difference = now.difference(createdAt);
|
||||
|
||||
if (difference.inDays > 0) {
|
||||
return '${difference.inDays}天前';
|
||||
} else if (difference.inHours > 0) {
|
||||
return '${difference.inHours}小时前';
|
||||
} else if (difference.inMinutes > 0) {
|
||||
return '${difference.inMinutes}分钟前';
|
||||
} else {
|
||||
return '刚刚';
|
||||
}
|
||||
}
|
||||
}
|
||||
37
client/lib/shared/models/notification_model.g.dart
Normal file
37
client/lib/shared/models/notification_model.g.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'notification_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
NotificationModel _$NotificationModelFromJson(Map<String, dynamic> json) =>
|
||||
NotificationModel(
|
||||
id: (json['id'] as num).toInt(),
|
||||
userId: (json['user_id'] as num).toInt(),
|
||||
type: json['type'] as String,
|
||||
title: json['title'] as String,
|
||||
content: json['content'] as String,
|
||||
link: json['link'] as String?,
|
||||
isRead: json['is_read'] as bool,
|
||||
priority: (json['priority'] as num).toInt(),
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
readAt: json['read_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['read_at'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$NotificationModelToJson(NotificationModel instance) =>
|
||||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'user_id': instance.userId,
|
||||
'type': instance.type,
|
||||
'title': instance.title,
|
||||
'content': instance.content,
|
||||
'link': instance.link,
|
||||
'is_read': instance.isRead,
|
||||
'priority': instance.priority,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'read_at': instance.readAt?.toIso8601String(),
|
||||
};
|
||||
220
client/lib/shared/models/user_model.dart
Normal file
220
client/lib/shared/models/user_model.dart
Normal file
@@ -0,0 +1,220 @@
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
part 'user_model.g.dart';
|
||||
|
||||
/// 用户模型
|
||||
@JsonSerializable()
|
||||
class UserModel {
|
||||
@JsonKey(name: 'user_id')
|
||||
final int userId;
|
||||
|
||||
final String username;
|
||||
final String email;
|
||||
final String? nickname;
|
||||
final String? avatar;
|
||||
final String? phone;
|
||||
final DateTime? birthday;
|
||||
final String? gender;
|
||||
final String? bio;
|
||||
|
||||
@JsonKey(name: 'learning_level')
|
||||
final String learningLevel;
|
||||
|
||||
@JsonKey(name: 'target_language')
|
||||
final String targetLanguage;
|
||||
|
||||
@JsonKey(name: 'native_language')
|
||||
final String nativeLanguage;
|
||||
|
||||
@JsonKey(name: 'daily_goal')
|
||||
final int dailyGoal;
|
||||
|
||||
@JsonKey(name: 'study_streak')
|
||||
final int studyStreak;
|
||||
|
||||
@JsonKey(name: 'total_study_days')
|
||||
final int totalStudyDays;
|
||||
|
||||
@JsonKey(name: 'vocabulary_count')
|
||||
final int vocabularyCount;
|
||||
|
||||
@JsonKey(name: 'experience_points')
|
||||
final int experiencePoints;
|
||||
|
||||
@JsonKey(name: 'current_level')
|
||||
final int currentLevel;
|
||||
|
||||
@JsonKey(name: 'created_at')
|
||||
final DateTime createdAt;
|
||||
|
||||
@JsonKey(name: 'updated_at')
|
||||
final DateTime updatedAt;
|
||||
|
||||
@JsonKey(name: 'last_login_at')
|
||||
final DateTime? lastLoginAt;
|
||||
|
||||
@JsonKey(name: 'is_premium')
|
||||
final bool isPremium;
|
||||
|
||||
@JsonKey(name: 'premium_expires_at')
|
||||
final DateTime? premiumExpiresAt;
|
||||
|
||||
const UserModel({
|
||||
required this.userId,
|
||||
required this.username,
|
||||
required this.email,
|
||||
this.nickname,
|
||||
this.avatar,
|
||||
this.phone,
|
||||
this.birthday,
|
||||
this.gender,
|
||||
this.bio,
|
||||
required this.learningLevel,
|
||||
required this.targetLanguage,
|
||||
required this.nativeLanguage,
|
||||
required this.dailyGoal,
|
||||
required this.studyStreak,
|
||||
required this.totalStudyDays,
|
||||
required this.vocabularyCount,
|
||||
required this.experiencePoints,
|
||||
required this.currentLevel,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
this.lastLoginAt,
|
||||
required this.isPremium,
|
||||
this.premiumExpiresAt,
|
||||
});
|
||||
|
||||
factory UserModel.fromJson(Map<String, dynamic> json) => _$UserModelFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$UserModelToJson(this);
|
||||
|
||||
UserModel copyWith({
|
||||
int? userId,
|
||||
String? username,
|
||||
String? email,
|
||||
String? nickname,
|
||||
String? avatar,
|
||||
String? phone,
|
||||
DateTime? birthday,
|
||||
String? gender,
|
||||
String? bio,
|
||||
String? learningLevel,
|
||||
String? targetLanguage,
|
||||
String? nativeLanguage,
|
||||
int? dailyGoal,
|
||||
int? studyStreak,
|
||||
int? totalStudyDays,
|
||||
int? vocabularyCount,
|
||||
int? experiencePoints,
|
||||
int? currentLevel,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? lastLoginAt,
|
||||
bool? isPremium,
|
||||
DateTime? premiumExpiresAt,
|
||||
}) {
|
||||
return UserModel(
|
||||
userId: userId ?? this.userId,
|
||||
username: username ?? this.username,
|
||||
email: email ?? this.email,
|
||||
nickname: nickname ?? this.nickname,
|
||||
avatar: avatar ?? this.avatar,
|
||||
phone: phone ?? this.phone,
|
||||
birthday: birthday ?? this.birthday,
|
||||
gender: gender ?? this.gender,
|
||||
bio: bio ?? this.bio,
|
||||
learningLevel: learningLevel ?? this.learningLevel,
|
||||
targetLanguage: targetLanguage ?? this.targetLanguage,
|
||||
nativeLanguage: nativeLanguage ?? this.nativeLanguage,
|
||||
dailyGoal: dailyGoal ?? this.dailyGoal,
|
||||
studyStreak: studyStreak ?? this.studyStreak,
|
||||
totalStudyDays: totalStudyDays ?? this.totalStudyDays,
|
||||
vocabularyCount: vocabularyCount ?? this.vocabularyCount,
|
||||
experiencePoints: experiencePoints ?? this.experiencePoints,
|
||||
currentLevel: currentLevel ?? this.currentLevel,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
lastLoginAt: lastLoginAt ?? this.lastLoginAt,
|
||||
isPremium: isPremium ?? this.isPremium,
|
||||
premiumExpiresAt: premiumExpiresAt ?? this.premiumExpiresAt,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is UserModel &&
|
||||
other.userId == userId &&
|
||||
other.username == username &&
|
||||
other.email == email;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
return userId.hashCode ^
|
||||
username.hashCode ^
|
||||
email.hashCode;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'UserModel(userId: $userId, username: $username, email: $email)';
|
||||
}
|
||||
}
|
||||
|
||||
/// 用户统计信息模型
|
||||
@JsonSerializable()
|
||||
class UserStatsModel {
|
||||
@JsonKey(name: 'total_words_learned')
|
||||
final int totalWordsLearned;
|
||||
|
||||
@JsonKey(name: 'words_learned_today')
|
||||
final int wordsLearnedToday;
|
||||
|
||||
@JsonKey(name: 'study_time_today')
|
||||
final int studyTimeToday; // 分钟
|
||||
|
||||
@JsonKey(name: 'total_study_time')
|
||||
final int totalStudyTime; // 分钟
|
||||
|
||||
@JsonKey(name: 'listening_score')
|
||||
final double listeningScore;
|
||||
|
||||
@JsonKey(name: 'reading_score')
|
||||
final double readingScore;
|
||||
|
||||
@JsonKey(name: 'writing_score')
|
||||
final double writingScore;
|
||||
|
||||
@JsonKey(name: 'speaking_score')
|
||||
final double speakingScore;
|
||||
|
||||
@JsonKey(name: 'overall_score')
|
||||
final double overallScore;
|
||||
|
||||
@JsonKey(name: 'weekly_progress')
|
||||
final List<int> weeklyProgress;
|
||||
|
||||
@JsonKey(name: 'monthly_progress')
|
||||
final List<int> monthlyProgress;
|
||||
|
||||
const UserStatsModel({
|
||||
required this.totalWordsLearned,
|
||||
required this.wordsLearnedToday,
|
||||
required this.studyTimeToday,
|
||||
required this.totalStudyTime,
|
||||
required this.listeningScore,
|
||||
required this.readingScore,
|
||||
required this.writingScore,
|
||||
required this.speakingScore,
|
||||
required this.overallScore,
|
||||
required this.weeklyProgress,
|
||||
required this.monthlyProgress,
|
||||
});
|
||||
|
||||
factory UserStatsModel.fromJson(Map<String, dynamic> json) => _$UserStatsModelFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$UserStatsModelToJson(this);
|
||||
}
|
||||
99
client/lib/shared/models/user_model.g.dart
Normal file
99
client/lib/shared/models/user_model.g.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'user_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel(
|
||||
userId: (json['user_id'] as num).toInt(),
|
||||
username: json['username'] as String,
|
||||
email: json['email'] as String,
|
||||
nickname: json['nickname'] as String?,
|
||||
avatar: json['avatar'] as String?,
|
||||
phone: json['phone'] as String?,
|
||||
birthday: json['birthday'] == null
|
||||
? null
|
||||
: DateTime.parse(json['birthday'] as String),
|
||||
gender: json['gender'] as String?,
|
||||
bio: json['bio'] as String?,
|
||||
learningLevel: json['learning_level'] as String,
|
||||
targetLanguage: json['target_language'] as String,
|
||||
nativeLanguage: json['native_language'] as String,
|
||||
dailyGoal: (json['daily_goal'] as num).toInt(),
|
||||
studyStreak: (json['study_streak'] as num).toInt(),
|
||||
totalStudyDays: (json['total_study_days'] as num).toInt(),
|
||||
vocabularyCount: (json['vocabulary_count'] as num).toInt(),
|
||||
experiencePoints: (json['experience_points'] as num).toInt(),
|
||||
currentLevel: (json['current_level'] as num).toInt(),
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
lastLoginAt: json['last_login_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['last_login_at'] as String),
|
||||
isPremium: json['is_premium'] as bool,
|
||||
premiumExpiresAt: json['premium_expires_at'] == null
|
||||
? null
|
||||
: DateTime.parse(json['premium_expires_at'] as String),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
|
||||
'user_id': instance.userId,
|
||||
'username': instance.username,
|
||||
'email': instance.email,
|
||||
'nickname': instance.nickname,
|
||||
'avatar': instance.avatar,
|
||||
'phone': instance.phone,
|
||||
'birthday': instance.birthday?.toIso8601String(),
|
||||
'gender': instance.gender,
|
||||
'bio': instance.bio,
|
||||
'learning_level': instance.learningLevel,
|
||||
'target_language': instance.targetLanguage,
|
||||
'native_language': instance.nativeLanguage,
|
||||
'daily_goal': instance.dailyGoal,
|
||||
'study_streak': instance.studyStreak,
|
||||
'total_study_days': instance.totalStudyDays,
|
||||
'vocabulary_count': instance.vocabularyCount,
|
||||
'experience_points': instance.experiencePoints,
|
||||
'current_level': instance.currentLevel,
|
||||
'created_at': instance.createdAt.toIso8601String(),
|
||||
'updated_at': instance.updatedAt.toIso8601String(),
|
||||
'last_login_at': instance.lastLoginAt?.toIso8601String(),
|
||||
'is_premium': instance.isPremium,
|
||||
'premium_expires_at': instance.premiumExpiresAt?.toIso8601String(),
|
||||
};
|
||||
|
||||
UserStatsModel _$UserStatsModelFromJson(Map<String, dynamic> json) =>
|
||||
UserStatsModel(
|
||||
totalWordsLearned: (json['total_words_learned'] as num).toInt(),
|
||||
wordsLearnedToday: (json['words_learned_today'] as num).toInt(),
|
||||
studyTimeToday: (json['study_time_today'] as num).toInt(),
|
||||
totalStudyTime: (json['total_study_time'] as num).toInt(),
|
||||
listeningScore: (json['listening_score'] as num).toDouble(),
|
||||
readingScore: (json['reading_score'] as num).toDouble(),
|
||||
writingScore: (json['writing_score'] as num).toDouble(),
|
||||
speakingScore: (json['speaking_score'] as num).toDouble(),
|
||||
overallScore: (json['overall_score'] as num).toDouble(),
|
||||
weeklyProgress: (json['weekly_progress'] as List<dynamic>)
|
||||
.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
monthlyProgress: (json['monthly_progress'] as List<dynamic>)
|
||||
.map((e) => (e as num).toInt())
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$UserStatsModelToJson(UserStatsModel instance) =>
|
||||
<String, dynamic>{
|
||||
'total_words_learned': instance.totalWordsLearned,
|
||||
'words_learned_today': instance.wordsLearnedToday,
|
||||
'study_time_today': instance.studyTimeToday,
|
||||
'total_study_time': instance.totalStudyTime,
|
||||
'listening_score': instance.listeningScore,
|
||||
'reading_score': instance.readingScore,
|
||||
'writing_score': instance.writingScore,
|
||||
'speaking_score': instance.speakingScore,
|
||||
'overall_score': instance.overallScore,
|
||||
'weekly_progress': instance.weeklyProgress,
|
||||
'monthly_progress': instance.monthlyProgress,
|
||||
};
|
||||
292
client/lib/shared/models/vocabulary_model.dart
Normal file
292
client/lib/shared/models/vocabulary_model.dart
Normal file
@@ -0,0 +1,292 @@
|
||||
/// 词汇模型
|
||||
class VocabularyModel {
|
||||
final int wordId;
|
||||
final String word;
|
||||
final String pronunciation;
|
||||
final String phonetic;
|
||||
final List<WordMeaning> meanings;
|
||||
final String? etymology;
|
||||
final List<String> examples;
|
||||
final String? imageUrl;
|
||||
final String? audioUrl;
|
||||
final int difficulty;
|
||||
final List<String> tags;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
const VocabularyModel({
|
||||
required this.wordId,
|
||||
required this.word,
|
||||
required this.pronunciation,
|
||||
required this.phonetic,
|
||||
required this.meanings,
|
||||
this.etymology,
|
||||
required this.examples,
|
||||
this.imageUrl,
|
||||
this.audioUrl,
|
||||
required this.difficulty,
|
||||
required this.tags,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory VocabularyModel.fromJson(Map<String, dynamic> json) {
|
||||
return VocabularyModel(
|
||||
wordId: json['word_id'] as int,
|
||||
word: json['word'] as String,
|
||||
pronunciation: json['pronunciation'] as String,
|
||||
phonetic: json['phonetic'] as String,
|
||||
meanings: (json['meanings'] as List)
|
||||
.map((e) => WordMeaning.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
etymology: json['etymology'] as String?,
|
||||
examples: (json['examples'] as List).cast<String>(),
|
||||
imageUrl: json['image_url'] as String?,
|
||||
audioUrl: json['audio_url'] as String?,
|
||||
difficulty: json['difficulty'] as int,
|
||||
tags: (json['tags'] as List).cast<String>(),
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'word_id': wordId,
|
||||
'word': word,
|
||||
'pronunciation': pronunciation,
|
||||
'phonetic': phonetic,
|
||||
'meanings': meanings.map((e) => e.toJson()).toList(),
|
||||
'etymology': etymology,
|
||||
'examples': examples,
|
||||
'image_url': imageUrl,
|
||||
'audio_url': audioUrl,
|
||||
'difficulty': difficulty,
|
||||
'tags': tags,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
VocabularyModel copyWith({
|
||||
int? wordId,
|
||||
String? word,
|
||||
String? pronunciation,
|
||||
String? phonetic,
|
||||
List<WordMeaning>? meanings,
|
||||
String? etymology,
|
||||
List<String>? examples,
|
||||
String? imageUrl,
|
||||
String? audioUrl,
|
||||
int? difficulty,
|
||||
List<String>? tags,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return VocabularyModel(
|
||||
wordId: wordId ?? this.wordId,
|
||||
word: word ?? this.word,
|
||||
pronunciation: pronunciation ?? this.pronunciation,
|
||||
phonetic: phonetic ?? this.phonetic,
|
||||
meanings: meanings ?? this.meanings,
|
||||
etymology: etymology ?? this.etymology,
|
||||
examples: examples ?? this.examples,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
audioUrl: audioUrl ?? this.audioUrl,
|
||||
difficulty: difficulty ?? this.difficulty,
|
||||
tags: tags ?? this.tags,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 词汇含义模型
|
||||
class WordMeaning {
|
||||
final String partOfSpeech;
|
||||
final String definition;
|
||||
final String? chineseDefinition;
|
||||
final List<String> synonyms;
|
||||
final List<String> antonyms;
|
||||
final List<String> examples;
|
||||
|
||||
const WordMeaning({
|
||||
required this.partOfSpeech,
|
||||
required this.definition,
|
||||
this.chineseDefinition,
|
||||
required this.synonyms,
|
||||
required this.antonyms,
|
||||
required this.examples,
|
||||
});
|
||||
|
||||
factory WordMeaning.fromJson(Map<String, dynamic> json) {
|
||||
return WordMeaning(
|
||||
partOfSpeech: json['part_of_speech'] as String,
|
||||
definition: json['definition'] as String,
|
||||
chineseDefinition: json['chinese_definition'] as String?,
|
||||
synonyms: (json['synonyms'] as List).cast<String>(),
|
||||
antonyms: (json['antonyms'] as List).cast<String>(),
|
||||
examples: (json['examples'] as List).cast<String>(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'part_of_speech': partOfSpeech,
|
||||
'definition': definition,
|
||||
'chinese_definition': chineseDefinition,
|
||||
'synonyms': synonyms,
|
||||
'antonyms': antonyms,
|
||||
'examples': examples,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 用户词汇学习记录模型
|
||||
class UserVocabularyModel {
|
||||
final int userWordId;
|
||||
final int userId;
|
||||
final int wordId;
|
||||
final VocabularyModel? vocabulary;
|
||||
final LearningStatus status;
|
||||
final int reviewCount;
|
||||
final int correctCount;
|
||||
final int incorrectCount;
|
||||
final double masteryLevel;
|
||||
final DateTime? lastReviewAt;
|
||||
final DateTime? nextReviewAt;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
const UserVocabularyModel({
|
||||
required this.userWordId,
|
||||
required this.userId,
|
||||
required this.wordId,
|
||||
this.vocabulary,
|
||||
required this.status,
|
||||
required this.reviewCount,
|
||||
required this.correctCount,
|
||||
required this.incorrectCount,
|
||||
required this.masteryLevel,
|
||||
this.lastReviewAt,
|
||||
this.nextReviewAt,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory UserVocabularyModel.fromJson(Map<String, dynamic> json) {
|
||||
return UserVocabularyModel(
|
||||
userWordId: json['user_word_id'] as int,
|
||||
userId: json['user_id'] as int,
|
||||
wordId: json['word_id'] as int,
|
||||
vocabulary: json['vocabulary'] != null
|
||||
? VocabularyModel.fromJson(json['vocabulary'] as Map<String, dynamic>)
|
||||
: null,
|
||||
status: LearningStatus.values.firstWhere(
|
||||
(e) => e.name == json['status'],
|
||||
orElse: () => LearningStatus.new_word,
|
||||
),
|
||||
reviewCount: json['review_count'] as int,
|
||||
correctCount: json['correct_count'] as int,
|
||||
incorrectCount: json['incorrect_count'] as int,
|
||||
masteryLevel: (json['mastery_level'] as num).toDouble(),
|
||||
lastReviewAt: json['last_review_at'] != null
|
||||
? DateTime.parse(json['last_review_at'] as String)
|
||||
: null,
|
||||
nextReviewAt: json['next_review_at'] != null
|
||||
? DateTime.parse(json['next_review_at'] as String)
|
||||
: null,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'user_word_id': userWordId,
|
||||
'user_id': userId,
|
||||
'word_id': wordId,
|
||||
'vocabulary': vocabulary?.toJson(),
|
||||
'status': status.name,
|
||||
'review_count': reviewCount,
|
||||
'correct_count': correctCount,
|
||||
'incorrect_count': incorrectCount,
|
||||
'mastery_level': masteryLevel,
|
||||
'last_review_at': lastReviewAt?.toIso8601String(),
|
||||
'next_review_at': nextReviewAt?.toIso8601String(),
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 学习状态枚举
|
||||
enum LearningStatus {
|
||||
new_word('new_word', '新单词'),
|
||||
learning('learning', '学习中'),
|
||||
reviewing('reviewing', '复习中'),
|
||||
mastered('mastered', '已掌握'),
|
||||
forgotten('forgotten', '已遗忘');
|
||||
|
||||
const LearningStatus(this.value, this.label);
|
||||
|
||||
final String value;
|
||||
final String label;
|
||||
}
|
||||
|
||||
/// 词库模型
|
||||
class VocabularyBookModel {
|
||||
final int bookId;
|
||||
final String name;
|
||||
final String description;
|
||||
final String category;
|
||||
final String level;
|
||||
final int totalWords;
|
||||
final String? coverImage;
|
||||
final bool isPremium;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
const VocabularyBookModel({
|
||||
required this.bookId,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.category,
|
||||
required this.level,
|
||||
required this.totalWords,
|
||||
this.coverImage,
|
||||
required this.isPremium,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory VocabularyBookModel.fromJson(Map<String, dynamic> json) {
|
||||
return VocabularyBookModel(
|
||||
bookId: json['book_id'] as int,
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String,
|
||||
category: json['category'] as String,
|
||||
level: json['level'] as String,
|
||||
totalWords: json['total_words'] as int,
|
||||
coverImage: json['cover_image'] as String?,
|
||||
isPremium: json['is_premium'] as bool,
|
||||
createdAt: DateTime.parse(json['created_at'] as String),
|
||||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'book_id': bookId,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'category': category,
|
||||
'level': level,
|
||||
'total_words': totalWords,
|
||||
'cover_image': coverImage,
|
||||
'is_premium': isPremium,
|
||||
'created_at': createdAt.toIso8601String(),
|
||||
'updated_at': updatedAt.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user