【Flutter for OpenHarmony】实战 - Day 4(1):作业页开发
·
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
一、本日目标
- 实现作业管理页面
- 创建作业数据模型
- 实现作业卡片组件
- 完成作业增删改查功能
- 添加筛选和统计功能
二、文件变更
lib/
├── models/
│ └── homework.dart # 作业数据模型(新增)
├── widgets/
│ ├── homework_card.dart # 作业卡片组件(新增)
│ └── add_homework_dialog.dart # 添加/编辑作业弹窗(新增)
└── pages/
└── homework_page.dart # 作业主页(重构)
三、核心代码实现
3.1 作业数据模型 (lib/models/homework.dart)
class Homework {
final String id;
final String title; // 作业标题
final String courseName; // 所属课程
final String description; // 作业描述
final DateTime deadline; // 截止日期
final bool isCompleted; // 是否完成
final int priority; // 优先级 1-高 2-中 3-低
Homework({
required this.id,
required this.title,
required this.courseName,
required this.description,
required this.deadline,
this.isCompleted = false,
this.priority = 2,
});
// 从 JSON 解析
factory Homework.fromJson(Map<String, dynamic> json) {
return Homework(
id: json['id'],
title: json['title'],
courseName: json['courseName'],
description: json['description'],
deadline: DateTime.parse(json['deadline']),
isCompleted: json['isCompleted'] ?? false,
priority: json['priority'] ?? 2,
);
}
// 转换为 JSON
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'courseName': courseName,
'description': description,
'deadline': deadline.toIso8601String(),
'isCompleted': isCompleted,
'priority': priority,
};
}
// 复制并修改
Homework copyWith({
String? id,
String? title,
String? courseName,
String? description,
DateTime? deadline,
bool? isCompleted,
int? priority,
}) {
return Homework(
id: id ?? this.id,
title: title ?? this.title,
courseName: courseName ?? this.courseName,
description: description ?? this.description,
deadline: deadline ?? this.deadline,
isCompleted: isCompleted ?? this.isCompleted,
priority: priority ?? this.priority,
);
}
}
3.2 作业卡片组件 (lib/widgets/homework_card.dart)
支持完成状态切换、编辑和删除操作:
class HomeworkCard extends StatelessWidget {
final Homework homework;
final VoidCallback onToggleComplete;
final VoidCallback onEdit;
final VoidCallback onDelete;
const HomeworkCard({
super.key,
required this.homework,
required this.onToggleComplete,
required this.onEdit,
required this.onDelete,
});
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 8)],
),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: onEdit,
child: Container(
padding: const EdgeInsets.all(16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Checkbox(value: homework.isCompleted, onChanged: (_) => onToggleComplete(), activeColor: Colors.green),
Expanded(child: _buildContent()),
PopupMenuButton<String>(
onSelected: (value) => value == 'edit' ? onEdit() : onDelete(),
itemBuilder: (context) => const [
PopupMenuItem(value: 'edit', child: Row(children: [Icon(Icons.edit, size: 18), SizedBox(width: 8), Text('编辑')])),
PopupMenuItem(value: 'delete', child: Row(children: [Icon(Icons.delete, size: 18, color: Colors.red), SizedBox(width: 8), Text('删除', style: TextStyle(color: Colors.red))])),
],
),
],
),
),
),
),
);
}
Widget _buildContent() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Expanded(child: Text(homework.title, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, decoration: homework.isCompleted ? TextDecoration.lineThrough : null))),
_buildPriorityChip(),
]),
const SizedBox(height: 4),
Row(children: [Icon(Icons.book, size: 14, color: Colors.grey[600]), const SizedBox(width: 4), Text(homework.courseName, style: TextStyle(fontSize: 13))]),
if (homework.description.isNotEmpty) ...[
const SizedBox(height: 4),
Text(homework.description, style: TextStyle(fontSize: 13, color: Colors.grey[600]), maxLines: 2, overflow: TextOverflow.ellipsis),
],
const SizedBox(height: 4),
Row(children: [Icon(Icons.event, size: 14, color: _getDeadlineColor()), const SizedBox(width: 4), Text('截止:${_formatDate(homework.deadline)}', style: TextStyle(fontSize: 12, color: _getDeadlineColor()))]),
],
);
}
Widget _buildPriorityChip() {
final colors = {1: Colors.red, 2: Colors.orange, 3: Colors.green};
final labels = {1: '高', 2: '中', 3: '低'};
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(color: colors[homework.priority]?.withOpacity(0.1), borderRadius: BorderRadius.circular(12)),
child: Text(labels[homework.priority]!, style: TextStyle(fontSize: 11, color: colors[homework.priority])),
);
}
Color _getDeadlineColor() {
if (homework.isCompleted) return Colors.grey;
if (homework.deadline.isBefore(DateTime.now())) return Colors.red;
if (homework.deadline.difference(DateTime.now()).inDays <= 2) return Colors.orange;
return Colors.grey;
}
String _formatDate(DateTime date) => '${date.month}/${date.day} ${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}';
}
3.3 添加/编辑作业弹窗 (lib/widgets/add_homework_dialog.dart)
支持作业标题、所属课程、描述、截止日期、优先级:
class AddHomeworkDialog extends StatefulWidget {
final Homework? homework;
const AddHomeworkDialog({super.key, this.homework});
State<AddHomeworkDialog> createState() => _AddHomeworkDialogState();
}
class _AddHomeworkDialogState extends State<AddHomeworkDialog> {
final _formKey = GlobalKey<FormState>();
late String _title, _courseName, _description;
late DateTime _deadline;
late int _priority;
void initState() {
super.initState();
if (widget.homework != null) {
_title = widget.homework!.title;
_courseName = widget.homework!.courseName;
_description = widget.homework!.description;
_deadline = widget.homework!.deadline;
_priority = widget.homework!.priority;
} else {
_title = _courseName = _description = '';
_deadline = DateTime.now().add(const Duration(days: 7));
_priority = 2;
}
}
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container(
padding: const EdgeInsets.all(20),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(widget.homework == null ? '添加作业' : '编辑作业', style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
TextFormField(decoration: const InputDecoration(labelText: '作业标题'), validator: (v) => v?.isEmpty == true ? '请输入标题' : null, onSaved: (v) => _title = v!),
const SizedBox(height: 16),
DropdownButtonFormField<String>(
decoration: const InputDecoration(labelText: '所属课程'),
items: ['高等数学', '大学英语', '程序设计', '数据结构', '体育'].map((c) => DropdownMenuItem(value: c, child: Text(c))).toList(),
onChanged: (v) => setState(() => _courseName = v!),
validator: (v) => v == null ? '请选择课程' : null,
),
const SizedBox(height: 16),
TextFormField(decoration: const InputDecoration(labelText: '作业描述'), maxLines: 3, onSaved: (v) => _description = v!),
const SizedBox(height: 16),
ListTile(
tileColor: Colors.grey.shade50, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
title: const Text('截止日期'),
trailing: Text('${_deadline.month}/${_deadline.day} ${_deadline.hour}:${_deadline.minute}'),
onTap: () async {
final date = await showDatePicker(context: context, initialDate: _deadline, firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 365)));
if (date != null) {
final time = await showTimePicker(context: context, initialTime: TimeOfDay.fromDateTime(_deadline));
if (time != null) setState(() => _deadline = DateTime(date.year, date.month, date.day, time.hour, time.minute));
}
},
),
const SizedBox(height: 16),
SegmentedButton<int>(
segments: const [ButtonSegment(value: 1, label: Text('高')), ButtonSegment(value: 2, label: Text('中')), ButtonSegment(value: 3, label: Text('低'))],
selected: {_priority},
onSelectionChanged: (set) => setState(() => _priority = set.first),
),
const SizedBox(height: 24),
Row(mainAxisAlignment: MainAxisAlignment.end, children: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')),
ElevatedButton(onPressed: _submit, child: Text(widget.homework == null ? '添加' : '保存')),
]),
],
),
),
),
),
);
}
void _submit() {
if (_formKey.currentState!.validate()) {
_formKey.currentState!.save();
Navigator.pop(context, Homework(
id: widget.homework?.id ?? DateTime.now().millisecondsSinceEpoch.toString(),
title: _title, courseName: _courseName, description: _description, deadline: _deadline,
isCompleted: widget.homework?.isCompleted ?? false, priority: _priority,
));
}
}
}
3.4 作业主页 (lib/pages/homework_page.dart)
class HomeworkPage extends StatefulWidget {
const HomeworkPage({super.key});
State<HomeworkPage> createState() => _HomeworkPageState();
}
class _HomeworkPageState extends State<HomeworkPage> {
List<Homework> _homeworks = [];
int _selectedFilter = 0; // 0-全部 1-未完成 2-已完成
final List<Homework> _initialHomeworks = [
Homework(id: '1', title: '高等数学作业', courseName: '高等数学', description: '完成课本第45-50页习题', deadline: DateTime.now().add(const Duration(days: 5)), priority: 1),
Homework(id: '2', title: '程序设计实验报告', courseName: '程序设计', description: '完成学生管理系统界面设计', deadline: DateTime.now().add(const Duration(days: 3)), priority: 2),
Homework(id: '3', title: '英语作文', courseName: '大学英语', description: '写一篇关于环保的作文', deadline: DateTime.now().subtract(const Duration(days: 1)), priority: 1),
];
void initState() {
super.initState();
_homeworks = List.from(_initialHomeworks);
}
Widget build(BuildContext context) {
final filteredHomeworks = _getFilteredHomeworks();
return Column(
children: [
_buildFilterChips(),
_buildStatsBar(),
Expanded(
child: filteredHomeworks.isEmpty
? const Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [Icon(Icons.assignment_turned_in, size: 64, color: Colors.grey), Text('暂无作业')]))
: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: filteredHomeworks.length,
itemBuilder: (context, index) => HomeworkCard(
homework: filteredHomeworks[index],
onToggleComplete: () => _toggleComplete(filteredHomeworks[index].id),
onEdit: () => _editHomework(filteredHomeworks[index]),
onDelete: () => _deleteHomework(filteredHomeworks[index].id),
),
),
),
],
);
}
Widget _buildFilterChips() {
return Container(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row(children: [
_buildFilterChip('全部', 0), const SizedBox(width: 8),
_buildFilterChip('未完成', 1), const SizedBox(width: 8),
_buildFilterChip('已完成', 2),
]));
}
Widget _buildFilterChip(String label, int index) => FilterChip(
label: Text(label), selected: _selectedFilter == index,
onSelected: (_) => setState(() => _selectedFilter = index),
backgroundColor: Colors.grey.shade200, selectedColor: Colors.blue.shade100,
);
Widget _buildStatsBar() {
final total = _homeworks.length, completed = _homeworks.where((h) => h.isCompleted).length;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12)),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
_buildStatItem('总作业', total, Colors.blue), _buildStatItem('已完成', completed, Colors.green), _buildStatItem('未完成', total - completed, Colors.orange),
]),
);
}
Widget _buildStatItem(String label, int count, Color color) => Column(children: [
Text('$count', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: color)),
Text(label, style: TextStyle(fontSize: 12, color: Colors.grey[600])),
]);
List<Homework> _getFilteredHomeworks() {
switch (_selectedFilter) {
case 1: return _homeworks.where((h) => !h.isCompleted).toList();
case 2: return _homeworks.where((h) => h.isCompleted).toList();
default: return _homeworks;
}
}
void _addHomework() async {
final result = await showDialog<Homework>(context: context, builder: (context) => const AddHomeworkDialog());
if (result != null) setState(() => _homeworks.add(result));
}
void _editHomework(Homework homework) async {
final result = await showDialog<Homework>(context: context, builder: (context) => AddHomeworkDialog(homework: homework));
if (result != null) setState(() => _homeworks[_homeworks.indexWhere((h) => h.id == homework.id)] = result);
}
void _deleteHomework(String id) {
showDialog(context: context, builder: (context) => AlertDialog(
title: const Text('删除作业'), content: const Text('确定要删除这个作业吗?'),
actions: [
TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')),
TextButton(onPressed: () => setState(() => _homeworks.removeWhere((h) => h.id == id)) || Navigator.pop(context), child: const Text('删除', style: TextStyle(color: Colors.red))),
],
));
}
void _toggleComplete(String id) => setState(() {
final index = _homeworks.indexWhere((h) => h.id == id);
if (index != -1) _homeworks[index] = _homeworks[index].copyWith(isCompleted: !_homeworks[index].isCompleted);
});
}
四、本日成果
| 成果 | 说明 |
|---|---|
| ✅ 作业数据模型 | 定义 Homework 类,包含标题、课程、描述、截止日期、优先级、完成状态 |
| ✅ 作业卡片组件 | 展示作业详情,支持复选框、优先级标签、截止日期颜色提示 |
| ✅ 添加作业 | 底部浮动按钮,弹窗表单填写 |
| ✅ 编辑作业 | 点击卡片或菜单编辑,修改后实时更新 |
| ✅ 删除作业 | 菜单删除,带确认对话框 |
| ✅ 筛选功能 | 全部/未完成/已完成 三种筛选 |
| ✅ 统计信息 | 显示总作业数、已完成数、未完成数 |
| ✅ 完成状态 | 复选框切换,已完成作业划线显示 |
| ✅ 截止日期智能提示 | 过期显示红色,2天内显示橙色 |
五、交互流程图
┌─────────────────────────────────────────────────────────────────────┐
│ 作业页交互流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 底部➕按钮 ──→ 添加作业弹窗 ──→ 填写表单 ──→ 保存 ──→ 作业列表新增 │
│ │
│ 点击作业卡片 ──→ 编辑作业弹窗 ──→ 修改表单 ──→ 保存 ──→ 列表更新 │
│ │
│ 三点菜单 ──→ 编辑/删除 │
│ │ │
│ └── 删除 ──→ 确认对话框 ──→ 确认 ──→ 列表移除 │
│ │
│ 复选框 ──→ 切换完成状态 ──→ 卡片样式更新(划线/颜色变化) │
│ │
│ 筛选标签 ──→ 全部/未完成/已完成 ──→ 作业列表筛选显示 │
│ │
└─────────────────────────────────────────────────────────────────────┘
六、运行验证
flutter run
| 预期效果 | 状态 |
|---|---|
| 作业页显示作业列表 | ✅ |
| 顶部显示筛选标签和统计信息 | ✅ |
| 底部显示浮动添加按钮 | ✅ |
| 点击➕按钮可添加作业 | ✅ |
| 点击复选框可切换完成状态 | ✅ |
| 点击卡片可编辑作业 | ✅ |
| 点击三点菜单可编辑/删除 | ✅ |
| 删除时弹出确认对话框 | ✅ |
| 筛选标签可切换显示不同状态作业 | ✅ |
| 截止日期临近/过期有颜色提示 | ✅ |

七、下一步计划
| 任务 | 优先级 |
|---|---|
| 数据持久化(保存到本地) | 高 |
| 个人页面完善 | 中 |
| 作业提醒通知 | 中 |
| 课表导入(拍照识别OCR) | 低 |
本日完成:作业管理页面完整实现,支持增删改查、筛选统计、完成状态管理。项目核心功能已基本具备,下一步将实现数据持久化。
更多推荐

所有评论(0)