F8 班级系统: - ClassBloc 状态管理 (班级列表/日记墙/成员/主题/评语) - 班级主页: 日记墙 + 主题布置 Tab + 成员列表 - 老师管理页: 创建班级 + 布置主题 + 点评入口 - 班级码展示 + 评语卡片 F9 家长功能 (占位): - 家长中心页面框架: 日记查看/心情统计/使用时间/数据管理 - PIPL 合规提示卡片 F10 搜索/设置 (部分): - 个人中心: 用户信息 + 角色展示 + 功能入口 + 退出登录 - 搜索页: 标签筛选 + 心情过滤 + 搜索框 验证: flutter analyze 0 error
411 lines
10 KiB
Dart
411 lines
10 KiB
Dart
// 班级 BLoC — 管理班级状态、成员、日记墙、主题布置和评语
|
|
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:nuanji_app/data/models/journal_entry.dart';
|
|
import 'package:nuanji_app/data/models/school_class.dart';
|
|
|
|
// ===== Events =====
|
|
|
|
sealed class ClassEvent {
|
|
const ClassEvent();
|
|
}
|
|
|
|
/// 加载我的班级列表
|
|
final class ClassLoadMyClasses extends ClassEvent {
|
|
const ClassLoadMyClasses();
|
|
}
|
|
|
|
/// 选择当前班级
|
|
final class ClassSelected extends ClassEvent {
|
|
final String classId;
|
|
const ClassSelected(this.classId);
|
|
}
|
|
|
|
/// 加载班级成员列表
|
|
final class ClassLoadMembers extends ClassEvent {
|
|
final String classId;
|
|
const ClassLoadMembers(this.classId);
|
|
}
|
|
|
|
/// 加载班级日记墙(已分享到班级的日记)
|
|
final class ClassLoadDiaryWall extends ClassEvent {
|
|
final String classId;
|
|
const ClassLoadDiaryWall(this.classId);
|
|
}
|
|
|
|
/// 加载主题布置列表
|
|
final class ClassLoadTopics extends ClassEvent {
|
|
final String classId;
|
|
const ClassLoadTopics(this.classId);
|
|
}
|
|
|
|
/// 创建班级(老师)
|
|
final class ClassCreate extends ClassEvent {
|
|
final String name;
|
|
final String? schoolName;
|
|
const ClassCreate({required this.name, this.schoolName});
|
|
}
|
|
|
|
/// 布置主题(老师)
|
|
final class TopicAssign extends ClassEvent {
|
|
final String classId;
|
|
final String title;
|
|
final String? description;
|
|
final DateTime? dueDate;
|
|
const TopicAssign({
|
|
required this.classId,
|
|
required this.title,
|
|
this.description,
|
|
this.dueDate,
|
|
});
|
|
}
|
|
|
|
/// 加载日记评语
|
|
final class ClassLoadComments extends ClassEvent {
|
|
final String journalId;
|
|
const ClassLoadComments(this.journalId);
|
|
}
|
|
|
|
// ===== State =====
|
|
|
|
/// 班级成员模型
|
|
class ClassMember {
|
|
final String userId;
|
|
final String role;
|
|
final String? nickname;
|
|
final DateTime joinedAt;
|
|
|
|
const ClassMember({
|
|
required this.userId,
|
|
required this.role,
|
|
this.nickname,
|
|
required this.joinedAt,
|
|
});
|
|
}
|
|
|
|
/// 主题布置模型
|
|
class TopicAssignment {
|
|
final String id;
|
|
final String classId;
|
|
final String teacherId;
|
|
final String title;
|
|
final String? description;
|
|
final DateTime? dueDate;
|
|
final bool isActive;
|
|
|
|
const TopicAssignment({
|
|
required this.id,
|
|
required this.classId,
|
|
required this.teacherId,
|
|
required this.title,
|
|
this.description,
|
|
this.dueDate,
|
|
this.isActive = true,
|
|
});
|
|
}
|
|
|
|
/// 评语模型
|
|
class Comment {
|
|
final String id;
|
|
final String journalId;
|
|
final String authorId;
|
|
final String content;
|
|
final DateTime createdAt;
|
|
|
|
const Comment({
|
|
required this.id,
|
|
required this.journalId,
|
|
required this.authorId,
|
|
required this.content,
|
|
required this.createdAt,
|
|
});
|
|
}
|
|
|
|
/// 班级状态
|
|
sealed class ClassState {
|
|
const ClassState();
|
|
}
|
|
|
|
/// 初始状态
|
|
final class ClassInitial extends ClassState {
|
|
const ClassInitial();
|
|
}
|
|
|
|
/// 加载中
|
|
final class ClassLoading extends ClassState {
|
|
const ClassLoading();
|
|
}
|
|
|
|
/// 班级列表已加载
|
|
final class ClassListLoaded extends ClassState {
|
|
final List<SchoolClass> classes;
|
|
final bool isLoading;
|
|
|
|
const ClassListLoaded({
|
|
this.classes = const [],
|
|
this.isLoading = false,
|
|
});
|
|
|
|
ClassListLoaded copyWith({
|
|
List<SchoolClass>? classes,
|
|
bool? isLoading,
|
|
}) =>
|
|
ClassListLoaded(
|
|
classes: classes ?? this.classes,
|
|
isLoading: isLoading ?? this.isLoading,
|
|
);
|
|
}
|
|
|
|
/// 班级详情已加载(日记墙 / 成员 / 主题 / 评语)
|
|
final class ClassDetailLoaded extends ClassState {
|
|
final SchoolClass classInfo;
|
|
final List<ClassMember> members;
|
|
final List<JournalEntry> diaryWall;
|
|
final List<TopicAssignment> topics;
|
|
final List<Comment> comments;
|
|
final bool isLoadingWall;
|
|
final bool isLoadingMembers;
|
|
final String? selectedJournalId;
|
|
|
|
const ClassDetailLoaded({
|
|
required this.classInfo,
|
|
this.members = const [],
|
|
this.diaryWall = const [],
|
|
this.topics = const [],
|
|
this.comments = const [],
|
|
this.isLoadingWall = false,
|
|
this.isLoadingMembers = false,
|
|
this.selectedJournalId,
|
|
});
|
|
|
|
ClassDetailLoaded copyWith({
|
|
SchoolClass? classInfo,
|
|
List<ClassMember>? members,
|
|
List<JournalEntry>? diaryWall,
|
|
List<TopicAssignment>? topics,
|
|
List<Comment>? comments,
|
|
bool? isLoadingWall,
|
|
bool? isLoadingMembers,
|
|
String? selectedJournalId,
|
|
bool clearSelectedJournal = false,
|
|
}) =>
|
|
ClassDetailLoaded(
|
|
classInfo: classInfo ?? this.classInfo,
|
|
members: members ?? this.members,
|
|
diaryWall: diaryWall ?? this.diaryWall,
|
|
topics: topics ?? this.topics,
|
|
comments: comments ?? this.comments,
|
|
isLoadingWall: isLoadingWall ?? this.isLoadingWall,
|
|
isLoadingMembers: isLoadingMembers ?? this.isLoadingMembers,
|
|
selectedJournalId: clearSelectedJournal
|
|
? null
|
|
: (selectedJournalId ?? this.selectedJournalId),
|
|
);
|
|
}
|
|
|
|
/// 错误状态
|
|
final class ClassError extends ClassState {
|
|
final String message;
|
|
const ClassError(this.message);
|
|
}
|
|
|
|
// ===== BLoC =====
|
|
|
|
class ClassBloc extends Bloc<ClassEvent, ClassState> {
|
|
ClassBloc() : super(const ClassInitial()) {
|
|
on<ClassLoadMyClasses>(_onLoadMyClasses);
|
|
on<ClassSelected>(_onClassSelected);
|
|
on<ClassLoadMembers>(_onLoadMembers);
|
|
on<ClassLoadDiaryWall>(_onLoadDiaryWall);
|
|
on<ClassLoadTopics>(_onLoadTopics);
|
|
on<ClassCreate>(_onCreateClass);
|
|
on<TopicAssign>(_onTopicAssign);
|
|
on<ClassLoadComments>(_onLoadComments);
|
|
}
|
|
|
|
Future<void> _onLoadMyClasses(
|
|
ClassLoadMyClasses event,
|
|
Emitter<ClassState> emit,
|
|
) async {
|
|
emit(const ClassListLoaded(isLoading: true));
|
|
|
|
// Phase 1: 占位数据,待 API 集成
|
|
await Future.delayed(const Duration(milliseconds: 300));
|
|
final now = DateTime.now();
|
|
|
|
emit(ClassListLoaded(classes: [
|
|
SchoolClass(
|
|
id: 'class-1',
|
|
name: '三年二班',
|
|
schoolName: '阳光小学',
|
|
teacherId: 'teacher-1',
|
|
classCode: 'a1b2c3',
|
|
memberCount: 28,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
),
|
|
]));
|
|
}
|
|
|
|
Future<void> _onClassSelected(
|
|
ClassSelected event,
|
|
Emitter<ClassState> emit,
|
|
) async {
|
|
final now = DateTime.now();
|
|
final classInfo = SchoolClass(
|
|
id: event.classId,
|
|
name: '三年二班',
|
|
schoolName: '阳光小学',
|
|
teacherId: 'teacher-1',
|
|
classCode: 'a1b2c3',
|
|
memberCount: 28,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
);
|
|
|
|
emit(ClassDetailLoaded(classInfo: classInfo));
|
|
|
|
add(ClassLoadDiaryWall(event.classId));
|
|
add(ClassLoadMembers(event.classId));
|
|
add(ClassLoadTopics(event.classId));
|
|
}
|
|
|
|
Future<void> _onLoadMembers(
|
|
ClassLoadMembers event,
|
|
Emitter<ClassState> emit,
|
|
) async {
|
|
if (state is! ClassDetailLoaded) return;
|
|
final current = state as ClassDetailLoaded;
|
|
emit(current.copyWith(isLoadingMembers: true));
|
|
|
|
await Future.delayed(const Duration(milliseconds: 200));
|
|
final now = DateTime.now();
|
|
|
|
final members = List.generate(
|
|
28,
|
|
(i) => ClassMember(
|
|
userId: 'user-$i',
|
|
role: i == 0 ? 'teacher' : 'student',
|
|
nickname: i == 0 ? '王老师' : '同学$i',
|
|
joinedAt: now,
|
|
),
|
|
);
|
|
|
|
emit(current.copyWith(members: members, isLoadingMembers: false));
|
|
}
|
|
|
|
Future<void> _onLoadDiaryWall(
|
|
ClassLoadDiaryWall event,
|
|
Emitter<ClassState> emit,
|
|
) async {
|
|
if (state is! ClassDetailLoaded) return;
|
|
final current = state as ClassDetailLoaded;
|
|
emit(current.copyWith(isLoadingWall: true));
|
|
|
|
await Future.delayed(const Duration(milliseconds: 200));
|
|
final now = DateTime.now();
|
|
final titles = ['快乐的周末', '春天来了', '我的小猫', '数学课', '好朋友', '下雨天'];
|
|
|
|
final diaries = List.generate(
|
|
6,
|
|
(i) => JournalEntry(
|
|
id: 'diary-$i',
|
|
authorId: 'user-${i + 1}',
|
|
classId: event.classId,
|
|
title: titles[i],
|
|
date: now.subtract(Duration(days: i)),
|
|
mood: Mood.values[i % Mood.values.length],
|
|
tags: const ['日常', '心情'],
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
),
|
|
);
|
|
|
|
emit(current.copyWith(diaryWall: diaries, isLoadingWall: false));
|
|
}
|
|
|
|
Future<void> _onLoadTopics(
|
|
ClassLoadTopics event,
|
|
Emitter<ClassState> emit,
|
|
) async {
|
|
if (state is! ClassDetailLoaded) return;
|
|
final current = state as ClassDetailLoaded;
|
|
|
|
final topics = [
|
|
TopicAssignment(
|
|
id: 'topic-1',
|
|
classId: event.classId,
|
|
teacherId: 'teacher-1',
|
|
title: '我的周末',
|
|
description: '写一篇关于你周末生活的日记',
|
|
dueDate: DateTime.now().add(const Duration(days: 3)),
|
|
),
|
|
TopicAssignment(
|
|
id: 'topic-2',
|
|
classId: event.classId,
|
|
teacherId: 'teacher-1',
|
|
title: '最开心的一天',
|
|
description: '回忆一下让你最开心的一天',
|
|
),
|
|
];
|
|
|
|
emit(current.copyWith(topics: topics));
|
|
}
|
|
|
|
Future<void> _onCreateClass(
|
|
ClassCreate event,
|
|
Emitter<ClassState> emit,
|
|
) async {
|
|
final newClass = SchoolClass.create(
|
|
name: event.name,
|
|
schoolName: event.schoolName ?? '',
|
|
teacherId: 'current-user',
|
|
classCode: 'x1y2z3',
|
|
);
|
|
|
|
if (state is ClassListLoaded) {
|
|
final current = state as ClassListLoaded;
|
|
emit(current.copyWith(classes: [...current.classes, newClass]));
|
|
}
|
|
}
|
|
|
|
Future<void> _onTopicAssign(
|
|
TopicAssign event,
|
|
Emitter<ClassState> emit,
|
|
) async {
|
|
if (state is! ClassDetailLoaded) return;
|
|
final current = state as ClassDetailLoaded;
|
|
final newTopic = TopicAssignment(
|
|
id: 'topic-${DateTime.now().millisecondsSinceEpoch}',
|
|
classId: event.classId,
|
|
teacherId: 'current-user',
|
|
title: event.title,
|
|
description: event.description,
|
|
dueDate: event.dueDate,
|
|
);
|
|
emit(current.copyWith(topics: [newTopic, ...current.topics]));
|
|
}
|
|
|
|
Future<void> _onLoadComments(
|
|
ClassLoadComments event,
|
|
Emitter<ClassState> emit,
|
|
) async {
|
|
if (state is! ClassDetailLoaded) return;
|
|
final current = state as ClassDetailLoaded;
|
|
|
|
final comments = [
|
|
Comment(
|
|
id: 'comment-1',
|
|
journalId: event.journalId,
|
|
authorId: 'teacher-1',
|
|
content: '写得很好,继续保持!',
|
|
createdAt: DateTime.now(),
|
|
),
|
|
];
|
|
emit(current.copyWith(
|
|
comments: comments,
|
|
selectedJournalId: event.journalId,
|
|
));
|
|
}
|
|
}
|