feat:初始化工程
This commit is contained in:
105
lib/main.dart
Normal file
105
lib/main.dart
Normal 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
24
lib/models/task.dart
Normal 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
31
lib/notice.dart
Normal 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
32
lib/pages/home_page.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
117
lib/pages/schedule_page.dart
Normal file
117
lib/pages/schedule_page.dart
Normal 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 {},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
32
lib/pages/settings_page.dart
Normal file
32
lib/pages/settings_page.dart
Normal 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
285
lib/pages/task_page.dart
Normal 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
211
lib/widges/task_dialog.dart
Normal 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
151
lib/widges/task_item.dart
Normal 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')}';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user