😋 初始化仓库

This commit is contained in:
2025-11-13 09:14:49 +08:00
commit 347d264437
133 changed files with 11214 additions and 0 deletions

2
lib/models/.gitkeep Normal file
View File

@@ -0,0 +1,2 @@
# Models directory
# This directory contains data models for the application

View File

@@ -0,0 +1,111 @@
import 'course.dart';
/// Concurrent evaluation task status
enum TaskStatus {
waiting, // 等待开始
preparing, // 准备评教(访问页面、解析问卷)
countdown, // 倒计时等待
submitting, // 提交中
verifying, // 验证中
completed, // 完成
failed, // 失败
}
/// Concurrent evaluation task
class ConcurrentTask {
final int taskId;
final Course course;
TaskStatus status;
String? statusMessage;
int countdownRemaining;
int countdownTotal;
String? errorMessage;
DateTime? startTime;
DateTime? endTime;
ConcurrentTask({
required this.taskId,
required this.course,
this.status = TaskStatus.waiting,
this.statusMessage,
this.countdownRemaining = 0,
this.countdownTotal = 0,
this.errorMessage,
this.startTime,
this.endTime,
});
/// Get progress (0.0 to 1.0)
double get progress {
switch (status) {
case TaskStatus.waiting:
return 0.0;
case TaskStatus.preparing:
return 0.1;
case TaskStatus.countdown:
if (countdownTotal > 0) {
return 0.1 +
0.7 * (countdownTotal - countdownRemaining) / countdownTotal;
}
return 0.1;
case TaskStatus.submitting:
return 0.85;
case TaskStatus.verifying:
return 0.95;
case TaskStatus.completed:
case TaskStatus.failed:
return 1.0;
}
}
/// Get status display text
String get statusText {
if (statusMessage != null) return statusMessage!;
switch (status) {
case TaskStatus.waiting:
return '等待开始';
case TaskStatus.preparing:
return '准备评教';
case TaskStatus.countdown:
return '等待提交 (${countdownRemaining}s)';
case TaskStatus.submitting:
return '提交中';
case TaskStatus.verifying:
return '验证中';
case TaskStatus.completed:
return '完成';
case TaskStatus.failed:
return '失败';
}
}
/// Check if task is finished
bool get isFinished =>
status == TaskStatus.completed || status == TaskStatus.failed;
/// Check if task is successful
bool get isSuccess => status == TaskStatus.completed;
ConcurrentTask copyWith({
TaskStatus? status,
String? statusMessage,
int? countdownRemaining,
int? countdownTotal,
String? errorMessage,
DateTime? startTime,
DateTime? endTime,
}) {
return ConcurrentTask(
taskId: taskId,
course: course,
status: status ?? this.status,
statusMessage: statusMessage ?? this.statusMessage,
countdownRemaining: countdownRemaining ?? this.countdownRemaining,
countdownTotal: countdownTotal ?? this.countdownTotal,
errorMessage: errorMessage ?? this.errorMessage,
startTime: startTime ?? this.startTime,
endTime: endTime ?? this.endTime,
);
}
}

101
lib/models/course.dart Normal file
View File

@@ -0,0 +1,101 @@
/// Course data model representing a course that needs evaluation
class Course {
final String id;
final String name;
final String teacher;
final String evaluatedPeople;
final String evaluatedPeopleNumber;
final String coureSequenceNumber;
final String evaluationContentNumber;
final String questionnaireCode;
final String questionnaireName;
final bool isEvaluated;
Course({
required this.id,
required this.name,
required this.teacher,
required this.evaluatedPeople,
required this.evaluatedPeopleNumber,
required this.coureSequenceNumber,
required this.evaluationContentNumber,
required this.questionnaireCode,
required this.questionnaireName,
this.isEvaluated = false,
});
/// Create Course from JSON
factory Course.fromJson(Map<String, dynamic> json) {
return Course(
id: json['id'] as String? ?? '',
name: json['name'] as String? ?? '',
teacher: json['teacher'] as String? ?? '',
evaluatedPeople: json['evaluatedPeople'] as String? ?? '',
evaluatedPeopleNumber: json['evaluatedPeopleNumber'] as String? ?? '',
coureSequenceNumber: json['coureSequenceNumber'] as String? ?? '',
evaluationContentNumber: json['evaluationContentNumber'] as String? ?? '',
questionnaireCode: json['questionnaireCode'] as String? ?? '',
questionnaireName: json['questionnaireName'] as String? ?? '',
isEvaluated: json['isEvaluated'] as bool? ?? false,
);
}
/// Convert Course to JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'name': name,
'teacher': teacher,
'evaluatedPeople': evaluatedPeople,
'evaluatedPeopleNumber': evaluatedPeopleNumber,
'coureSequenceNumber': coureSequenceNumber,
'evaluationContentNumber': evaluationContentNumber,
'questionnaireCode': questionnaireCode,
'questionnaireName': questionnaireName,
'isEvaluated': isEvaluated,
};
}
/// Create a copy of Course with updated fields
Course copyWith({
String? id,
String? name,
String? teacher,
String? evaluatedPeople,
String? evaluatedPeopleNumber,
String? coureSequenceNumber,
String? evaluationContentNumber,
String? questionnaireCode,
String? questionnaireName,
bool? isEvaluated,
}) {
return Course(
id: id ?? this.id,
name: name ?? this.name,
teacher: teacher ?? this.teacher,
evaluatedPeople: evaluatedPeople ?? this.evaluatedPeople,
evaluatedPeopleNumber:
evaluatedPeopleNumber ?? this.evaluatedPeopleNumber,
coureSequenceNumber: coureSequenceNumber ?? this.coureSequenceNumber,
evaluationContentNumber:
evaluationContentNumber ?? this.evaluationContentNumber,
questionnaireCode: questionnaireCode ?? this.questionnaireCode,
questionnaireName: questionnaireName ?? this.questionnaireName,
isEvaluated: isEvaluated ?? this.isEvaluated,
);
}
@override
String toString() {
return 'Course(id: $id, name: $name, teacher: $teacher, isEvaluated: $isEvaluated)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Course && other.id == id;
}
@override
int get hashCode => id.hashCode;
}

View File

@@ -0,0 +1,54 @@
import 'course.dart';
/// Evaluation history record
class EvaluationHistory {
final String id;
final Course course;
final DateTime timestamp;
final bool success;
final String? errorMessage;
EvaluationHistory({
required this.id,
required this.course,
required this.timestamp,
required this.success,
this.errorMessage,
});
factory EvaluationHistory.fromJson(Map<String, dynamic> json) {
return EvaluationHistory(
id: json['id'] as String? ?? '',
course: Course.fromJson(json['course'] as Map<String, dynamic>),
timestamp: json['timestamp'] != null
? DateTime.parse(json['timestamp'] as String)
: DateTime.now(),
success: json['success'] as bool? ?? false,
errorMessage: json['errorMessage'] as String?,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'course': course.toJson(),
'timestamp': timestamp.toIso8601String(),
'success': success,
'errorMessage': errorMessage,
};
}
@override
String toString() {
return 'EvaluationHistory(id: $id, course: ${course.name}, success: $success, timestamp: $timestamp)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is EvaluationHistory && other.id == id;
}
@override
int get hashCode => id.hashCode;
}

View File

@@ -0,0 +1,89 @@
import 'course.dart';
/// Result of a single course evaluation
class EvaluationResult {
final Course course;
final bool success;
final String? errorMessage;
final DateTime timestamp;
EvaluationResult({
required this.course,
required this.success,
this.errorMessage,
DateTime? timestamp,
}) : timestamp = timestamp ?? DateTime.now();
factory EvaluationResult.fromJson(Map<String, dynamic> json) {
return EvaluationResult(
course: Course.fromJson(json['course'] as Map<String, dynamic>),
success: json['success'] as bool? ?? false,
errorMessage: json['errorMessage'] as String?,
timestamp: json['timestamp'] != null
? DateTime.parse(json['timestamp'] as String)
: DateTime.now(),
);
}
Map<String, dynamic> toJson() {
return {
'course': course.toJson(),
'success': success,
'errorMessage': errorMessage,
'timestamp': timestamp.toIso8601String(),
};
}
@override
String toString() {
return 'EvaluationResult(course: ${course.name}, success: $success, errorMessage: $errorMessage)';
}
}
/// Result of batch evaluation
class BatchEvaluationResult {
final int total;
final int success;
final int failed;
final List<EvaluationResult> results;
final Duration duration;
BatchEvaluationResult({
required this.total,
required this.success,
required this.failed,
required this.results,
required this.duration,
});
factory BatchEvaluationResult.fromJson(Map<String, dynamic> json) {
return BatchEvaluationResult(
total: json['total'] as int? ?? 0,
success: json['success'] as int? ?? 0,
failed: json['failed'] as int? ?? 0,
results:
(json['results'] as List<dynamic>?)
?.map((e) => EvaluationResult.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
duration: Duration(milliseconds: json['durationMs'] as int? ?? 0),
);
}
Map<String, dynamic> toJson() {
return {
'total': total,
'success': success,
'failed': failed,
'results': results.map((e) => e.toJson()).toList(),
'durationMs': duration.inMilliseconds,
};
}
double get successRate => total > 0 ? success / total : 0.0;
@override
String toString() {
return 'BatchEvaluationResult(total: $total, success: $success, failed: $failed, duration: ${duration.inSeconds}s)';
}
}

View File

@@ -0,0 +1,85 @@
/// EC登录状态
class ECLoginStatus {
final bool success;
final bool failNotFoundTwfid;
final bool failNotFoundRsaKey;
final bool failNotFoundRsaExp;
final bool failNotFoundCsrfCode;
final bool failInvalidCredentials;
final bool failMaybeAttacked;
final bool failNetworkError;
final bool failUnknownError;
ECLoginStatus({
this.success = false,
this.failNotFoundTwfid = false,
this.failNotFoundRsaKey = false,
this.failNotFoundRsaExp = false,
this.failNotFoundCsrfCode = false,
this.failInvalidCredentials = false,
this.failMaybeAttacked = false,
this.failNetworkError = false,
this.failUnknownError = false,
});
bool get isSuccess => success;
bool get isFailed => !success;
String get errorMessage {
if (failNotFoundTwfid) return '未找到TwfID';
if (failNotFoundRsaKey) return '未找到RSA密钥';
if (failNotFoundRsaExp) return '未找到RSA指数';
if (failNotFoundCsrfCode) return '未找到CSRF代码';
if (failInvalidCredentials) return '用户名或密码错误';
if (failMaybeAttacked) return '可能受到攻击或需要验证码';
if (failNetworkError) return '网络连接错误';
if (failUnknownError) return '未知错误';
return '';
}
}
/// UAAP登录状态
class UAAPLoginStatus {
final bool success;
final bool failNotFoundLt;
final bool failNotFoundExecution;
final bool failInvalidCredentials;
final bool failNetworkError;
final bool failUnknownError;
UAAPLoginStatus({
this.success = false,
this.failNotFoundLt = false,
this.failNotFoundExecution = false,
this.failInvalidCredentials = false,
this.failNetworkError = false,
this.failUnknownError = false,
});
bool get isSuccess => success;
bool get isFailed => !success;
String get errorMessage {
if (failNotFoundLt) return '未找到lt参数';
if (failNotFoundExecution) return '未找到execution参数';
if (failInvalidCredentials) return '用户名或密码错误';
if (failNetworkError) return '网络连接错误';
if (failUnknownError) return '未知错误';
return '';
}
}
/// EC检查状态
class ECCheckStatus {
final bool loggedIn;
final bool failNetworkError;
final bool failUnknownError;
ECCheckStatus({
this.loggedIn = false,
this.failNetworkError = false,
this.failUnknownError = false,
});
bool get isLoggedIn => loggedIn;
}

6
lib/models/models.dart Normal file
View File

@@ -0,0 +1,6 @@
/// Export all data models
export 'course.dart';
export 'questionnaire.dart';
export 'user_credentials.dart';
export 'evaluation_result.dart';
export 'evaluation_history.dart';

View File

@@ -0,0 +1,224 @@
/// Question type enum for text questions
enum QuestionType {
inspiration, // 启发类(包含"启发"关键词)
suggestion, // 建议类(包含"建议"、"意见"关键词)
overall, // 总体评价zgpj
general, // 通用类型
}
/// Metadata for questionnaire
class QuestionnaireMetadata {
final String title;
final String evaluatedPerson;
final String evaluationContent;
final String tokenValue;
final String questionnaireCode;
final String evaluatedPeopleNumber;
QuestionnaireMetadata({
required this.title,
required this.evaluatedPerson,
required this.evaluationContent,
required this.tokenValue,
required this.questionnaireCode,
required this.evaluatedPeopleNumber,
});
factory QuestionnaireMetadata.fromJson(Map<String, dynamic> json) {
return QuestionnaireMetadata(
title: json['title'] as String? ?? '',
evaluatedPerson: json['evaluatedPerson'] as String? ?? '',
evaluationContent: json['evaluationContent'] as String? ?? '',
tokenValue: json['tokenValue'] as String? ?? '',
questionnaireCode: json['questionnaireCode'] as String? ?? '',
evaluatedPeopleNumber: json['evaluatedPeopleNumber'] as String? ?? '',
);
}
Map<String, dynamic> toJson() {
return {
'title': title,
'evaluatedPerson': evaluatedPerson,
'evaluationContent': evaluationContent,
'tokenValue': tokenValue,
'questionnaireCode': questionnaireCode,
'evaluatedPeopleNumber': evaluatedPeopleNumber,
};
}
}
/// Radio option for single-choice questions
class RadioOption {
final String label; // 如"(A) 非常满意"
final String value; // 如"5_1"5分×100%
final double score; // 解析后的分数
final double weight; // 解析后的权重
RadioOption({
required this.label,
required this.value,
required this.score,
required this.weight,
});
factory RadioOption.fromJson(Map<String, dynamic> json) {
return RadioOption(
label: json['label'] as String? ?? '',
value: json['value'] as String? ?? '',
score: (json['score'] as num?)?.toDouble() ?? 0.0,
weight: (json['weight'] as num?)?.toDouble() ?? 0.0,
);
}
Map<String, dynamic> toJson() {
return {'label': label, 'value': value, 'score': score, 'weight': weight};
}
@override
String toString() {
return 'RadioOption(label: $label, value: $value, score: $score, weight: $weight)';
}
}
/// Radio question (single-choice question)
class RadioQuestion {
final String key; // 动态key如"0000000401"
final String questionText; // 题目文本
final List<RadioOption> options;
final String category; // 如"师德师风"、"教学内容"
RadioQuestion({
required this.key,
required this.questionText,
required this.options,
this.category = '',
});
factory RadioQuestion.fromJson(Map<String, dynamic> json) {
return RadioQuestion(
key: json['key'] as String? ?? '',
questionText: json['questionText'] as String? ?? '',
options:
(json['options'] as List<dynamic>?)
?.map((e) => RadioOption.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
category: json['category'] as String? ?? '',
);
}
Map<String, dynamic> toJson() {
return {
'key': key,
'questionText': questionText,
'options': options.map((e) => e.toJson()).toList(),
'category': category,
};
}
@override
String toString() {
return 'RadioQuestion(key: $key, questionText: $questionText, category: $category, options: ${options.length})';
}
}
/// Text question (open-ended question)
class TextQuestion {
final String key; // 动态key或"zgpj"
final String questionText; // 题目文本
final QuestionType type; // 通过关键词识别的类型
final bool isRequired; // 是否必填
TextQuestion({
required this.key,
required this.questionText,
required this.type,
this.isRequired = false,
});
factory TextQuestion.fromJson(Map<String, dynamic> json) {
return TextQuestion(
key: json['key'] as String? ?? '',
questionText: json['questionText'] as String? ?? '',
type: QuestionType.values.firstWhere(
(e) => e.toString() == 'QuestionType.${json['type']}',
orElse: () => QuestionType.general,
),
isRequired: json['isRequired'] as bool? ?? false,
);
}
Map<String, dynamic> toJson() {
return {
'key': key,
'questionText': questionText,
'type': type.toString().split('.').last,
'isRequired': isRequired,
};
}
@override
String toString() {
return 'TextQuestion(key: $key, questionText: $questionText, type: $type, isRequired: $isRequired)';
}
}
/// Complete questionnaire structure
class Questionnaire {
final QuestionnaireMetadata metadata;
final List<RadioQuestion> radioQuestions;
final List<TextQuestion> textQuestions;
final String tokenValue;
final String questionnaireCode;
final String evaluationContent;
final String evaluatedPeopleNumber;
Questionnaire({
required this.metadata,
required this.radioQuestions,
required this.textQuestions,
required this.tokenValue,
required this.questionnaireCode,
required this.evaluationContent,
required this.evaluatedPeopleNumber,
});
factory Questionnaire.fromJson(Map<String, dynamic> json) {
return Questionnaire(
metadata: QuestionnaireMetadata.fromJson(
json['metadata'] as Map<String, dynamic>? ?? {},
),
radioQuestions:
(json['radioQuestions'] as List<dynamic>?)
?.map((e) => RadioQuestion.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
textQuestions:
(json['textQuestions'] as List<dynamic>?)
?.map((e) => TextQuestion.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
tokenValue: json['tokenValue'] as String? ?? '',
questionnaireCode: json['questionnaireCode'] as String? ?? '',
evaluationContent: json['evaluationContent'] as String? ?? '',
evaluatedPeopleNumber: json['evaluatedPeopleNumber'] as String? ?? '',
);
}
Map<String, dynamic> toJson() {
return {
'metadata': metadata.toJson(),
'radioQuestions': radioQuestions.map((e) => e.toJson()).toList(),
'textQuestions': textQuestions.map((e) => e.toJson()).toList(),
'tokenValue': tokenValue,
'questionnaireCode': questionnaireCode,
'evaluationContent': evaluationContent,
'evaluatedPeopleNumber': evaluatedPeopleNumber,
};
}
@override
String toString() {
return 'Questionnaire(radioQuestions: ${radioQuestions.length}, textQuestions: ${textQuestions.length})';
}
}

View File

@@ -0,0 +1,78 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
/// User credentials for authentication
class UserCredentials {
final String userId;
final String ecPassword;
final String password;
UserCredentials({
required this.userId,
required this.ecPassword,
required this.password,
});
factory UserCredentials.fromJson(Map<String, dynamic> json) {
return UserCredentials(
userId: json['userId'] as String? ?? '',
ecPassword: json['ecPassword'] as String? ?? '',
password: json['password'] as String? ?? '',
);
}
Map<String, dynamic> toJson() {
return {'userId': userId, 'ecPassword': ecPassword, 'password': password};
}
/// Save credentials securely using flutter_secure_storage
Future<void> saveSecurely() async {
const storage = FlutterSecureStorage();
await storage.write(key: 'user_id', value: userId);
await storage.write(key: 'ec_password', value: ecPassword);
await storage.write(key: 'password', value: password);
}
/// Load credentials from secure storage
static Future<UserCredentials?> loadSecurely() async {
const storage = FlutterSecureStorage();
final userId = await storage.read(key: 'user_id');
final ecPassword = await storage.read(key: 'ec_password');
final password = await storage.read(key: 'password');
if (userId == null || ecPassword == null || password == null) {
return null;
}
return UserCredentials(
userId: userId,
ecPassword: ecPassword,
password: password,
);
}
/// Clear credentials from secure storage
static Future<void> clearSecurely() async {
const storage = FlutterSecureStorage();
await storage.delete(key: 'user_id');
await storage.delete(key: 'ec_password');
await storage.delete(key: 'password');
}
@override
String toString() {
return 'UserCredentials(userId: $userId)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is UserCredentials &&
other.userId == userId &&
other.ecPassword == ecPassword &&
other.password == password;
}
@override
int get hashCode => Object.hash(userId, ecPassword, password);
}