Files
2025-11-17 14:09:17 +08:00

559 lines
16 KiB
Dart

/// 测试类型枚举
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,
);
}
}