1. 新增我的考试页面,包含学科筛选Tab和分页考试列表 2. 新增学科图标工具类,替换我的报告页重复的学科图标逻辑 3. 在我的页面菜单中添加我的考试入口 4. 新增exam模块的API接口和数据模型 5. 优化我的报告页面卡片样式,统一与考试卡片的布局
110 lines
3.1 KiB
Dart
110 lines
3.1 KiB
Dart
/// ============================================================
|
|
/// 【exam 模块模型】我的考试 —— 对标 Vue3 端 api/exam/type.ts
|
|
///
|
|
/// ExamDto —— 单条考试记录
|
|
/// ExamSubjectTab —— 考试科目筛选 Tab
|
|
/// ExamResponse —— 考试列表分页响应
|
|
///
|
|
/// 序列化:手动 fromJson,与项目其他模块风格一致,不依赖 build_runner。
|
|
///
|
|
/// ⚠ 注意:后端返回的 id 字段类型不统一(数字 or 字符串),
|
|
/// 所有数值字段统一用 _asInt() 做兼容解析,避免类型转换异常。
|
|
/// ============================================================
|
|
library;
|
|
|
|
int? _optInt(dynamic v) {
|
|
if (v == null) return null;
|
|
if (v is int) return v;
|
|
if (v is double) return v.toInt();
|
|
final s = v.toString();
|
|
final n = int.tryParse(s);
|
|
if (n != null) return n;
|
|
return double.tryParse(s)?.toInt();
|
|
}
|
|
|
|
int _asInt(dynamic v, [int fallback = 0]) => _optInt(v) ?? fallback;
|
|
|
|
/// 单条考试记录
|
|
class ExamDto {
|
|
const ExamDto({
|
|
this.id = 0,
|
|
this.taskId = 0,
|
|
this.taskName = '',
|
|
this.baseSubjectId = 0,
|
|
this.baseSubjectName = '',
|
|
this.startTime = '',
|
|
this.endTime = '',
|
|
this.totalScore = 0,
|
|
this.userScore = 0,
|
|
});
|
|
|
|
final int id;
|
|
final int taskId;
|
|
final String taskName;
|
|
final int baseSubjectId;
|
|
final String baseSubjectName;
|
|
final String startTime;
|
|
final String endTime;
|
|
final int totalScore;
|
|
final int userScore;
|
|
|
|
factory ExamDto.fromJson(Map<String, dynamic> json) {
|
|
return ExamDto(
|
|
id: _asInt(json['id']),
|
|
taskId: _asInt(json['taskId']),
|
|
taskName: (json['taskName'] as String?) ?? '',
|
|
baseSubjectId: _asInt(json['baseSubjectId']),
|
|
baseSubjectName: (json['baseSubjectName'] as String?) ?? '',
|
|
startTime: (json['startTime'] as String?) ?? '',
|
|
endTime: (json['endTime'] as String?) ?? '',
|
|
totalScore: _asInt(json['totalScore']),
|
|
userScore: _asInt(json['userScore']),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 考试科目筛选 Tab
|
|
class ExamSubjectTab {
|
|
const ExamSubjectTab({this.id, this.name = ''});
|
|
|
|
/// 为 null 表示"全部"
|
|
final int? id;
|
|
final String name;
|
|
|
|
factory ExamSubjectTab.fromJson(Map<String, dynamic> json) {
|
|
return ExamSubjectTab(
|
|
id: _optInt(json['id']),
|
|
name: (json['name'] as String?) ?? '',
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 考试列表分页响应
|
|
class ExamResponse {
|
|
const ExamResponse({
|
|
this.data = const [],
|
|
this.total = 0,
|
|
this.pageIndex = 1,
|
|
this.pageSize = 10,
|
|
});
|
|
|
|
final List<ExamDto> data;
|
|
final int total;
|
|
final int pageIndex;
|
|
final int pageSize;
|
|
|
|
factory ExamResponse.fromJson(Map<String, dynamic> json) {
|
|
final payload = (json['data'] as Map<String, dynamic>?) ?? json;
|
|
final list = (payload['data'] as List<dynamic>? ?? const [])
|
|
.whereType<Map<String, dynamic>>()
|
|
.map(ExamDto.fromJson)
|
|
.toList();
|
|
return ExamResponse(
|
|
data: list,
|
|
total: (payload['total'] as num?)?.toInt() ?? list.length,
|
|
pageIndex: (payload['pageIndex'] as num?)?.toInt() ?? 1,
|
|
pageSize: (payload['pageSize'] as num?)?.toInt() ?? 10,
|
|
);
|
|
}
|
|
}
|