Files
nj/app/lib/data/repositories/class_repository.dart
iven 263ddf31a6 feat(app): 数据层集成 — RemoteJournalRepository + ClassRepository + SSE 通知
数据层新增:
- RemoteJournalRepository: 日记 CRUD + 元素管理,通过 ApiClient 连接后端
- ClassRepository: 班级/主题/评语 API 操作(getMyClasses/joinClass/assignTopic/createComment)
- SseNotificationService: SSE 实时通知监听 + 自动重连 + 事件流
- ApiException: 统一 API 错误封装
- DTO: ClassMemberDto + TopicDto + CommentDto

设计:
- Repository 模式: 抽象接口 + 远程实现 + 内存实现
- SSE: Dio stream + SSE 协议解析 + 3秒自动重连
- 所有 Repository 通过 ApiClient 注入,依赖现有 JWT 拦截器

验证: flutter analyze 0 error
2026-06-01 10:11:47 +08:00

202 lines
6.0 KiB
Dart

// 班级仓库 — 通过 API 客户端管理班级、主题、评语
import '../models/school_class.dart';
import '../remote/api_client.dart';
/// 班级成员数据
class ClassMemberDto {
final String userId;
final String role;
final String? nickname;
final DateTime joinedAt;
const ClassMemberDto({
required this.userId,
required this.role,
this.nickname,
required this.joinedAt,
});
factory ClassMemberDto.fromJson(Map<String, dynamic> json) => ClassMemberDto(
userId: json['user_id'] as String,
role: json['role'] as String,
nickname: json['nickname'] as String?,
joinedAt: DateTime.parse(json['joined_at'] as String),
);
}
/// 主题布置数据
class TopicDto {
final String id;
final String classId;
final String teacherId;
final String title;
final String? description;
final DateTime? dueDate;
final bool isActive;
const TopicDto({
required this.id,
required this.classId,
required this.teacherId,
required this.title,
this.description,
this.dueDate,
this.isActive = true,
});
factory TopicDto.fromJson(Map<String, dynamic> json) => TopicDto(
id: json['id'] as String,
classId: json['class_id'] as String,
teacherId: json['teacher_id'] as String,
title: json['title'] as String,
description: json['description'] as String?,
dueDate: json['due_date'] != null
? DateTime.parse(json['due_date'] as String)
: null,
isActive: (json['is_active'] as bool?) ?? true,
);
}
/// 评语数据
class CommentDto {
final String id;
final String journalId;
final String authorId;
final String content;
final DateTime createdAt;
const CommentDto({
required this.id,
required this.journalId,
required this.authorId,
required this.content,
required this.createdAt,
});
factory CommentDto.fromJson(Map<String, dynamic> json) => CommentDto(
id: json['id'] as String,
journalId: json['journal_id'] as String,
authorId: json['author_id'] as String,
content: json['content'] as String,
createdAt: DateTime.parse(json['created_at'] as String),
);
}
/// 班级仓库 — 班级/主题/评语的 API 操作
class ClassRepository {
final ApiClient _api;
ClassRepository({required ApiClient api}) : _api = api;
// ===== 班级 =====
/// 获取我加入的班级列表
Future<List<SchoolClass>> getMyClasses() async {
final response = await _api.get('/diary/classes');
final body = response.data as Map<String, dynamic>;
final items = body['data'] as List? ?? [];
return items
.map((json) => SchoolClass.fromJson(json as Map<String, dynamic>))
.toList();
}
/// 创建班级(老师)
Future<SchoolClass> createClass({
required String name,
String? schoolName,
}) async {
final response = await _api.post('/diary/classes', data: {
'name': name,
if (schoolName != null) 'school_name': schoolName,
});
final body = response.data as Map<String, dynamic>;
return SchoolClass.fromJson(body['data'] as Map<String, dynamic>);
}
/// 加入班级(通过班级码)
Future<SchoolClass> joinClass(String classCode, {String? nickname}) async {
final response = await _api.post('/diary/classes/join', data: {
'class_code': classCode,
if (nickname != null) 'nickname': nickname,
});
final body = response.data as Map<String, dynamic>;
return SchoolClass.fromJson(body['data'] as Map<String, dynamic>);
}
/// 获取班级详情
Future<SchoolClass> getClass(String classId) async {
final response = await _api.get('/diary/classes/$classId');
final body = response.data as Map<String, dynamic>;
return SchoolClass.fromJson(body['data'] as Map<String, dynamic>);
}
/// 获取班级成员列表
Future<List<ClassMemberDto>> getMembers(String classId) async {
final response = await _api.get('/diary/classes/$classId/members');
final body = response.data as Map<String, dynamic>;
final items = body['data'] as List? ?? [];
return items
.map((json) => ClassMemberDto.fromJson(json as Map<String, dynamic>))
.toList();
}
// ===== 主题 =====
/// 获取班级主题列表
Future<List<TopicDto>> getTopics(String classId) async {
final response = await _api.get('/diary/classes/$classId/topics');
final body = response.data as Map<String, dynamic>;
final items = body['data'] as List? ?? [];
return items
.map((json) => TopicDto.fromJson(json as Map<String, dynamic>))
.toList();
}
/// 布置主题(老师)
Future<TopicDto> assignTopic({
required String classId,
required String title,
String? description,
DateTime? dueDate,
}) async {
final response = await _api.post('/diary/classes/$classId/topics', data: {
'title': title,
if (description != null) 'description': description,
if (dueDate != null) 'due_date': dueDate.toIso8601String(),
});
final body = response.data as Map<String, dynamic>;
return TopicDto.fromJson(body['data'] as Map<String, dynamic>);
}
// ===== 评语 =====
/// 获取日记评语列表
Future<List<CommentDto>> getComments(String journalId) async {
final response = await _api.get('/diary/journals/$journalId/comments');
final body = response.data as Map<String, dynamic>;
final items = body['data'] as List? ?? [];
return items
.map((json) => CommentDto.fromJson(json as Map<String, dynamic>))
.toList();
}
/// 添加评语(老师点评学生日记)
Future<CommentDto> createComment({
required String journalId,
required String content,
}) async {
final response = await _api.post(
'/diary/journals/$journalId/comments',
data: {'content': content},
);
final body = response.data as Map<String, dynamic>;
return CommentDto.fromJson(body['data'] as Map<String, dynamic>);
}
/// 删除评语
Future<void> deleteComment(String commentId) async {
await _api.delete('/diary/comments/$commentId');
}
}