init
This commit is contained in:
559
client/lib/features/comprehensive_test/models/test_models.dart
Normal file
559
client/lib/features/comprehensive_test/models/test_models.dart
Normal file
@@ -0,0 +1,559 @@
|
||||
/// 测试类型枚举
|
||||
enum TestType {
|
||||
quick, // 快速测试
|
||||
standard, // 标准测试
|
||||
full, // 完整测试
|
||||
mock, // 模拟考试
|
||||
vocabulary, // 词汇专项
|
||||
grammar, // 语法专项
|
||||
reading, // 阅读专项
|
||||
listening, // 听力专项
|
||||
speaking, // 口语专项
|
||||
writing, // 写作专项
|
||||
}
|
||||
|
||||
/// 测试状态枚举
|
||||
enum TestStatus {
|
||||
notStarted, // 未开始
|
||||
inProgress, // 进行中
|
||||
paused, // 暂停
|
||||
completed, // 已完成
|
||||
expired, // 已过期
|
||||
}
|
||||
|
||||
/// 题目类型枚举
|
||||
enum QuestionType {
|
||||
multipleChoice, // 单选题
|
||||
multipleSelect, // 多选题
|
||||
fillInBlank, // 填空题
|
||||
reading, // 阅读理解
|
||||
listening, // 听力理解
|
||||
speaking, // 口语题
|
||||
writing, // 写作题
|
||||
}
|
||||
|
||||
/// 难度等级枚举
|
||||
enum DifficultyLevel {
|
||||
beginner, // 初级
|
||||
elementary, // 基础
|
||||
intermediate, // 中级
|
||||
upperIntermediate, // 中高级
|
||||
advanced, // 高级
|
||||
expert, // 专家级
|
||||
}
|
||||
|
||||
/// 技能类型枚举
|
||||
enum SkillType {
|
||||
vocabulary, // 词汇
|
||||
grammar, // 语法
|
||||
reading, // 阅读
|
||||
listening, // 听力
|
||||
speaking, // 口语
|
||||
writing, // 写作
|
||||
}
|
||||
|
||||
/// 测试题目模型
|
||||
class TestQuestion {
|
||||
final String id;
|
||||
final QuestionType type;
|
||||
final SkillType skillType;
|
||||
final DifficultyLevel difficulty;
|
||||
final String content;
|
||||
final List<String> options;
|
||||
final List<String> correctAnswers;
|
||||
final String? audioUrl;
|
||||
final String? imageUrl;
|
||||
final String? explanation;
|
||||
final int points;
|
||||
final int timeLimit; // 秒
|
||||
final Map<String, dynamic>? metadata;
|
||||
|
||||
const TestQuestion({
|
||||
required this.id,
|
||||
required this.type,
|
||||
required this.skillType,
|
||||
required this.difficulty,
|
||||
required this.content,
|
||||
this.options = const [],
|
||||
this.correctAnswers = const [],
|
||||
this.audioUrl,
|
||||
this.imageUrl,
|
||||
this.explanation,
|
||||
this.points = 1,
|
||||
this.timeLimit = 60,
|
||||
this.metadata,
|
||||
});
|
||||
|
||||
factory TestQuestion.fromJson(Map<String, dynamic> json) {
|
||||
return TestQuestion(
|
||||
id: json['id'] as String,
|
||||
type: QuestionType.values.firstWhere(
|
||||
(e) => e.name == json['type'],
|
||||
orElse: () => QuestionType.multipleChoice,
|
||||
),
|
||||
skillType: SkillType.values.firstWhere(
|
||||
(e) => e.name == json['skillType'],
|
||||
orElse: () => SkillType.vocabulary,
|
||||
),
|
||||
difficulty: DifficultyLevel.values.firstWhere(
|
||||
(e) => e.name == json['difficulty'],
|
||||
orElse: () => DifficultyLevel.intermediate,
|
||||
),
|
||||
content: json['content'] as String,
|
||||
options: List<String>.from(json['options'] ?? []),
|
||||
correctAnswers: List<String>.from(json['correctAnswers'] ?? []),
|
||||
audioUrl: json['audioUrl'] as String?,
|
||||
imageUrl: json['imageUrl'] as String?,
|
||||
explanation: json['explanation'] as String?,
|
||||
points: json['points'] as int? ?? 1,
|
||||
timeLimit: json['timeLimit'] as int? ?? 60,
|
||||
metadata: json['metadata'] as Map<String, dynamic>?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'type': type.name,
|
||||
'skillType': skillType.name,
|
||||
'difficulty': difficulty.name,
|
||||
'content': content,
|
||||
'options': options,
|
||||
'correctAnswers': correctAnswers,
|
||||
'audioUrl': audioUrl,
|
||||
'imageUrl': imageUrl,
|
||||
'explanation': explanation,
|
||||
'points': points,
|
||||
'timeLimit': timeLimit,
|
||||
'metadata': metadata,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 语言技能枚举 (用于UI显示)
|
||||
enum LanguageSkill {
|
||||
listening, // 听力
|
||||
reading, // 阅读
|
||||
speaking, // 口语
|
||||
writing, // 写作
|
||||
vocabulary, // 词汇
|
||||
grammar, // 语法
|
||||
pronunciation, // 发音
|
||||
comprehension, // 理解
|
||||
}
|
||||
|
||||
/// 用户技能统计模型
|
||||
class UserSkillStatistics {
|
||||
final SkillType skillType;
|
||||
final double averageScore;
|
||||
final int totalTests;
|
||||
final int correctAnswers;
|
||||
final int totalQuestions;
|
||||
final DifficultyLevel strongestLevel;
|
||||
final DifficultyLevel weakestLevel;
|
||||
final DateTime lastTestDate;
|
||||
|
||||
const UserSkillStatistics({
|
||||
required this.skillType,
|
||||
required this.averageScore,
|
||||
required this.totalTests,
|
||||
required this.correctAnswers,
|
||||
required this.totalQuestions,
|
||||
required this.strongestLevel,
|
||||
required this.weakestLevel,
|
||||
required this.lastTestDate,
|
||||
});
|
||||
|
||||
factory UserSkillStatistics.fromJson(Map<String, dynamic> json) {
|
||||
return UserSkillStatistics(
|
||||
skillType: SkillType.values.firstWhere(
|
||||
(e) => e.toString().split('.').last == json['skillType'],
|
||||
),
|
||||
averageScore: (json['averageScore'] as num).toDouble(),
|
||||
totalTests: json['totalTests'] as int,
|
||||
correctAnswers: json['correctAnswers'] as int,
|
||||
totalQuestions: json['totalQuestions'] as int,
|
||||
strongestLevel: DifficultyLevel.values.firstWhere(
|
||||
(e) => e.toString().split('.').last == json['strongestLevel'],
|
||||
),
|
||||
weakestLevel: DifficultyLevel.values.firstWhere(
|
||||
(e) => e.toString().split('.').last == json['weakestLevel'],
|
||||
),
|
||||
lastTestDate: DateTime.parse(json['lastTestDate']),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'skillType': skillType.toString().split('.').last,
|
||||
'averageScore': averageScore,
|
||||
'totalTests': totalTests,
|
||||
'correctAnswers': correctAnswers,
|
||||
'totalQuestions': totalQuestions,
|
||||
'strongestLevel': strongestLevel.toString().split('.').last,
|
||||
'weakestLevel': weakestLevel.toString().split('.').last,
|
||||
'lastTestDate': lastTestDate.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
double get accuracy => totalQuestions > 0 ? correctAnswers / totalQuestions : 0.0;
|
||||
}
|
||||
|
||||
/// 用户答案模型
|
||||
class UserAnswer {
|
||||
final String questionId;
|
||||
final List<String> selectedAnswers;
|
||||
final String? textAnswer;
|
||||
final String? audioUrl;
|
||||
final DateTime answeredAt;
|
||||
final int timeSpent; // 秒
|
||||
|
||||
const UserAnswer({
|
||||
required this.questionId,
|
||||
this.selectedAnswers = const [],
|
||||
this.textAnswer,
|
||||
this.audioUrl,
|
||||
required this.answeredAt,
|
||||
required this.timeSpent,
|
||||
});
|
||||
|
||||
factory UserAnswer.fromJson(Map<String, dynamic> json) {
|
||||
return UserAnswer(
|
||||
questionId: json['questionId'] as String,
|
||||
selectedAnswers: List<String>.from(json['selectedAnswers'] ?? []),
|
||||
textAnswer: json['textAnswer'] as String?,
|
||||
audioUrl: json['audioUrl'] as String?,
|
||||
answeredAt: DateTime.parse(json['answeredAt'] as String),
|
||||
timeSpent: json['timeSpent'] as int,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'questionId': questionId,
|
||||
'selectedAnswers': selectedAnswers,
|
||||
'textAnswer': textAnswer,
|
||||
'audioUrl': audioUrl,
|
||||
'answeredAt': answeredAt.toIso8601String(),
|
||||
'timeSpent': timeSpent,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 技能得分模型
|
||||
class SkillScore {
|
||||
final SkillType skillType;
|
||||
final int score;
|
||||
final int maxScore;
|
||||
final double percentage;
|
||||
final DifficultyLevel level;
|
||||
final String feedback;
|
||||
|
||||
const SkillScore({
|
||||
required this.skillType,
|
||||
required this.score,
|
||||
required this.maxScore,
|
||||
required this.percentage,
|
||||
required this.level,
|
||||
required this.feedback,
|
||||
});
|
||||
|
||||
factory SkillScore.fromJson(Map<String, dynamic> json) {
|
||||
return SkillScore(
|
||||
skillType: SkillType.values.firstWhere(
|
||||
(e) => e.name == json['skillType'],
|
||||
orElse: () => SkillType.vocabulary,
|
||||
),
|
||||
score: json['score'] as int,
|
||||
maxScore: json['maxScore'] as int,
|
||||
percentage: (json['percentage'] as num).toDouble(),
|
||||
level: DifficultyLevel.values.firstWhere(
|
||||
(e) => e.name == json['level'],
|
||||
orElse: () => DifficultyLevel.intermediate,
|
||||
),
|
||||
feedback: json['feedback'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'skillType': skillType.name,
|
||||
'score': score,
|
||||
'maxScore': maxScore,
|
||||
'percentage': percentage,
|
||||
'level': level.name,
|
||||
'feedback': feedback,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试结果模型
|
||||
class TestResult {
|
||||
final String id;
|
||||
final String testId;
|
||||
final String userId;
|
||||
final TestType testType;
|
||||
final int totalScore;
|
||||
final int maxScore;
|
||||
final double percentage;
|
||||
final DifficultyLevel overallLevel;
|
||||
final List<SkillScore> skillScores;
|
||||
final List<UserAnswer> answers;
|
||||
final DateTime startTime;
|
||||
final DateTime endTime;
|
||||
final int duration; // 秒
|
||||
final String feedback;
|
||||
final Map<String, dynamic>? recommendations;
|
||||
|
||||
const TestResult({
|
||||
required this.id,
|
||||
required this.testId,
|
||||
required this.userId,
|
||||
required this.testType,
|
||||
required this.totalScore,
|
||||
required this.maxScore,
|
||||
required this.percentage,
|
||||
required this.overallLevel,
|
||||
required this.skillScores,
|
||||
required this.answers,
|
||||
required this.startTime,
|
||||
required this.endTime,
|
||||
required this.duration,
|
||||
required this.feedback,
|
||||
this.recommendations,
|
||||
});
|
||||
|
||||
factory TestResult.fromJson(Map<String, dynamic> json) {
|
||||
return TestResult(
|
||||
id: json['id'] as String,
|
||||
testId: json['testId'] as String,
|
||||
userId: json['userId'] as String,
|
||||
testType: TestType.values.firstWhere(
|
||||
(e) => e.name == json['testType'],
|
||||
orElse: () => TestType.standard,
|
||||
),
|
||||
totalScore: json['totalScore'] as int,
|
||||
maxScore: json['maxScore'] as int,
|
||||
percentage: (json['percentage'] as num).toDouble(),
|
||||
overallLevel: DifficultyLevel.values.firstWhere(
|
||||
(e) => e.name == json['overallLevel'],
|
||||
orElse: () => DifficultyLevel.intermediate,
|
||||
),
|
||||
skillScores: (json['skillScores'] as List<dynamic>)
|
||||
.map((e) => SkillScore.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
answers: (json['answers'] as List<dynamic>)
|
||||
.map((e) => UserAnswer.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
startTime: DateTime.parse(json['startTime'] as String),
|
||||
endTime: DateTime.parse(json['endTime'] as String),
|
||||
duration: json['duration'] as int,
|
||||
feedback: json['feedback'] as String,
|
||||
recommendations: json['recommendations'] as Map<String, dynamic>?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'testId': testId,
|
||||
'userId': userId,
|
||||
'testType': testType.name,
|
||||
'totalScore': totalScore,
|
||||
'maxScore': maxScore,
|
||||
'percentage': percentage,
|
||||
'overallLevel': overallLevel.name,
|
||||
'skillScores': skillScores.map((e) => e.toJson()).toList(),
|
||||
'answers': answers.map((e) => e.toJson()).toList(),
|
||||
'startTime': startTime.toIso8601String(),
|
||||
'endTime': endTime.toIso8601String(),
|
||||
'duration': duration,
|
||||
'feedback': feedback,
|
||||
'recommendations': recommendations,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试模板模型
|
||||
class TestTemplate {
|
||||
final String id;
|
||||
final String name;
|
||||
final String description;
|
||||
final TestType type;
|
||||
final int duration; // 分钟
|
||||
final int totalQuestions;
|
||||
final Map<SkillType, int> skillDistribution;
|
||||
final Map<DifficultyLevel, int> difficultyDistribution;
|
||||
final List<String> questionIds;
|
||||
final bool isActive;
|
||||
final DateTime createdAt;
|
||||
final DateTime updatedAt;
|
||||
|
||||
const TestTemplate({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.type,
|
||||
required this.duration,
|
||||
required this.totalQuestions,
|
||||
required this.skillDistribution,
|
||||
required this.difficultyDistribution,
|
||||
required this.questionIds,
|
||||
this.isActive = true,
|
||||
required this.createdAt,
|
||||
required this.updatedAt,
|
||||
});
|
||||
|
||||
factory TestTemplate.fromJson(Map<String, dynamic> json) {
|
||||
return TestTemplate(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
description: json['description'] as String,
|
||||
type: TestType.values.firstWhere(
|
||||
(e) => e.name == json['type'],
|
||||
orElse: () => TestType.standard,
|
||||
),
|
||||
duration: json['duration'] as int,
|
||||
totalQuestions: json['totalQuestions'] as int,
|
||||
skillDistribution: Map<SkillType, int>.fromEntries(
|
||||
(json['skillDistribution'] as Map<String, dynamic>).entries.map(
|
||||
(e) => MapEntry(
|
||||
SkillType.values.firstWhere((skill) => skill.name == e.key),
|
||||
e.value as int,
|
||||
),
|
||||
),
|
||||
),
|
||||
difficultyDistribution: Map<DifficultyLevel, int>.fromEntries(
|
||||
(json['difficultyDistribution'] as Map<String, dynamic>).entries.map(
|
||||
(e) => MapEntry(
|
||||
DifficultyLevel.values.firstWhere((level) => level.name == e.key),
|
||||
e.value as int,
|
||||
),
|
||||
),
|
||||
),
|
||||
questionIds: List<String>.from(json['questionIds']),
|
||||
isActive: json['isActive'] as bool? ?? true,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
updatedAt: DateTime.parse(json['updatedAt'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'type': type.name,
|
||||
'duration': duration,
|
||||
'totalQuestions': totalQuestions,
|
||||
'skillDistribution': skillDistribution.map(
|
||||
(key, value) => MapEntry(key.name, value),
|
||||
),
|
||||
'difficultyDistribution': difficultyDistribution.map(
|
||||
(key, value) => MapEntry(key.name, value),
|
||||
),
|
||||
'questionIds': questionIds,
|
||||
'isActive': isActive,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'updatedAt': updatedAt.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试会话模型
|
||||
class TestSession {
|
||||
final String id;
|
||||
final String templateId;
|
||||
final String userId;
|
||||
final TestStatus status;
|
||||
final List<TestQuestion> questions;
|
||||
final List<UserAnswer> answers;
|
||||
final int currentQuestionIndex;
|
||||
final DateTime startTime;
|
||||
final DateTime? endTime;
|
||||
final int timeRemaining; // 秒
|
||||
final Map<String, dynamic>? metadata;
|
||||
|
||||
const TestSession({
|
||||
required this.id,
|
||||
required this.templateId,
|
||||
required this.userId,
|
||||
required this.status,
|
||||
required this.questions,
|
||||
this.answers = const [],
|
||||
this.currentQuestionIndex = 0,
|
||||
required this.startTime,
|
||||
this.endTime,
|
||||
required this.timeRemaining,
|
||||
this.metadata,
|
||||
});
|
||||
|
||||
factory TestSession.fromJson(Map<String, dynamic> json) {
|
||||
return TestSession(
|
||||
id: json['id'] as String,
|
||||
templateId: json['templateId'] as String,
|
||||
userId: json['userId'] as String,
|
||||
status: TestStatus.values.firstWhere(
|
||||
(e) => e.name == json['status'],
|
||||
orElse: () => TestStatus.notStarted,
|
||||
),
|
||||
questions: (json['questions'] as List<dynamic>)
|
||||
.map((e) => TestQuestion.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
answers: (json['answers'] as List<dynamic>?)
|
||||
?.map((e) => UserAnswer.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ?? [],
|
||||
currentQuestionIndex: json['currentQuestionIndex'] as int? ?? 0,
|
||||
startTime: DateTime.parse(json['startTime'] as String),
|
||||
endTime: json['endTime'] != null
|
||||
? DateTime.parse(json['endTime'] as String)
|
||||
: null,
|
||||
timeRemaining: json['timeRemaining'] as int,
|
||||
metadata: json['metadata'] as Map<String, dynamic>?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'templateId': templateId,
|
||||
'userId': userId,
|
||||
'status': status.name,
|
||||
'questions': questions.map((e) => e.toJson()).toList(),
|
||||
'answers': answers.map((e) => e.toJson()).toList(),
|
||||
'currentQuestionIndex': currentQuestionIndex,
|
||||
'startTime': startTime.toIso8601String(),
|
||||
'endTime': endTime?.toIso8601String(),
|
||||
'timeRemaining': timeRemaining,
|
||||
'metadata': metadata,
|
||||
};
|
||||
}
|
||||
|
||||
TestSession copyWith({
|
||||
String? id,
|
||||
String? templateId,
|
||||
String? userId,
|
||||
TestStatus? status,
|
||||
List<TestQuestion>? questions,
|
||||
List<UserAnswer>? answers,
|
||||
int? currentQuestionIndex,
|
||||
DateTime? startTime,
|
||||
DateTime? endTime,
|
||||
int? timeRemaining,
|
||||
Map<String, dynamic>? metadata,
|
||||
}) {
|
||||
return TestSession(
|
||||
id: id ?? this.id,
|
||||
templateId: templateId ?? this.templateId,
|
||||
userId: userId ?? this.userId,
|
||||
status: status ?? this.status,
|
||||
questions: questions ?? this.questions,
|
||||
answers: answers ?? this.answers,
|
||||
currentQuestionIndex: currentQuestionIndex ?? this.currentQuestionIndex,
|
||||
startTime: startTime ?? this.startTime,
|
||||
endTime: endTime ?? this.endTime,
|
||||
timeRemaining: timeRemaining ?? this.timeRemaining,
|
||||
metadata: metadata ?? this.metadata,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user