init
This commit is contained in:
96
client/lib/features/writing/models/writing_record.dart
Normal file
96
client/lib/features/writing/models/writing_record.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
/// 写作完成记录模型
|
||||
class WritingRecord {
|
||||
final String id;
|
||||
final String taskId;
|
||||
final String taskTitle;
|
||||
final String taskDescription;
|
||||
final String content;
|
||||
final int wordCount;
|
||||
final int timeUsed; // 使用的时间(秒)
|
||||
final int score;
|
||||
final DateTime completedAt;
|
||||
final Map<String, dynamic>? feedback;
|
||||
|
||||
const WritingRecord({
|
||||
required this.id,
|
||||
required this.taskId,
|
||||
required this.taskTitle,
|
||||
required this.taskDescription,
|
||||
required this.content,
|
||||
required this.wordCount,
|
||||
required this.timeUsed,
|
||||
required this.score,
|
||||
required this.completedAt,
|
||||
this.feedback,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'taskId': taskId,
|
||||
'taskTitle': taskTitle,
|
||||
'taskDescription': taskDescription,
|
||||
'content': content,
|
||||
'wordCount': wordCount,
|
||||
'timeUsed': timeUsed,
|
||||
'score': score,
|
||||
'completedAt': completedAt.toIso8601String(),
|
||||
'feedback': feedback,
|
||||
};
|
||||
}
|
||||
|
||||
factory WritingRecord.fromJson(Map<String, dynamic> json) {
|
||||
return WritingRecord(
|
||||
id: json['id'],
|
||||
taskId: json['taskId'],
|
||||
taskTitle: json['taskTitle'],
|
||||
taskDescription: json['taskDescription'],
|
||||
content: json['content'],
|
||||
wordCount: json['wordCount'],
|
||||
timeUsed: json['timeUsed'],
|
||||
score: json['score'],
|
||||
completedAt: DateTime.parse(json['completedAt']),
|
||||
feedback: json['feedback'],
|
||||
);
|
||||
}
|
||||
|
||||
WritingRecord copyWith({
|
||||
String? id,
|
||||
String? taskId,
|
||||
String? taskTitle,
|
||||
String? taskDescription,
|
||||
String? content,
|
||||
int? wordCount,
|
||||
int? timeUsed,
|
||||
int? score,
|
||||
DateTime? completedAt,
|
||||
Map<String, dynamic>? feedback,
|
||||
}) {
|
||||
return WritingRecord(
|
||||
id: id ?? this.id,
|
||||
taskId: taskId ?? this.taskId,
|
||||
taskTitle: taskTitle ?? this.taskTitle,
|
||||
taskDescription: taskDescription ?? this.taskDescription,
|
||||
content: content ?? this.content,
|
||||
wordCount: wordCount ?? this.wordCount,
|
||||
timeUsed: timeUsed ?? this.timeUsed,
|
||||
score: score ?? this.score,
|
||||
completedAt: completedAt ?? this.completedAt,
|
||||
feedback: feedback ?? this.feedback,
|
||||
);
|
||||
}
|
||||
|
||||
String get formattedDate {
|
||||
return '${completedAt.year}-${completedAt.month.toString().padLeft(2, '0')}-${completedAt.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
String get formattedTime {
|
||||
final minutes = timeUsed ~/ 60;
|
||||
final seconds = timeUsed % 60;
|
||||
return '${minutes}分${seconds}秒';
|
||||
}
|
||||
|
||||
String get scoreText {
|
||||
return '${score}分';
|
||||
}
|
||||
}
|
||||
238
client/lib/features/writing/models/writing_stats.dart
Normal file
238
client/lib/features/writing/models/writing_stats.dart
Normal file
@@ -0,0 +1,238 @@
|
||||
class WritingStats {
|
||||
final String userId;
|
||||
final int totalTasks;
|
||||
final int completedTasks;
|
||||
final int totalWords;
|
||||
final int totalTimeSpent; // 秒
|
||||
final double averageScore;
|
||||
final Map<String, int> taskTypeStats;
|
||||
final Map<String, int> difficultyStats;
|
||||
final List<WritingProgressData> progressData;
|
||||
final WritingSkillAnalysis skillAnalysis;
|
||||
final DateTime lastUpdated;
|
||||
|
||||
const WritingStats({
|
||||
required this.userId,
|
||||
required this.totalTasks,
|
||||
required this.completedTasks,
|
||||
required this.totalWords,
|
||||
required this.totalTimeSpent,
|
||||
required this.averageScore,
|
||||
required this.taskTypeStats,
|
||||
required this.difficultyStats,
|
||||
required this.progressData,
|
||||
required this.skillAnalysis,
|
||||
required this.lastUpdated,
|
||||
});
|
||||
|
||||
double get completionRate => totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
|
||||
|
||||
double get averageWordsPerTask => completedTasks > 0 ? totalWords / completedTasks : 0;
|
||||
|
||||
double get averageTimePerTask => completedTasks > 0 ? totalTimeSpent / completedTasks : 0;
|
||||
|
||||
factory WritingStats.fromJson(Map<String, dynamic> json) {
|
||||
final userIdVal = json['userId'];
|
||||
final totalTasksVal = json['totalTasks'] ?? json['total_submissions'];
|
||||
final completedTasksVal = json['completedTasks'] ?? json['completed_submissions'];
|
||||
final totalWordsVal = json['totalWords'] ?? json['total_word_count'];
|
||||
final totalTimeSpentVal = json['totalTimeSpent'] ?? json['total_time_spent'];
|
||||
final averageScoreVal = json['averageScore'] ?? json['average_score'];
|
||||
final difficultyStatsVal = json['difficultyStats'] ?? json['difficulty_stats'] ?? {};
|
||||
final progressDataVal = json['progressData'] ?? [];
|
||||
final skillAnalysisVal = json['skillAnalysis'];
|
||||
final lastUpdatedVal = json['lastUpdated'];
|
||||
|
||||
final stats = WritingStats(
|
||||
userId: userIdVal?.toString() ?? '',
|
||||
totalTasks: _asInt(totalTasksVal),
|
||||
completedTasks: _asInt(completedTasksVal),
|
||||
totalWords: _asInt(totalWordsVal),
|
||||
totalTimeSpent: _asInt(totalTimeSpentVal),
|
||||
averageScore: _asDouble(averageScoreVal),
|
||||
taskTypeStats: Map<String, int>.from(json['taskTypeStats'] ?? {}),
|
||||
difficultyStats: Map<String, int>.from(difficultyStatsVal),
|
||||
progressData: (progressDataVal is List)
|
||||
? progressDataVal
|
||||
.map((e) => WritingProgressData.fromJson(Map<String, dynamic>.from(e)))
|
||||
.toList()
|
||||
: <WritingProgressData>[],
|
||||
skillAnalysis: skillAnalysisVal is Map<String, dynamic>
|
||||
? WritingSkillAnalysis.fromJson(skillAnalysisVal)
|
||||
: WritingSkillAnalysis(
|
||||
criteriaScores: {
|
||||
'grammar': _normalize01(_asDouble(json['average_grammar_score'])),
|
||||
'coherence': _normalize01(_asDouble(json['average_coherence_score'])),
|
||||
'vocab': _normalize01(_asDouble(json['average_vocab_score'])),
|
||||
},
|
||||
errorCounts: {},
|
||||
strengths: const [],
|
||||
weaknesses: const [],
|
||||
recommendations: const [],
|
||||
improvementRate: 0.0,
|
||||
lastAnalyzed: DateTime.now(),
|
||||
),
|
||||
lastUpdated: lastUpdatedVal is String
|
||||
? DateTime.parse(lastUpdatedVal)
|
||||
: DateTime.now(),
|
||||
);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'userId': userId,
|
||||
'totalTasks': totalTasks,
|
||||
'completedTasks': completedTasks,
|
||||
'totalWords': totalWords,
|
||||
'totalTimeSpent': totalTimeSpent,
|
||||
'averageScore': averageScore,
|
||||
'taskTypeStats': taskTypeStats,
|
||||
'difficultyStats': difficultyStats,
|
||||
'progressData': progressData.map((e) => e.toJson()).toList(),
|
||||
'skillAnalysis': skillAnalysis.toJson(),
|
||||
'lastUpdated': lastUpdated.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
WritingStats copyWith({
|
||||
String? userId,
|
||||
int? totalTasks,
|
||||
int? completedTasks,
|
||||
int? totalWords,
|
||||
int? totalTimeSpent,
|
||||
double? averageScore,
|
||||
Map<String, int>? taskTypeStats,
|
||||
Map<String, int>? difficultyStats,
|
||||
List<WritingProgressData>? progressData,
|
||||
WritingSkillAnalysis? skillAnalysis,
|
||||
DateTime? lastUpdated,
|
||||
}) {
|
||||
return WritingStats(
|
||||
userId: userId ?? this.userId,
|
||||
totalTasks: totalTasks ?? this.totalTasks,
|
||||
completedTasks: completedTasks ?? this.completedTasks,
|
||||
totalWords: totalWords ?? this.totalWords,
|
||||
totalTimeSpent: totalTimeSpent ?? this.totalTimeSpent,
|
||||
averageScore: averageScore ?? this.averageScore,
|
||||
taskTypeStats: taskTypeStats ?? this.taskTypeStats,
|
||||
difficultyStats: difficultyStats ?? this.difficultyStats,
|
||||
progressData: progressData ?? this.progressData,
|
||||
skillAnalysis: skillAnalysis ?? this.skillAnalysis,
|
||||
lastUpdated: lastUpdated ?? this.lastUpdated,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WritingProgressData {
|
||||
final DateTime date;
|
||||
final double score;
|
||||
final int wordCount;
|
||||
final int timeSpent;
|
||||
final String taskType;
|
||||
final String difficulty;
|
||||
|
||||
const WritingProgressData({
|
||||
required this.date,
|
||||
required this.score,
|
||||
required this.wordCount,
|
||||
required this.timeSpent,
|
||||
required this.taskType,
|
||||
required this.difficulty,
|
||||
});
|
||||
|
||||
factory WritingProgressData.fromJson(Map<String, dynamic> json) {
|
||||
return WritingProgressData(
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
score: (json['score'] as num).toDouble(),
|
||||
wordCount: json['wordCount'] as int,
|
||||
timeSpent: json['timeSpent'] as int,
|
||||
taskType: json['taskType'] as String,
|
||||
difficulty: json['difficulty'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'date': date.toIso8601String(),
|
||||
'score': score,
|
||||
'wordCount': wordCount,
|
||||
'timeSpent': timeSpent,
|
||||
'taskType': taskType,
|
||||
'difficulty': difficulty,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class WritingSkillAnalysis {
|
||||
final Map<String, double> criteriaScores;
|
||||
final Map<String, int> errorCounts;
|
||||
final List<String> strengths;
|
||||
final List<String> weaknesses;
|
||||
final List<String> recommendations;
|
||||
final double improvementRate;
|
||||
final DateTime lastAnalyzed;
|
||||
|
||||
const WritingSkillAnalysis({
|
||||
required this.criteriaScores,
|
||||
required this.errorCounts,
|
||||
required this.strengths,
|
||||
required this.weaknesses,
|
||||
required this.recommendations,
|
||||
required this.improvementRate,
|
||||
required this.lastAnalyzed,
|
||||
});
|
||||
|
||||
factory WritingSkillAnalysis.fromJson(Map<String, dynamic> json) {
|
||||
return WritingSkillAnalysis(
|
||||
criteriaScores: Map<String, double>.from(
|
||||
(json['criteriaScores'] as Map<String, dynamic>).map(
|
||||
(k, v) => MapEntry(k, (v as num).toDouble()),
|
||||
),
|
||||
),
|
||||
errorCounts: Map<String, int>.from(json['errorCounts'] ?? {}),
|
||||
strengths: List<String>.from(json['strengths'] ?? []),
|
||||
weaknesses: List<String>.from(json['weaknesses'] ?? []),
|
||||
recommendations: List<String>.from(json['recommendations'] ?? []),
|
||||
improvementRate: (json['improvementRate'] as num).toDouble(),
|
||||
lastAnalyzed: DateTime.parse(json['lastAnalyzed'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'criteriaScores': criteriaScores,
|
||||
'errorCounts': errorCounts,
|
||||
'strengths': strengths,
|
||||
'weaknesses': weaknesses,
|
||||
'recommendations': recommendations,
|
||||
'improvementRate': improvementRate,
|
||||
'lastAnalyzed': lastAnalyzed.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
int _asInt(dynamic v) {
|
||||
if (v == null) return 0;
|
||||
if (v is int) return v;
|
||||
if (v is num) return v.toInt();
|
||||
if (v is String) {
|
||||
final parsed = double.tryParse(v);
|
||||
return parsed?.toInt() ?? int.tryParse(v) ?? 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
double _asDouble(dynamic v) {
|
||||
if (v == null) return 0.0;
|
||||
if (v is double) return v;
|
||||
if (v is num) return v.toDouble();
|
||||
if (v is String) return double.tryParse(v) ?? 0.0;
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
double _normalize01(double v) {
|
||||
if (v <= 1.0) return v;
|
||||
return v / 100.0;
|
||||
}
|
||||
408
client/lib/features/writing/models/writing_submission.dart
Normal file
408
client/lib/features/writing/models/writing_submission.dart
Normal file
@@ -0,0 +1,408 @@
|
||||
import 'writing_task.dart';
|
||||
|
||||
class WritingSubmission {
|
||||
final String id;
|
||||
final String taskId;
|
||||
final String userId;
|
||||
final String content;
|
||||
final int wordCount;
|
||||
final int timeSpent; // 秒
|
||||
final WritingStatus status;
|
||||
final DateTime submittedAt;
|
||||
final WritingFeedback? feedback;
|
||||
final WritingScore? score;
|
||||
|
||||
const WritingSubmission({
|
||||
required this.id,
|
||||
required this.taskId,
|
||||
required this.userId,
|
||||
required this.content,
|
||||
required this.wordCount,
|
||||
required this.timeSpent,
|
||||
required this.status,
|
||||
required this.submittedAt,
|
||||
this.feedback,
|
||||
this.score,
|
||||
});
|
||||
|
||||
factory WritingSubmission.fromJson(Map<String, dynamic> json) {
|
||||
return WritingSubmission(
|
||||
id: json['id'] as String,
|
||||
taskId: json['taskId'] as String,
|
||||
userId: json['userId'] as String,
|
||||
content: json['content'] as String,
|
||||
wordCount: json['wordCount'] as int,
|
||||
timeSpent: json['timeSpent'] as int,
|
||||
status: WritingStatus.values.firstWhere(
|
||||
(e) => e.name == json['status'],
|
||||
orElse: () => WritingStatus.draft,
|
||||
),
|
||||
submittedAt: DateTime.parse(json['submittedAt'] as String),
|
||||
feedback: json['feedback'] != null
|
||||
? WritingFeedback.fromJson(json['feedback'] as Map<String, dynamic>)
|
||||
: null,
|
||||
score: json['score'] != null
|
||||
? WritingScore.fromJson(json['score'] as Map<String, dynamic>)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'taskId': taskId,
|
||||
'userId': userId,
|
||||
'content': content,
|
||||
'wordCount': wordCount,
|
||||
'timeSpent': timeSpent,
|
||||
'status': status.name,
|
||||
'submittedAt': submittedAt.toIso8601String(),
|
||||
'feedback': feedback?.toJson(),
|
||||
'score': score?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
WritingSubmission copyWith({
|
||||
String? id,
|
||||
String? taskId,
|
||||
String? userId,
|
||||
String? content,
|
||||
int? wordCount,
|
||||
int? timeSpent,
|
||||
WritingStatus? status,
|
||||
DateTime? submittedAt,
|
||||
WritingFeedback? feedback,
|
||||
WritingScore? score,
|
||||
}) {
|
||||
return WritingSubmission(
|
||||
id: id ?? this.id,
|
||||
taskId: taskId ?? this.taskId,
|
||||
userId: userId ?? this.userId,
|
||||
content: content ?? this.content,
|
||||
wordCount: wordCount ?? this.wordCount,
|
||||
timeSpent: timeSpent ?? this.timeSpent,
|
||||
status: status ?? this.status,
|
||||
submittedAt: submittedAt ?? this.submittedAt,
|
||||
feedback: feedback ?? this.feedback,
|
||||
score: score ?? this.score,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class WritingFeedback {
|
||||
final String id;
|
||||
final String submissionId;
|
||||
final String overallComment;
|
||||
final List<WritingCriteriaFeedback> criteriaFeedbacks;
|
||||
final List<WritingError> errors;
|
||||
final List<WritingSuggestion> suggestions;
|
||||
final DateTime createdAt;
|
||||
|
||||
const WritingFeedback({
|
||||
required this.id,
|
||||
required this.submissionId,
|
||||
required this.overallComment,
|
||||
required this.criteriaFeedbacks,
|
||||
required this.errors,
|
||||
required this.suggestions,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
factory WritingFeedback.fromJson(Map<String, dynamic> json) {
|
||||
return WritingFeedback(
|
||||
id: json['id'] as String,
|
||||
submissionId: json['submissionId'] as String,
|
||||
overallComment: json['overallComment'] as String,
|
||||
criteriaFeedbacks: (json['criteriaFeedbacks'] as List<dynamic>)
|
||||
.map((e) => WritingCriteriaFeedback.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
errors: (json['errors'] as List<dynamic>)
|
||||
.map((e) => WritingError.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
suggestions: (json['suggestions'] as List<dynamic>)
|
||||
.map((e) => WritingSuggestion.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'submissionId': submissionId,
|
||||
'overallComment': overallComment,
|
||||
'criteriaFeedbacks': criteriaFeedbacks.map((e) => e.toJson()).toList(),
|
||||
'errors': errors.map((e) => e.toJson()).toList(),
|
||||
'suggestions': suggestions.map((e) => e.toJson()).toList(),
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class WritingScore {
|
||||
final String id;
|
||||
final String submissionId;
|
||||
final double totalScore;
|
||||
final double maxScore;
|
||||
final Map<WritingCriteria, double> criteriaScores;
|
||||
final String grade;
|
||||
final DateTime createdAt;
|
||||
|
||||
const WritingScore({
|
||||
required this.id,
|
||||
required this.submissionId,
|
||||
required this.totalScore,
|
||||
required this.maxScore,
|
||||
required this.criteriaScores,
|
||||
required this.grade,
|
||||
required this.createdAt,
|
||||
});
|
||||
|
||||
double get percentage => (totalScore / maxScore) * 100;
|
||||
|
||||
factory WritingScore.fromJson(Map<String, dynamic> json) {
|
||||
return WritingScore(
|
||||
id: json['id'] as String,
|
||||
submissionId: json['submissionId'] as String,
|
||||
totalScore: (json['totalScore'] as num).toDouble(),
|
||||
maxScore: (json['maxScore'] as num).toDouble(),
|
||||
criteriaScores: Map<WritingCriteria, double>.fromEntries(
|
||||
(json['criteriaScores'] as Map<String, dynamic>).entries.map(
|
||||
(e) => MapEntry(
|
||||
WritingCriteria.values.firstWhere((c) => c.name == e.key),
|
||||
(e.value as num).toDouble(),
|
||||
),
|
||||
),
|
||||
),
|
||||
grade: json['grade'] as String,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'submissionId': submissionId,
|
||||
'totalScore': totalScore,
|
||||
'maxScore': maxScore,
|
||||
'criteriaScores': Map<String, dynamic>.fromEntries(
|
||||
criteriaScores.entries.map(
|
||||
(e) => MapEntry(e.key.name, e.value),
|
||||
),
|
||||
),
|
||||
'grade': grade,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class WritingCriteriaFeedback {
|
||||
final WritingCriteria criteria;
|
||||
final double score;
|
||||
final double maxScore;
|
||||
final String comment;
|
||||
final List<String> strengths;
|
||||
final List<String> improvements;
|
||||
|
||||
const WritingCriteriaFeedback({
|
||||
required this.criteria,
|
||||
required this.score,
|
||||
required this.maxScore,
|
||||
required this.comment,
|
||||
required this.strengths,
|
||||
required this.improvements,
|
||||
});
|
||||
|
||||
factory WritingCriteriaFeedback.fromJson(Map<String, dynamic> json) {
|
||||
return WritingCriteriaFeedback(
|
||||
criteria: WritingCriteria.values.firstWhere(
|
||||
(e) => e.name == json['criteria'],
|
||||
),
|
||||
score: (json['score'] as num).toDouble(),
|
||||
maxScore: (json['maxScore'] as num).toDouble(),
|
||||
comment: json['comment'] as String,
|
||||
strengths: List<String>.from(json['strengths'] ?? []),
|
||||
improvements: List<String>.from(json['improvements'] ?? []),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'criteria': criteria.name,
|
||||
'score': score,
|
||||
'maxScore': maxScore,
|
||||
'comment': comment,
|
||||
'strengths': strengths,
|
||||
'improvements': improvements,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class WritingError {
|
||||
final WritingErrorType type;
|
||||
final String description;
|
||||
final String originalText;
|
||||
final String? suggestedText;
|
||||
final int startPosition;
|
||||
final int endPosition;
|
||||
final String explanation;
|
||||
|
||||
const WritingError({
|
||||
required this.type,
|
||||
required this.description,
|
||||
required this.originalText,
|
||||
this.suggestedText,
|
||||
required this.startPosition,
|
||||
required this.endPosition,
|
||||
required this.explanation,
|
||||
});
|
||||
|
||||
factory WritingError.fromJson(Map<String, dynamic> json) {
|
||||
return WritingError(
|
||||
type: WritingErrorType.values.firstWhere(
|
||||
(e) => e.name == json['type'],
|
||||
),
|
||||
description: json['description'] as String,
|
||||
originalText: json['originalText'] as String,
|
||||
suggestedText: json['suggestedText'] as String?,
|
||||
startPosition: json['startPosition'] as int,
|
||||
endPosition: json['endPosition'] as int,
|
||||
explanation: json['explanation'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'type': type.name,
|
||||
'description': description,
|
||||
'originalText': originalText,
|
||||
'suggestedText': suggestedText,
|
||||
'startPosition': startPosition,
|
||||
'endPosition': endPosition,
|
||||
'explanation': explanation,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class WritingSuggestion {
|
||||
final WritingSuggestionType type;
|
||||
final String title;
|
||||
final String description;
|
||||
final String? example;
|
||||
final int? position;
|
||||
|
||||
const WritingSuggestion({
|
||||
required this.type,
|
||||
required this.title,
|
||||
required this.description,
|
||||
this.example,
|
||||
this.position,
|
||||
});
|
||||
|
||||
factory WritingSuggestion.fromJson(Map<String, dynamic> json) {
|
||||
return WritingSuggestion(
|
||||
type: WritingSuggestionType.values.firstWhere(
|
||||
(e) => e.name == json['type'],
|
||||
),
|
||||
title: json['title'] as String,
|
||||
description: json['description'] as String,
|
||||
example: json['example'] as String?,
|
||||
position: json['position'] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'type': type.name,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'example': example,
|
||||
'position': position,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
enum WritingStatus {
|
||||
draft,
|
||||
submitted,
|
||||
grading,
|
||||
graded,
|
||||
revised,
|
||||
}
|
||||
|
||||
enum WritingCriteria {
|
||||
content,
|
||||
organization,
|
||||
vocabulary,
|
||||
grammar,
|
||||
mechanics,
|
||||
}
|
||||
|
||||
enum WritingErrorType {
|
||||
grammar,
|
||||
spelling,
|
||||
punctuation,
|
||||
vocabulary,
|
||||
structure,
|
||||
style,
|
||||
}
|
||||
|
||||
enum WritingSuggestionType {
|
||||
improvement,
|
||||
enhancement,
|
||||
alternative,
|
||||
clarification,
|
||||
}
|
||||
|
||||
extension WritingStatusExtension on WritingStatus {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case WritingStatus.draft:
|
||||
return '草稿';
|
||||
case WritingStatus.submitted:
|
||||
return '已提交';
|
||||
case WritingStatus.grading:
|
||||
return '评分中';
|
||||
case WritingStatus.graded:
|
||||
return '已评分';
|
||||
case WritingStatus.revised:
|
||||
return '已修改';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WritingCriteriaExtension on WritingCriteria {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case WritingCriteria.content:
|
||||
return '内容';
|
||||
case WritingCriteria.organization:
|
||||
return '结构';
|
||||
case WritingCriteria.vocabulary:
|
||||
return '词汇';
|
||||
case WritingCriteria.grammar:
|
||||
return '语法';
|
||||
case WritingCriteria.mechanics:
|
||||
return '拼写标点';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WritingErrorTypeExtension on WritingErrorType {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case WritingErrorType.grammar:
|
||||
return '语法错误';
|
||||
case WritingErrorType.spelling:
|
||||
return '拼写错误';
|
||||
case WritingErrorType.punctuation:
|
||||
return '标点错误';
|
||||
case WritingErrorType.vocabulary:
|
||||
return '词汇错误';
|
||||
case WritingErrorType.structure:
|
||||
return '结构错误';
|
||||
case WritingErrorType.style:
|
||||
return '风格问题';
|
||||
}
|
||||
}
|
||||
}
|
||||
267
client/lib/features/writing/models/writing_task.dart
Normal file
267
client/lib/features/writing/models/writing_task.dart
Normal file
@@ -0,0 +1,267 @@
|
||||
class WritingTask {
|
||||
final String id;
|
||||
final String title;
|
||||
final String description;
|
||||
final WritingType type;
|
||||
final WritingDifficulty difficulty;
|
||||
final int timeLimit; // 分钟
|
||||
final int wordLimit;
|
||||
final List<String> keywords;
|
||||
final List<String> requirements;
|
||||
final String? prompt;
|
||||
final String? imageUrl;
|
||||
final ExamType? examType; // 考试类型
|
||||
final DateTime createdAt;
|
||||
final DateTime? updatedAt;
|
||||
|
||||
const WritingTask({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.type,
|
||||
required this.difficulty,
|
||||
required this.timeLimit,
|
||||
required this.wordLimit,
|
||||
required this.keywords,
|
||||
required this.requirements,
|
||||
this.prompt,
|
||||
this.imageUrl,
|
||||
this.examType,
|
||||
required this.createdAt,
|
||||
this.updatedAt,
|
||||
});
|
||||
|
||||
factory WritingTask.fromJson(Map<String, dynamic> json) {
|
||||
final idVal = json['id'] ?? json['prompt_id'];
|
||||
final typeVal = json['type'];
|
||||
final difficultyVal = json['difficulty'];
|
||||
final timeLimitVal = json['timeLimit'] ?? json['time_limit'];
|
||||
final wordLimitVal = json['wordLimit'] ?? json['word_limit'];
|
||||
final examTypeVal = json['examType'] ?? json['exam_type'];
|
||||
final createdAtVal = json['createdAt'] ?? json['created_at'];
|
||||
final updatedAtVal = json['updatedAt'] ?? json['updated_at'];
|
||||
|
||||
return WritingTask(
|
||||
id: (idVal ?? '').toString(),
|
||||
title: (json['title'] ?? '').toString(),
|
||||
description: (json['description'] ?? '').toString(),
|
||||
type: typeVal is String
|
||||
? WritingType.values.firstWhere(
|
||||
(e) => e.name == typeVal,
|
||||
orElse: () => WritingType.essay,
|
||||
)
|
||||
: WritingType.essay,
|
||||
difficulty: difficultyVal is String
|
||||
? WritingDifficulty.values.firstWhere(
|
||||
(e) => e.name == difficultyVal,
|
||||
orElse: () => WritingDifficulty.intermediate,
|
||||
)
|
||||
: WritingDifficulty.intermediate,
|
||||
timeLimit: _asInt(timeLimitVal),
|
||||
wordLimit: _asInt(wordLimitVal),
|
||||
keywords: List<String>.from(json['keywords'] ?? []),
|
||||
requirements: List<String>.from(json['requirements'] ?? []),
|
||||
prompt: json['prompt'] as String?,
|
||||
imageUrl: json['imageUrl'] as String?,
|
||||
examType: examTypeVal is String
|
||||
? ExamType.values.firstWhere(
|
||||
(e) => e.name == examTypeVal,
|
||||
orElse: () => ExamType.cet,
|
||||
)
|
||||
: null,
|
||||
createdAt: _parseDate(createdAtVal),
|
||||
updatedAt: updatedAtVal != null ? _parseDate(updatedAtVal) : null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'description': description,
|
||||
'type': type.name,
|
||||
'difficulty': difficulty.name,
|
||||
'timeLimit': timeLimit,
|
||||
'wordLimit': wordLimit,
|
||||
'keywords': keywords,
|
||||
'requirements': requirements,
|
||||
'prompt': prompt,
|
||||
'imageUrl': imageUrl,
|
||||
'examType': examType?.name,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'updatedAt': updatedAt?.toIso8601String(),
|
||||
};
|
||||
}
|
||||
|
||||
WritingTask copyWith({
|
||||
String? id,
|
||||
String? title,
|
||||
String? description,
|
||||
WritingType? type,
|
||||
WritingDifficulty? difficulty,
|
||||
int? timeLimit,
|
||||
int? wordLimit,
|
||||
List<String>? keywords,
|
||||
List<String>? requirements,
|
||||
String? prompt,
|
||||
String? imageUrl,
|
||||
ExamType? examType,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
}) {
|
||||
return WritingTask(
|
||||
id: id ?? this.id,
|
||||
title: title ?? this.title,
|
||||
description: description ?? this.description,
|
||||
type: type ?? this.type,
|
||||
difficulty: difficulty ?? this.difficulty,
|
||||
timeLimit: timeLimit ?? this.timeLimit,
|
||||
wordLimit: wordLimit ?? this.wordLimit,
|
||||
keywords: keywords ?? this.keywords,
|
||||
requirements: requirements ?? this.requirements,
|
||||
prompt: prompt ?? this.prompt,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
examType: examType ?? this.examType,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum WritingType {
|
||||
essay,
|
||||
letter,
|
||||
email,
|
||||
report,
|
||||
story,
|
||||
review,
|
||||
article,
|
||||
diary,
|
||||
description,
|
||||
argument,
|
||||
}
|
||||
|
||||
enum WritingDifficulty {
|
||||
beginner,
|
||||
elementary,
|
||||
intermediate,
|
||||
upperIntermediate,
|
||||
advanced,
|
||||
}
|
||||
|
||||
extension WritingTypeExtension on WritingType {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case WritingType.essay:
|
||||
return '议论文';
|
||||
case WritingType.letter:
|
||||
return '书信';
|
||||
case WritingType.email:
|
||||
return '邮件';
|
||||
case WritingType.report:
|
||||
return '报告';
|
||||
case WritingType.story:
|
||||
return '故事';
|
||||
case WritingType.review:
|
||||
return '评论';
|
||||
case WritingType.article:
|
||||
return '文章';
|
||||
case WritingType.diary:
|
||||
return '日记';
|
||||
case WritingType.description:
|
||||
return '描述文';
|
||||
case WritingType.argument:
|
||||
return '辩论文';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension WritingDifficultyExtension on WritingDifficulty {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case WritingDifficulty.beginner:
|
||||
return '初级';
|
||||
case WritingDifficulty.elementary:
|
||||
return '基础';
|
||||
case WritingDifficulty.intermediate:
|
||||
return '中级';
|
||||
case WritingDifficulty.upperIntermediate:
|
||||
return '中高级';
|
||||
case WritingDifficulty.advanced:
|
||||
return '高级';
|
||||
}
|
||||
}
|
||||
|
||||
int get level {
|
||||
switch (this) {
|
||||
case WritingDifficulty.beginner:
|
||||
return 1;
|
||||
case WritingDifficulty.elementary:
|
||||
return 2;
|
||||
case WritingDifficulty.intermediate:
|
||||
return 3;
|
||||
case WritingDifficulty.upperIntermediate:
|
||||
return 4;
|
||||
case WritingDifficulty.advanced:
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ExamType {
|
||||
cet, // 四六级
|
||||
kaoyan, // 考研
|
||||
toefl, // 托福
|
||||
ielts, // 雅思
|
||||
}
|
||||
|
||||
extension ExamTypeExtension on ExamType {
|
||||
String get displayName {
|
||||
switch (this) {
|
||||
case ExamType.cet:
|
||||
return '四六级';
|
||||
case ExamType.kaoyan:
|
||||
return '考研';
|
||||
case ExamType.toefl:
|
||||
return '托福';
|
||||
case ExamType.ielts:
|
||||
return '雅思';
|
||||
}
|
||||
}
|
||||
|
||||
String get description {
|
||||
switch (this) {
|
||||
case ExamType.cet:
|
||||
return '大学英语四六级考试写作';
|
||||
case ExamType.kaoyan:
|
||||
return '研究生入学考试英语写作';
|
||||
case ExamType.toefl:
|
||||
return 'TOEFL托福考试写作';
|
||||
case ExamType.ielts:
|
||||
return 'IELTS雅思考试写作';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int _asInt(dynamic v) {
|
||||
if (v == null) return 0;
|
||||
if (v is int) return v;
|
||||
if (v is num) return v.toInt();
|
||||
if (v is String) {
|
||||
final parsed = double.tryParse(v);
|
||||
return parsed?.toInt() ?? int.tryParse(v) ?? 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
DateTime _parseDate(dynamic v) {
|
||||
if (v is String) {
|
||||
try {
|
||||
return DateTime.parse(v);
|
||||
} catch (_) {}
|
||||
}
|
||||
if (v is int) {
|
||||
return DateTime.fromMillisecondsSinceEpoch(v);
|
||||
}
|
||||
return DateTime.now();
|
||||
}
|
||||
Reference in New Issue
Block a user