feat:初始化工程

This commit is contained in:
2026-04-25 21:25:02 +08:00
commit 57c6e552d4
37 changed files with 2701 additions and 0 deletions

105
lib/main.dart Normal file
View File

@@ -0,0 +1,105 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:task_hub/pages/home_page.dart';
import 'package:task_hub/pages/schedule_page.dart';
import 'package:task_hub/pages/settings_page.dart';
import 'package:task_hub/pages/task_page.dart';
void main() {
runApp(const TaskHubApp());
}
class TaskHubApp extends StatelessWidget {
const TaskHubApp({super.key});
@override
Widget build(BuildContext context) {
return FluentApp(
title: 'TaskHub - 待办清单',
themeMode: ThemeMode.system,
theme: FluentThemeData(
fontFamily: 'CustomFont',
),
debugShowCheckedModeBanner: false,
home: const MainLayout(),
);
}
}
class MainLayout extends StatefulWidget {
const MainLayout({super.key});
@override
State<MainLayout> createState() => _MainLayoutState();
}
class _MainLayoutState extends State<MainLayout> {
int _currentIndex = 0;
@override
Widget build(BuildContext context) {
return NavigationView(
pane: NavigationPane(
selected: _currentIndex,
onChanged: (index) => setState(() => _currentIndex = index),
size: NavigationPaneSize(
openWidth: 150
),
header: const Padding(
padding: EdgeInsets.all(10),
child: Text(
'TaskHub',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
items: [
PaneItem(
icon: const Icon(FluentIcons.home),
title: const Text('首页'),
body: Container(
color: Colors.white,
child: const HomePage(),
),
),
PaneItem(
icon: const Icon(FluentIcons.check_list),
title: const Text('待办'),
body: Container(
color: Colors.white,
child: const TaskPage(),
),
),
PaneItem(
icon: const Icon(FluentIcons.calendar),
title: const Text('日程'),
body: Container(
color: Colors.white,
child: const SchedulePage(),
),
),
PaneItemSeparator(),
PaneItem(
icon: const Icon(FluentIcons.settings),
title: const Text('设置'),
body: Container(
color: Colors.white,
child: const SettingsPage(),
),
),
],
footerItems: [
PaneItemSeparator(),
PaneItemAction(
icon: const Icon(FluentIcons.add),
title: const Text('添加任务'),
onTap: () {
// 添加任务的逻辑
},
),
],
),
);
}
}

24
lib/models/task.dart Normal file
View File

@@ -0,0 +1,24 @@
class Task {
final String id;
String title;
String? description;
DateTime createdAt;
DateTime? dueDate;
bool isCompleted;
Priority priority;
String? category;
Task({
String? id,
required this.title,
this.description,
DateTime? createdAt,
this.dueDate,
this.isCompleted = false,
this.priority = Priority.medium,
this.category,
}) : id = id ?? DateTime.timestamp().microsecond.toString(),
createdAt = createdAt ?? DateTime.now();
}
enum Priority { low, medium, high }

31
lib/notice.dart Normal file
View File

@@ -0,0 +1,31 @@
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
// 初始化通知
Future<void> _initNotify() async {
final notify = FlutterLocalNotificationsPlugin();
await notify.initialize(
const InitializationSettings(
windows: WindowsInitializationSettings(
appName: 'TaskHub',
appUserModelId: 'com.cxx.task',
guid: 'f47ac10b-58cc-4372-a567-0e02b2c3d479',
iconPath: 'assets/icons/task.png'
),
),
);
}
// 发送通知
Future<void> _showNotify(String title, String text) async {
final notify = FlutterLocalNotificationsPlugin();
final id = DateTime.now().millisecondsSinceEpoch.remainder(10000);
await notify.show(
id,
title,
text,
const NotificationDetails(
windows: WindowsNotificationDetails(),
),
);
}

32
lib/pages/home_page.dart Normal file
View File

@@ -0,0 +1,32 @@
import 'package:fluent_ui/fluent_ui.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return ScaffoldPage.withPadding(
header: const PageHeader(
title: Text('首页'),
),
content: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
FluentIcons.home,
size: 64,
),
const SizedBox(height: 20),
Text(
'首页',
style: TextStyle(
fontSize: 24,
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,117 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/material.dart' show ButtonSegment, SegmentedButton;
import 'package:syncfusion_flutter_calendar/calendar.dart' as calendar;
class AppointmentDataSource extends calendar.CalendarDataSource {
AppointmentDataSource(List<calendar.Appointment> source) {
appointments = source;
}
}
class SchedulePage extends StatefulWidget {
const SchedulePage({super.key});
@override
State<SchedulePage> createState() => _SchedulePageState();
}
class _SchedulePageState extends State<SchedulePage> {
final calendar.CalendarController _calendarController = calendar.CalendarController();
late List<calendar.Appointment> _appointments = [];
String _viewType = 'week';
@override
Widget build(BuildContext context) {
return ScaffoldPage.withPadding(
header: const PageHeader(title: Text('日程安排')),
content: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SegmentedButton<String>(
showSelectedIcon: false,
selected: <String>{_viewType},
onSelectionChanged: (Set<String> value) {
setState(() {
_viewType = value.first;
switch(_viewType) {
case 'week':
_calendarController.view = calendar.CalendarView.week;
break;
case 'month':
_calendarController.view = calendar.CalendarView.month;
break;
case 'schedule':
_calendarController.view = calendar.CalendarView.schedule;
break;
}
});
},
segments: const [
ButtonSegment(
value: 'week',
label: Text('周视图', style: TextStyle(fontFamily: 'CustomFont')),
),
ButtonSegment(
value: 'month',
label: Text(
'月视图',
style: TextStyle(fontFamily: 'CustomFont'),
),
),
ButtonSegment(
value: 'schedule',
label: Text(
'日程视图',
style: TextStyle(fontFamily: 'CustomFont'),
),
),
],
),
const SizedBox(height: 20),
Expanded(
child: calendar.SfCalendar(
view: calendar.CalendarView.week,
controller: _calendarController,
backgroundColor: Colors.grey[20],
headerStyle: calendar.CalendarHeaderStyle(
backgroundColor: Colors.grey[20],
),
dataSource: AppointmentDataSource(_appointments),
firstDayOfWeek: 1,
showDatePickerButton: true,
showNavigationArrow: true,
showTodayButton: true,
allowViewNavigation: true,
// 月份视图设置
monthViewSettings: calendar.MonthViewSettings(
appointmentDisplayMode:
calendar.MonthAppointmentDisplayMode.appointment,
showAgenda: true,
),
scheduleViewSettings: calendar.ScheduleViewSettings(
monthHeaderSettings: calendar.MonthHeaderSettings(
backgroundColor: Colors.grey[20],
height: 85,
),
),
// 时间区域设置
timeSlotViewSettings: calendar.TimeSlotViewSettings(
startHour: 7,
endHour: 23,
timeFormat: 'HH:mm',
timeInterval: Duration(minutes: 30),
timeRulerSize: 60,
),
// 选择日期回调
onTap: (calendar.CalendarTapDetails details) {},
// 选择日程回调
onLongPress: (calendar.CalendarLongPressDetails details) async {},
),
),
],
),
),
);
}
}

View File

@@ -0,0 +1,32 @@
import 'package:fluent_ui/fluent_ui.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
return ScaffoldPage.withPadding(
header: const PageHeader(
title: Text('设置'),
),
content: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
FluentIcons.settings,
size: 64,
),
const SizedBox(height: 20),
Text(
'设置',
style: TextStyle(
fontSize: 24,
),
),
],
),
),
);
}
}

285
lib/pages/task_page.dart Normal file
View File

@@ -0,0 +1,285 @@
import 'dart:ui';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter/material.dart' show SegmentedButton, ButtonSegment;
import 'package:task_hub/models/task.dart';
import 'package:task_hub/widges/task_dialog.dart';
import 'package:task_hub/widges/task_item.dart';
// 任务页面
class TaskPage extends StatefulWidget {
const TaskPage({super.key});
@override
State<TaskPage> createState() => _TaskPageState();
}
class _TaskPageState extends State<TaskPage> {
final List<Task> _tasks = [];
String _filter = 'all'; // all, active, completed
String _sortBy = 'created'; // created, due, priority
List<Task> get _filteredTasks {
List<Task> tasks =
_tasks.where((task) {
if (_filter == 'active') return !task.isCompleted;
if (_filter == 'completed') return task.isCompleted;
return true;
}).toList();
tasks.sort((a, b) {
switch (_sortBy) {
case 'due':
if (a.dueDate == null && b.dueDate == null) return 0;
if (a.dueDate == null) return 1;
if (b.dueDate == null) return -1;
return a.dueDate!.compareTo(b.dueDate!);
case 'priority':
return b.priority.index.compareTo(a.priority.index);
case 'created':
default:
return b.createdAt.compareTo(a.createdAt);
}
});
return tasks;
}
void _addTask() {
showDialog(
context: context,
builder:
(context) => TaskDialog(
onSave: (task) {
setState(() {
_tasks.add(task);
});
},
),
);
}
void _editTask(Task task) {
showDialog(
context: context,
builder:
(context) => TaskDialog(
task: task,
onSave: (editedTask) {
setState(() {
final index = _tasks.indexWhere((t) => t.id == task.id);
if (index != -1) {
_tasks[index] = editedTask;
}
});
},
),
);
}
void _deleteTask(String taskId) {
setState(() {
_tasks.removeWhere((task) => task.id == taskId);
});
}
void _toggleTaskComplete(Task task) {
setState(() {
final index = _tasks.indexWhere((t) => t.id == task.id);
if (index != -1) {
_tasks[index] = Task(
id: task.id,
title: task.title,
description: task.description,
createdAt: task.createdAt,
dueDate: task.dueDate,
isCompleted: !task.isCompleted,
priority: task.priority,
category: task.category,
);
}
});
}
void _updateTaskPriority(String taskId, Priority priority) {
setState(() {
final index = _tasks.indexWhere((task) => task.id == taskId);
if (index != -1) {
_tasks[index] = Task(
id: _tasks[index].id,
title: _tasks[index].title,
description: _tasks[index].description,
createdAt: _tasks[index].createdAt,
dueDate: _tasks[index].dueDate,
isCompleted: _tasks[index].isCompleted,
priority: priority,
category: _tasks[index].category,
);
}
});
}
@override
Widget build(BuildContext context) {
return ScaffoldPage.withPadding(
header: PageHeader(
title: const Text('待办任务'),
commandBar: CommandBar(
mainAxisAlignment: MainAxisAlignment.end,
primaryItems: [
CommandBarButton(
icon: const Icon(FluentIcons.add),
label: const Text('添加任务'),
onPressed: _addTask,
),
],
),
),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildStatsWidget(),
const SizedBox(height: 16),
_buildFilterWidget(),
const SizedBox(height: 16),
Expanded(
child: _tasks.isEmpty ? _buildEmptyTask() : _buildTaskList(),
),
],
),
);
}
Widget _buildStatsWidget() {
final completedCount = _tasks.where((task) => task.isCompleted).length;
final activeCount = _tasks.length - completedCount;
return Card(
backgroundColor: Colors.grey[20],
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildStatsItem('全部', '${_tasks.length}'),
_buildStatsItem('进行中', '$activeCount'),
_buildStatsItem('已完成', '$completedCount'),
],
),
),
);
}
Widget _buildStatsItem(String label, String value) {
return Column(
children: [
Text(
value,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(height: 4),
Text(label, style: TextStyle(color: Colors.grey)),
],
);
}
Widget _buildFilterWidget() {
return Card(
borderColor: Colors.white,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
children: [
const Text('筛选:'),
const SizedBox(width: 8),
SegmentedButton<String>(
showSelectedIcon: false,
selected: <String>{_filter},
onSelectionChanged: (Set<String> value) {
setState(() {
_filter = value.first;
});
},
segments: const [
ButtonSegment(
value: 'all',
label: Text('全部', style: TextStyle(fontFamily: 'CustomFont')),
),
ButtonSegment(
value: 'active',
label: Text(
'进行中',
style: TextStyle(fontFamily: 'CustomFont'),
),
),
ButtonSegment(
value: 'completed',
label: Text(
'已完成',
style: TextStyle(fontFamily: 'CustomFont'),
),
),
],
),
const Spacer(),
const Text('排序:'),
const SizedBox(width: 8),
ComboBox<String>(
value: _sortBy,
items: const [
ComboBoxItem(value: 'created', child: Text('创建时间')),
ComboBoxItem(value: 'due', child: Text('截止日期')),
ComboBoxItem(value: 'priority', child: Text('优先级')),
],
onChanged: (value) {
if (value != null) {
setState(() {
_sortBy = value;
});
}
},
),
],
),
),
);
}
Widget _buildEmptyTask() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(FluentIcons.check_list, size: 64, color: Colors.blue),
const SizedBox(height: 20),
const Text(
'还没有任务',
style: TextStyle(fontSize: 20, color: Colors.grey),
),
const SizedBox(height: 10),
Text('点击右上角的"添加任务"按钮开始', style: TextStyle(color: Colors.grey)),
],
),
);
}
Widget _buildTaskList() {
return ListView.builder(
itemCount: _filteredTasks.length,
itemBuilder: (context, index) {
final task = _filteredTasks[index];
return Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: TaskItem(
task: task,
onToggleComplete: () => _toggleTaskComplete(task),
onEdit: () => _editTask(task),
onDelete: () => _deleteTask(task.id),
onPriorityChanged:
(priority) => _updateTaskPriority(task.id, priority),
),
);
},
);
}
}

211
lib/widges/task_dialog.dart Normal file
View File

@@ -0,0 +1,211 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:task_hub/models/task.dart';
class TaskDialog extends StatefulWidget {
final Task? task;
final ValueChanged<Task> onSave;
const TaskDialog({super.key, this.task, required this.onSave});
@override
State<TaskDialog> createState() => _TaskDialogState();
}
class _TaskDialogState extends State<TaskDialog> {
late TextEditingController _titleController;
late TextEditingController _descriptionController;
late Priority _selectedPriority;
DateTime? _selectedDate;
final _formKey = GlobalKey<FormState>();
@override
void initState() {
super.initState();
_titleController = TextEditingController(text: widget.task?.title ?? '');
_descriptionController = TextEditingController(
text: widget.task?.description ?? '',
);
_selectedPriority = widget.task?.priority ?? Priority.medium;
_selectedDate = widget.task?.dueDate;
}
@override
void dispose() {
_titleController.dispose();
_descriptionController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ContentDialog(
title: Text(widget.task == null ? '添加任务' : '编辑任务'),
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 标题
_buildFormRow(
label: '任务标题',
required: true,
child: TextFormBox(
controller: _titleController,
placeholder: '请输入任务标题',
validator: (value) {
if (value == null || value.isEmpty) {
return '任务标题不能为空';
}
return null;
},
),
),
const SizedBox(height: 16),
// 描述
_buildFormRow(
label: '任务描述',
child: TextFormBox(
controller: _descriptionController,
placeholder: '请输入任务描述',
maxLines: 3,
),
),
const SizedBox(height: 16),
// 优先级
_buildFormRow(
label: '优先级',
child: RadioGroup<Priority>(
groupValue: _selectedPriority,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RadioButton<Priority>(
value: Priority.low,
content: Text(''),
),
RadioButton<Priority>(
value: Priority.medium,
content: Text(''),
),
RadioButton<Priority>(
value: Priority.high,
content: Text(''),
)
],
),
onChanged: (value) {
if (value != null) {
setState(() {
_selectedPriority = value;
});
}
},
),
),
const SizedBox(height: 16),
// 截止日期
_buildFormRow(
label: '截止日期',
child: DatePicker(
selected: _selectedDate,
onChanged: (date) {
setState(() {
_selectedDate = date;
});
},
),
),
],
),
),
actions: [
Button(
child: const Text('取消'),
onPressed: () => Navigator.pop(context),
),
FilledButton(
child: const Text('保存'),
onPressed: () {
if (_formKey.currentState!.validate()) {
final task = Task(
id: widget.task?.id,
title: _titleController.text,
description: _descriptionController.text.isEmpty
? null
: _descriptionController.text,
dueDate: _selectedDate,
priority: _selectedPriority,
isCompleted: widget.task?.isCompleted ?? false,
);
widget.onSave(task);
Navigator.pop(context);
}
},
),
],
);
}
// 构建表单行
Widget _buildFormRow({
required String label,
required Widget child,
bool required = false,
}) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 80,
child: Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (required)
Padding(
padding: EdgeInsets.only(right: 4),
child: Text(
'*',
style: TextStyle(color: Colors.red, fontSize: 16),
),
),
Text(
label,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
const Text(
':',
style: TextStyle(fontSize: 14),
),
],
),
),
),
const SizedBox(width: 12),
// 右侧表单控件
Expanded(
child: child,
),
],
);
}
String _getPriorityLabel(Priority priority) {
switch (priority) {
case Priority.low:
return '';
case Priority.medium:
return '';
case Priority.high:
return '';
}
}
}

151
lib/widges/task_item.dart Normal file
View File

@@ -0,0 +1,151 @@
import 'package:fluent_ui/fluent_ui.dart';
import 'package:task_hub/models/task.dart';
class TaskItem extends StatelessWidget {
final Task task;
final VoidCallback onToggleComplete;
final VoidCallback onEdit;
final VoidCallback onDelete;
final ValueChanged<Priority> onPriorityChanged;
const TaskItem({
super.key,
required this.task,
required this.onToggleComplete,
required this.onEdit,
required this.onDelete,
required this.onPriorityChanged,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
children: [
// 完成状态复选框
Checkbox(
checked: task.isCompleted,
onChanged: (value) => onToggleComplete(),
),
const SizedBox(width: 12),
// 任务信息
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
// 优先级指示器
_buildPriorityIndicator(task.priority),
const SizedBox(width: 8),
Expanded(
child: Text(
task.title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
decoration:
task.isCompleted
? TextDecoration.lineThrough
: null,
color: task.isCompleted ? Colors.grey : Colors.red,
),
),
),
],
),
const SizedBox(height: 2),
if (task.description != null && task.description!.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Text(
task.description!,
style: TextStyle(
color: Colors.grey,
fontSize: 14,
decoration:
task.isCompleted
? TextDecoration.lineThrough
: null,
),
),
),
const SizedBox(height: 2),
if (task.dueDate != null)
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
const Icon(
FluentIcons.calendar,
size: 12,
color: Colors.grey,
),
const SizedBox(width: 4),
Text(
'截止: ${_formatDate(task.dueDate!)}',
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
),
),
],
),
),
],
),
),
// 操作按钮
Row(
children: [
// 编辑按钮
IconButton(
icon: const Icon(FluentIcons.edit, size: 16),
onPressed: onEdit,
),
// 删除按钮
IconButton(
icon: const Icon(FluentIcons.delete, size: 16),
onPressed: onDelete,
),
],
),
],
),
),
);
}
Widget _buildPriorityIndicator(Priority priority) {
final Map<Priority, (Color, String)> priorityInfo = {
Priority.low: (Colors.grey, ''),
Priority.medium: (Colors.green, ''),
Priority.high: (Colors.red, ''),
};
final (color, label) = priorityInfo[priority]!;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(4),
),
child: Center(
child: Text(
label,
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
);
}
String _formatDate(DateTime date) {
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
}
}