Files
nj/app/lib/features/teacher/views/teacher_page.dart
iven d67eedf7de feat(app): 多页面动态化 — 搜索/资料/教师/贴纸库/模板/日历
- SearchPage: 热搜词从日记标签频率动态生成 + 模板搜索网格
- ProfilePage: 成就徽章从 AchievementBloc 动态加载 + 头像首字母
- TeacherPage: 班级码改为对话框展示 (班级名+码+人数)
- StickerLibraryPage: 分类从 API 动态合并 + 精选包卡片动态化
- TemplateGalleryPage: 适配动态数据
- ClassPage: 微调
- HomePage: 路由适配
- CalendarBloc: 新增测试
- AppRouter: 路由更新
2026-06-07 10:44:04 +08:00

350 lines
11 KiB
Dart

// 老师管理页面 — 创建班级 + 布置主题 + 点评日记
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:nuanji_app/core/theme/app_colors.dart';
import 'package:nuanji_app/core/theme/app_radius.dart';
import 'package:nuanji_app/data/repositories/class_repository.dart';
import 'package:nuanji_app/data/repositories/journal_repository.dart';
import 'package:nuanji_app/data/models/school_class.dart';
import '../../class_/bloc/class_bloc.dart';
/// 老师管理页面 — 教师专属功能入口
class TeacherPage extends StatelessWidget {
const TeacherPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => ClassBloc(
classRepository: context.read<ClassRepository>(),
journalRepository: context.read<JournalRepository>(),
)..add(const ClassLoadMyClasses()),
child: const _TeacherView(),
);
}
}
class _TeacherView extends StatelessWidget {
const _TeacherView();
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Scaffold(
appBar: AppBar(title: const Text('教师管理')),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 创建班级卡片
_ActionCard(
icon: Icons.add_circle_outline,
iconColor: AppColors.accent,
title: '创建班级',
subtitle: '创建新班级并邀请学生加入',
onTap: () => _showCreateClassDialog(context),
),
const SizedBox(height: 12),
// 布置主题卡片
_ActionCard(
icon: Icons.assignment_outlined,
iconColor: AppColors.secondary,
title: '布置主题',
subtitle: '给班级布置日记写作主题',
onTap: () => _showAssignTopicDialog(context),
),
const SizedBox(height: 12),
// 班级码管理
_ActionCard(
icon: Icons.qr_code,
iconColor: AppColors.tertiary,
title: '班级码管理',
subtitle: '查看和重置班级码',
onTap: () => _showClassCodes(context),
),
const SizedBox(height: 24),
// 最近点评
Text(
'快捷功能',
style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
_ActionCard(
icon: Icons.rate_review_outlined,
iconColor: AppColors.rose,
title: '点评日记',
subtitle: '查看学生日记并点评',
onTap: () => context.go('/class'),
),
const SizedBox(height: 12),
_ActionCard(
icon: Icons.bar_chart_outlined,
iconColor: colorScheme.primary,
title: '班级统计',
subtitle: '查看班级写作活跃度',
onTap: () => context.go('/mood'),
),
],
),
),
);
}
void _showCreateClassDialog(BuildContext context) {
final nameController = TextEditingController();
final schoolController = TextEditingController();
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('创建班级'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: nameController,
decoration: const InputDecoration(
labelText: '班级名称',
hintText: '例如: 三年二班',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: schoolController,
decoration: const InputDecoration(
labelText: '学校名称(可选)',
hintText: '例如: 阳光小学',
border: OutlineInputBorder(),
),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
if (nameController.text.trim().isNotEmpty) {
context.read<ClassBloc>().add(ClassCreate(
name: nameController.text.trim(),
schoolName: schoolController.text.trim().isEmpty
? null
: schoolController.text.trim(),
));
Navigator.pop(dialogContext);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('班级创建成功!')),
);
}
},
child: const Text('创建'),
),
],
),
);
}
void _showClassCodes(BuildContext context) {
final classState = context.read<ClassBloc>().state;
final classes = classState is ClassListLoaded ? classState.classes : <SchoolClass>[];
if (classes.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请先创建班级')),
);
return;
}
showDialog(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('班级码管理'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: classes.map((c) => ListTile(
leading: const Icon(Icons.qr_code, color: AppColors.tertiary),
title: Text(c.name),
subtitle: Text('班级码: ${c.classCode} · ${c.memberCount}'),
dense: true,
)).toList(),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('关闭'),
),
],
),
);
}
void _showAssignTopicDialog(BuildContext context) {
final titleController = TextEditingController();
final descController = TextEditingController();
// 从 ClassBloc 获取已加载的班级列表
final classState = context.read<ClassBloc>().state;
final classes = classState is ClassListLoaded ? classState.classes : <SchoolClass>[];
// 无班级时提示先创建
if (classes.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('请先创建班级后再布置主题')),
);
return;
}
String selectedClassId = classes.first.id;
showDialog(
context: context,
builder: (dialogContext) => StatefulBuilder(
builder: (context, setDialogState) => AlertDialog(
title: const Text('布置主题'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 班级选择下拉框
DropdownButtonFormField<String>(
value: selectedClassId,
decoration: const InputDecoration(
labelText: '选择班级',
border: OutlineInputBorder(),
),
items: classes
.map((c) => DropdownMenuItem(
value: c.id,
child: Text(c.name),
))
.toList(),
onChanged: (v) {
if (v != null) setDialogState(() => selectedClassId = v);
},
),
const SizedBox(height: 12),
TextField(
controller: titleController,
decoration: const InputDecoration(
labelText: '主题标题',
hintText: '例如: 我的周末',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 12),
TextField(
controller: descController,
decoration: const InputDecoration(
labelText: '描述(可选)',
hintText: '主题要求和说明',
border: OutlineInputBorder(),
),
maxLines: 3,
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
if (titleController.text.trim().isNotEmpty) {
context.read<ClassBloc>().add(TopicAssign(
classId: selectedClassId,
title: titleController.text.trim(),
description: descController.text.trim().isEmpty
? null
: descController.text.trim(),
));
Navigator.pop(dialogContext);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('主题布置成功!')),
);
}
},
child: const Text('布置'),
),
],
),
),
);
}
}
/// 操作卡片
class _ActionCard extends StatelessWidget {
const _ActionCard({
required this.icon,
required this.iconColor,
required this.title,
required this.subtitle,
required this.onTap,
});
final IconData icon;
final Color iconColor;
final String title;
final String subtitle;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.mdBorder,
side: BorderSide(color: colorScheme.outlineVariant),
),
child: InkWell(
onTap: onTap,
borderRadius: AppRadius.mdBorder,
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: iconColor.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: iconColor, size: 22),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: theme.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600)),
const SizedBox(height: 2),
Text(subtitle, style: theme.textTheme.bodySmall?.copyWith(
color: colorScheme.onSurface.withValues(alpha: 0.5),
)),
],
),
),
Icon(Icons.chevron_right, color: colorScheme.onSurface.withValues(alpha: 0.3)),
],
),
),
),
);
}
}