Flutter 数据层 (Phase F1): - journal_entry.dart: 日记数据模型 (Mood/Weather/tags/version) - journal_element.dart: 元素模型 (text/image/sticker/handwriting_ref/tape) - school_class.dart: 班级模型 - user_settings.dart: 用户设置 (主题/画笔/字号) - isar_database.dart: Isar 初始化 - api_client.dart: Dio + JWT注入 + 离线感知 + 401处理 - journal_repository.dart: 抽象接口 + InMemory实现 (乐观锁) - sync_engine.dart: WiFi同步 + 操作队列 + 重试(5次) + 快照持久化 Rust 班级系统 (Phase B3): - class_service.rs: 创建班级(6位码) + 加入班级 + 成员管理 - topic_service.rs: 老师布置主题 + 主题列表 - comment_service.rs: 老师点评 + 评语列表 - class_handler.rs: 5个API端点 + 权限守卫 - topic_handler.rs: 2个API端点 - comment_handler.rs: 2个API端点 - dto.rs: 新增5个DTO (ClassMemberResp/CreateTopicReq/TopicResp/CreateCommentReq/CommentResp) - 6条新路由注册 验证: cargo check 通过, 433测试全绿, flutter analyze 1 warning
113 lines
3.3 KiB
Dart
113 lines
3.3 KiB
Dart
// 班级数据模型 — 手写不可变类(避免 build_runner 依赖)
|
||
|
||
import 'package:uuid/uuid.dart';
|
||
|
||
/// 班级 — 老师创建,学生通过班级码加入
|
||
///
|
||
/// 班级码安全规则:
|
||
/// - 6 位字母数字混合(62^6 约 568 亿种组合)
|
||
/// - 有效期控制(学期结束自动失效)
|
||
/// - 连续 5 次错误锁定 30 分钟
|
||
class SchoolClass {
|
||
final String id;
|
||
final String name;
|
||
final String schoolName;
|
||
final String teacherId;
|
||
final String classCode;
|
||
final int memberCount;
|
||
final bool isActive;
|
||
final DateTime? expiresAt;
|
||
final DateTime createdAt;
|
||
final DateTime updatedAt;
|
||
|
||
const SchoolClass({
|
||
required this.id,
|
||
required this.name,
|
||
required this.schoolName,
|
||
required this.teacherId,
|
||
required this.classCode,
|
||
this.memberCount = 0,
|
||
this.isActive = true,
|
||
this.expiresAt,
|
||
required this.createdAt,
|
||
required this.updatedAt,
|
||
});
|
||
|
||
SchoolClass copyWith({
|
||
String? id,
|
||
String? name,
|
||
String? schoolName,
|
||
String? teacherId,
|
||
String? classCode,
|
||
int? memberCount,
|
||
bool? isActive,
|
||
DateTime? expiresAt,
|
||
bool clearExpiresAt = false,
|
||
DateTime? createdAt,
|
||
DateTime? updatedAt,
|
||
}) =>
|
||
SchoolClass(
|
||
id: id ?? this.id,
|
||
name: name ?? this.name,
|
||
schoolName: schoolName ?? this.schoolName,
|
||
teacherId: teacherId ?? this.teacherId,
|
||
classCode: classCode ?? this.classCode,
|
||
memberCount: memberCount ?? this.memberCount,
|
||
isActive: isActive ?? this.isActive,
|
||
expiresAt: clearExpiresAt ? null : (expiresAt ?? this.expiresAt),
|
||
createdAt: createdAt ?? this.createdAt,
|
||
updatedAt: updatedAt ?? this.updatedAt,
|
||
);
|
||
|
||
Map<String, dynamic> toJson() => {
|
||
'id': id,
|
||
'name': name,
|
||
'school_name': schoolName,
|
||
'teacher_id': teacherId,
|
||
'class_code': classCode,
|
||
'member_count': memberCount,
|
||
'is_active': isActive,
|
||
'expires_at': expiresAt?.toIso8601String(),
|
||
'created_at': createdAt.toIso8601String(),
|
||
'updated_at': updatedAt.toIso8601String(),
|
||
};
|
||
|
||
factory SchoolClass.fromJson(Map<String, dynamic> json) => SchoolClass(
|
||
id: json['id'] as String,
|
||
name: json['name'] as String,
|
||
schoolName: json['school_name'] as String,
|
||
teacherId: json['teacher_id'] as String,
|
||
classCode: json['class_code'] as String,
|
||
memberCount: (json['member_count'] as int?) ?? 0,
|
||
isActive: (json['is_active'] as bool?) ?? true,
|
||
expiresAt: json['expires_at'] != null
|
||
? DateTime.parse(json['expires_at'] as String)
|
||
: null,
|
||
createdAt: DateTime.parse(json['created_at'] as String),
|
||
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||
);
|
||
|
||
/// 创建新班级的工厂方法 — 自动生成 id 和时间戳
|
||
factory SchoolClass.create({
|
||
required String name,
|
||
required String schoolName,
|
||
required String teacherId,
|
||
required String classCode,
|
||
DateTime? expiresAt,
|
||
}) {
|
||
final now = DateTime.now();
|
||
return SchoolClass(
|
||
id: const Uuid().v4(),
|
||
name: name,
|
||
schoolName: schoolName,
|
||
teacherId: teacherId,
|
||
classCode: classCode,
|
||
memberCount: 0,
|
||
isActive: true,
|
||
expiresAt: expiresAt,
|
||
createdAt: now,
|
||
updatedAt: now,
|
||
);
|
||
}
|
||
}
|