# 日历事件创建功能实现计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 在日历月视图和日视图右上角添加 + 号图标,点击后弹出底部表单创建日历事件,创建完成后在视图中正确显示,并可点击进入详情页查看和编辑。 **Architecture:** - 使用 Mock Service 模式存储日历事件(参考 mock_history_service.dart) - 底部弹窗表单使用 showModalBottomSheet - 日视图使用时间线布局显示事件 **Tech Stack:** Flutter, BLoC, go_router, lucide_icons --- ## Task 1: 创建日历事件数据模型 **Files:** - Create: `apps/lib/features/calendar/data/models/schedule_item_model.dart` **Step 1: 创建数据模型文件** ```dart import 'package:flutter/material.dart'; enum ScheduleSourceType { manual, imported, agentGenerated } enum ScheduleStatus { active, completed, canceled, archived } class ScheduleItemModel { final String id; final String title; final String? description; final DateTime startAt; final DateTime? endAt; final String timezone; final ScheduleMetadata? metadata; final ScheduleSourceType sourceType; final ScheduleStatus status; final DateTime createdAt; ScheduleItemModel({ required this.id, required this.title, this.description, required this.startAt, this.endAt, this.timezone = 'Asia/Shanghai', this.metadata, this.sourceType = ScheduleSourceType.manual, this.status = ScheduleStatus.active, DateTime? createdAt, }) : createdAt = createdAt ?? DateTime.now(); ScheduleItemModel copyWith({ String? id, String? title, String? description, DateTime? startAt, DateTime? endAt, String? timezone, ScheduleMetadata? metadata, ScheduleSourceType? sourceType, ScheduleStatus? status, DateTime? createdAt, }) { return ScheduleItemModel( id: id ?? this.id, title: title ?? this.title, description: description ?? this.description, startAt: startAt ?? this.startAt, endAt: endAt ?? this.endAt, timezone: timezone ?? this.timezone, metadata: metadata ?? this.metadata, sourceType: sourceType ?? this.sourceType, status: status ?? this.status, createdAt: createdAt ?? this.createdAt, ); } } class ScheduleMetadata { final String? color; final String? location; final String? notes; final List? attachments; ScheduleMetadata({ this.color, this.location, this.notes, this.attachments, }); ScheduleMetadata copyWith({ String? color, String? location, String? notes, List? attachments, }) { return ScheduleMetadata( color: color ?? this.color, location: location ?? this.location, notes: notes ?? this.notes, attachments: attachments ?? this.attachments, ); } } class Attachment { final String name; final String? url; final String? content; final String type; Attachment({ required this.name, this.url, this.content, this.type = 'document', }); } const defaultColors = [ Color(0xFF3B82F6), // 蓝色 Color(0xFF8B5CF6), // 紫色 Color(0xFF10B981), // 绿色 Color(0xFFF59E0B), // 黄色 Color(0xFFEF4444), // 红色 ]; ``` **Step 2: 提交** ```bash git add apps/lib/features/calendar/data/models/schedule_item_model.dart git commit -m "feat(calendar): 添加日历事件数据模型" ``` --- ## Task 2: 创建 Mock Calendar Service **Files:** - Create: `apps/lib/features/calendar/data/services/mock_calendar_service.dart` **Step 1: 创建 Mock Service** ```dart import 'package:Env/Env.dart'; import '../models/schedule_item_model.dart'; class MockCalendarService { static final MockCalendarService _instance = MockCalendarService._internal(); factory MockCalendarService() => _instance; MockCalendarService._internal(); final List _events = []; List get events => List.unmodifiable(_events); List getEventsForDay(DateTime date) { final dateOnly = DateTime(date.year, date.month, date.day); return _events.where((event) { final eventDate = DateTime( event.startAt.year, event.startAt.month, event.startAt.day, ); return eventDate == dateOnly && event.status == ScheduleStatus.active; }).toList() ..sort((a, b) => a.startAt.compareTo(b.startAt)); } List getEventsForRange(DateTime start, DateTime end) { return _events.where((event) { return event.startAt.isAfter(start.subtract(const Duration(days: 1))) && event.startAt.isBefore(end.add(const Duration(days: 1))) && event.status == ScheduleStatus.active; }).toList() ..sort((a, b) => a.startAt.compareTo(b.startAt)); } ScheduleItemModel? getEventById(String id) { try { return _events.firstWhere((e) => e.id == id); } catch (_) { return null; } } void addEvent(ScheduleItemModel event) { _events.add(event); } void updateEvent(ScheduleItemModel event) { final index = _events.indexWhere((e) => e.id == event.id); if (index >= 0) { _events[index] = event; } } void deleteEvent(String id) { _events.removeWhere((e) => e.id == id); } } class CalendarService { static final CalendarService _instance = CalendarService._internal(); factory CalendarService() => _instance; CalendarService._internal(); MockCalendarService get _mock => MockCalendarService(); List getEventsForDay(DateTime date) { if (Env.isMockApi) { return _mock.getEventsForDay(date); } throw UnimplementedError('Real API not implemented'); } List getEventsForRange(DateTime start, DateTime end) { if (Env.isMockApi) { return _mock.getEventsForRange(start, end); } throw UnimplementedError('Real API not implemented'); } ScheduleItemModel? getEventById(String id) { if (Env.isMockApi) { return _mock.getEventById(id); } throw UnimplementedError('Real API not implemented'); } void addEvent(ScheduleItemModel event) { if (Env.isMockApi) { _mock.addEvent(event); return; } throw UnimplementedError('Real API not implemented'); } void updateEvent(ScheduleItemModel event) { if (Env.isMockApi) { _mock.updateEvent(event); return; } throw UnimplementedError('Real API not implemented'); } void deleteEvent(String id) { if (Env.isMockApi) { _mock.deleteEvent(id); return; } throw UnimplementedError('Real API not implemented'); } } ``` **Step 2: 提交** ```bash git add apps/lib/features/calendar/data/services/mock_calendar_service.dart git commit -m "feat(calendar): 添加 Mock Calendar Service" ``` --- ## Task 3: 创建日历事件创建底部弹窗 **Files:** - Create: `apps/lib/features/calendar/ui/widgets/create_event_sheet.dart` **Step 1: 创建底部弹窗组件** ```dart import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:lucide_icons/lucide_icons.dart'; import '../../../../core/theme/design_tokens.dart'; import '../../data/models/schedule_item_model.dart'; import '../../data/services/mock_calendar_service.dart'; class CreateEventSheet extends StatefulWidget { final DateTime? initialDate; final ScheduleItemModel? editingEvent; const CreateEventSheet({ super.key, this.initialDate, this.editingEvent, }); static Future show(BuildContext context, {DateTime? initialDate}) { return showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => CreateEventSheet(initialDate: initialDate), ); } static Future edit(BuildContext context, ScheduleItemModel event) { return showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => CreateEventSheet(editingEvent: event), ); } @override State createState() => _CreateEventSheetState(); } class _CreateEventSheetState extends State { late TabController _tabController; final _titleController = TextEditingController(); final _descriptionController = TextEditingController(); final _locationController = TextEditingController(); final _notesController = TextEditingController(); late DateTime _startDate; late DateTime _startTime; DateTime? _endDate; DateTime? _endTime; String _selectedColor = '#3B82F6'; bool get _isEditing => widget.editingEvent != null; @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); if (_isEditing) { final event = widget.editingEvent!; _titleController.text = event.title; _descriptionController.text = event.description ?? ''; _locationController.text = event.metadata?.location ?? ''; _notesController.text = event.metadata?.notes ?? ''; _startDate = event.startAt; _startTime = event.startAt; _endDate = event.endAt; _endTime = event.endAt; _selectedColor = event.metadata?.color ?? '#3B82F6'; } else { final now = widget.initialDate ?? DateTime.now(); _startDate = now; _startTime = now; _endDate = now; _endTime = now.add(const Duration(hours: 1)); } } @override void dispose() { _tabController.dispose(); _titleController.dispose(); _descriptionController.dispose(); _locationController.dispose(); _notesController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Container( height: MediaQuery.of(context).size.height * 0.85, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( children: [ _buildHeader(), _buildTabBar(), Expanded(child: _buildTabContent()), ], ), ); } Widget _buildHeader() { return Container( height: 56, padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: () => Navigator.pop(context), child: const Icon(LucideIcons.x, size: 24, color: AppColors.slate700), ), Text( _isEditing ? '编辑日程' : '新建日程', style: const TextStyle( fontSize: 17, fontWeight: FontWeight.w600, color: AppColors.slate900, ), ), GestureDetector( onTap: _saveEvent, child: Text( '保存', style: TextStyle( fontSize: 17, fontWeight: FontWeight.w600, color: _titleController.text.trim().isNotEmpty ? AppColors.blue600 : AppColors.slate400, ), ), ), ], ), ); } Widget _buildTabBar() { return Container( decoration: const BoxDecoration( border: Border(bottom: BorderSide(color: AppColors.border)), ), child: TabBar( controller: _tabController, labelColor: AppColors.blue600, unselectedLabelColor: AppColors.slate600, indicatorColor: AppColors.blue600, tabs: const [ Tab(text: '基础'), Tab(text: '进阶'), ], ), ); } Widget _buildTabContent() { return TabBarView( controller: _tabController, children: [ _buildBasicTab(), _buildAdvancedTab(), ], ); } Widget _buildBasicTab() { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTextField('标题', _titleController, '请输入日程标题'), const SizedBox(height: 20), _buildDateTimePicker('开始', _startDate, _startTime, (date, time) { setState(() { _startDate = date; _startTime = time; }); }), const SizedBox(height: 20), _buildDateTimePicker('结束', _endDate ?? _startDate, _endTime ?? _startTime, (date, time) { setState(() { _endDate = date; _endTime = time; }); }, isOptional: true), ], ), ); } Widget _buildAdvancedTab() { return SingleChildScrollView( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTextField('描述', _descriptionController, '请输入描述'), const SizedBox(height: 20), _buildTextField('地点', _locationController, '请输入地点'), const SizedBox(height: 20), _buildColorPicker(), const SizedBox(height: 20), _buildTextField('备注', _notesController, '请输入备注', maxLines: 3), ], ), ); } Widget _buildTextField(String label, TextEditingController controller, String hint, {int maxLines = 1}) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.slate700), ), const SizedBox(height: 8), TextField( controller: controller, maxLines: maxLines, decoration: InputDecoration( hintText: hint, hintStyle: const TextStyle(color: AppColors.slate400), filled: true, fillColor: AppColors.slate50, border: OutlineInputBorder(borderRadius: BorderRadius.circular(10), borderSide: BorderSide.none), contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), ), ), ], ); } Widget _buildDateTimePicker(String label, DateTime date, DateTime time, Function(DateTime, DateTime) onChanged, {bool isOptional = false}) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label + (isOptional ? '(可选)' : ''), style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.slate700), ), const SizedBox(height: 8), Row( children: [ Expanded( child: GestureDetector( onTap: () => _showDatePicker(date, (newDate) { onChanged(newDate, time); }), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), decoration: BoxDecoration( color: AppColors.slate50, borderRadius: BorderRadius.circular(10), ), child: Text( '${date.year}年${date.month}月${date.day}日', style: const TextStyle(fontSize: 15, color: AppColors.slate900), ), ), ), ), const SizedBox(width: 12), Expanded( child: GestureDetector( onTap: () => _showTimePicker(time, (newTime) { onChanged(date, newTime); }), child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 12), decoration: BoxDecoration( color: AppColors.slate50, borderRadius: BorderRadius.circular(10), ), child: Text( '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}', style: const TextStyle(fontSize: 15, color: AppColors.slate900), ), ), ), ), ], ), ], ); } Widget _buildColorPicker() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '颜色', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: AppColors.slate700), ), const SizedBox(height: 8), Row( children: defaultColors.map((color) { final colorHex = '#${color.value.toRadixString(16).substring(2).toUpperCase()}'; final isSelected = _selectedColor == colorHex; return GestureDetector( onTap: () => setState(() => _selectedColor = colorHex), child: Container( margin: const EdgeInsets.only(right: 12), width: 32, height: 32, decoration: BoxDecoration( color: color, shape: BoxShape.circle, border: isSelected ? Border.all(color: AppColors.slate900, width: 2) : null, ), child: isSelected ? const Icon(Icons.check, size: 16, color: Colors.white) : null, ), ); }).toList(), ), ], ); } void _showDatePicker(DateTime initial, Function(DateTime) onChanged) { showModalBottomSheet( context: context, builder: (context) => Container( height: 280, color: Colors.white, child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')), TextButton(onPressed: () => Navigator.pop(context), child: const Text('确定')), ], ), Expanded( child: CupertinoDatePicker( mode: CupertinoDatePickerMode.date, initialDateTime: initial, onDateTimeChanged: onChanged, ), ), ], ), ), ); } void _showTimePicker(DateTime initial, Function(DateTime) onChanged) { showModalBottomSheet( context: context, builder: (context) => Container( height: 280, color: Colors.white, child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')), TextButton(onPressed: () => Navigator.pop(context), child: const Text('确定')), ], ), Expanded( child: CupertinoDatePicker( mode: CupertinoDatePickerMode.time, initialDateTime: initial, onDateTimeChanged: onChanged, ), ), ], ), ), ); } void _saveEvent() { if (_titleController.text.trim().isEmpty) return; final startAt = DateTime( _startDate.year, _startDate.month, _startDate.day, _startTime.hour, _startTime.minute, ); DateTime? endAt; if (_endDate != null && _endTime != null) { endAt = DateTime( _endDate!.year, _endDate!.month, _endDate!.day, _endTime!.hour, _endTime!.minute, ); } final metadata = ScheduleMetadata( color: _selectedColor, location: _locationController.text.trim().isNotEmpty ? _locationController.text.trim() : null, notes: _notesController.text.trim().isNotEmpty ? _notesController.text.trim() : null, ); final event = ScheduleItemModel( id: _isEditing ? widget.editingEvent!.id : 'evt_${DateTime.now().millisecondsSinceEpoch}', title: _titleController.text.trim(), description: _descriptionController.text.trim().isNotEmpty ? _descriptionController.text.trim() : null, startAt: startAt, endAt: endAt, metadata: metadata, ); final service = CalendarService(); if (_isEditing) { service.updateEvent(event); } else { service.addEvent(event); } Navigator.pop(context); } } ``` **Step 2: 提交** ```bash git add apps/lib/features/calendar/ui/widgets/create_event_sheet.dart git commit -m "feat(calendar): 添加日历事件创建底部弹窗组件" ``` --- ## Task 4: 在月视图添加 + 号图标 **Files:** - Modify: `apps/lib/features/calendar/ui/screens/calendar_month_screen.dart` **Step 1: 添加 + 号图标到 header** 在 `_buildHeader` 方法中,在 `Row` 的末尾添加: ```dart // 在现有的 Row children 中添加 const Spacer(), GestureDetector( onTap: () => CreateEventSheet.show(context), child: Container( width: 36, height: 36, decoration: BoxDecoration( color: AppColors.blue600, borderRadius: BorderRadius.circular(18), ), child: const Icon( LucideIcons.plus, size: 20, color: Colors.white, ), ), ), ``` 同时添加 import: ```dart import '../../ui/widgets/create_event_sheet.dart'; ``` **Step 2: 提交** ```bash git add apps/lib/features/calendar/ui/screens/calendar_month_screen.dart git commit -m "feat(calendar): 在月视图添加创建事件按钮" ``` --- ## Task 5: 在日视图添加 + 号图标 **Files:** - Modify: `apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart` **Step 1: 添加 + 号图标到 header** 在 `_buildHeader` 方法中,在 `Row` 的末尾添加: ```dart const Spacer(), GestureDetector( onTap: () => CreateEventSheet.show(context, initialDate: _selectedDate), child: Container( width: 36, height: 36, decoration: BoxDecoration( color: AppColors.blue600, borderRadius: BorderRadius.circular(18), ), child: const Icon( LucideIcons.plus, size: 20, color: Colors.white, ), ), ), ``` 同时添加 import: ```dart import '../../ui/widgets/create_event_sheet.dart'; ``` **Step 2: 提交** ```bash git add apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart git commit -m "feat(calendar): 在日视图添加创建事件按钮" ``` --- ## Task 6: 在月视图显示事件 **Files:** - Modify: `apps/lib/features/calendar/ui/screens/calendar_month_screen.dart` **Step 1: 修改 `_buildWeekEvents` 方法显示事件** ```dart import '../../data/services/mock_calendar_service.dart'; Widget _buildWeekEvents(int weekStart, int startWeekday, int daysInMonth) { // 找到这一周第一天的日期 final firstDayOfMonth = DateTime(_currentMonth.year, _currentMonth.month, 1); final weekFirstDate = firstDayOfMonth.add(Duration(days: weekStart - startWeekday)); return SizedBox( height: 70, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: List.generate(7, (index) { final dayIndex = weekStart + index - startWeekday + 1; if (dayIndex < 1 || dayIndex > daysInMonth) { return const SizedBox(width: 38, height: 1); } final date = weekFirstDate.add(Duration(days: index)); final events = CalendarService().getEventsForDay(date); return SizedBox( width: 38, height: 70, child: Column( children: events.take(2).map((event) { final color = _parseColor(event.metadata?.color); return Container( margin: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), decoration: BoxDecoration( color: color.withOpacity(0.2), borderRadius: BorderRadius.circular(4), ), child: Text( event.title, style: TextStyle(fontSize: 9, color: color, fontWeight: FontWeight.w500), maxLines: 1, overflow: TextOverflow.ellipsis, ), ); }).toList(), ), ); }), ), ); } Color _parseColor(String? hex) { if (hex == null || hex.isEmpty) return AppColors.blue600; try { return Color(int.parse(hex.replaceFirst('#', '0xFF'))); } catch (_) { return AppColors.blue600; } } ``` **Step 2: 提交** ```bash git add apps/lib/features/calendar/ui/screens/calendar_month_screen.dart git commit -m "feat(calendar): 在月视图显示事件" ``` --- ## Task 7: 在日视图显示事件 **Files:** - Modify: `apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart` **Step 1: 修改 `_buildTimelineBoard` 方法显示事件** ```dart import '../../data/services/mock_calendar_service.dart'; import '../../data/models/schedule_item_model.dart'; Widget _buildTimelineBoard() { final now = DateTime.now(); final showCurrent = shouldShowCurrentMarker(_selectedDate, now); final events = CalendarService().getEventsForDay(_selectedDate); final rows = []; for (var hour = 7; hour <= 22; hour++) { // 查找这个小时的事件 final hourEvents = events.where((e) => e.startAt.hour == hour).toList(); if (hourEvents.isNotEmpty) { rows.add(_buildEventRow(hourEvents)); } rows.add(_buildTimelineRow(formatHour(hour))); if (showCurrent && now.hour == hour) { rows.add(_buildTimelineRow(formatHm(now), isCurrentTime: true)); } } rows.add(_buildTimelineRow(formatHour(24), isDisabled: true)); return Column(children: rows); } Widget _buildEventRow(List events) { return SizedBox( height: 34, child: Row( children: [ const SizedBox(width: 52), Expanded( child: ...events.map((event) => GestureDetector( onTap: () => context.push('/calendar/events/${event.id}'), child: Container( margin: const EdgeInsets.only(bottom: 2), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _parseColor(event.metadata?.color).withOpacity(0.2), borderRadius: BorderRadius.circular(6), border: Border.all( color: _parseColor(event.metadata?.color), width: 1, ), ), child: Row( children: [ Container( width: 8, height: 8, decoration: BoxDecoration( color: _parseColor(event.metadata?.color), shape: BoxShape.circle, ), ), const SizedBox(width: 6), Expanded( child: Text( event.title, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: _parseColor(event.metadata?.color), ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), ), )), ), ], ), ); } Color _parseColor(String? hex) { if (hex == null || hex.isEmpty) return AppColors.blue600; try { return Color(int.parse(hex.replaceFirst('#', '0xFF'))); } catch (_) { return AppColors.blue600; } } ``` **Step 2: 提交** ```bash git add apps/lib/features/calendar/ui/screens/calendar_dayweek_screen.dart git commit -m "feat(calendar): 在日视图显示事件" ``` --- ## Task 8: 更新事件详情页支持编辑和删除 **Files:** - Modify: `apps/lib/features/calendar/ui/screens/calendar_event_detail_screen.dart` **Step 1: 修改详情页接收事件 ID 并加载数据** ```dart import 'package:flutter/material.dart'; import 'package:lucide_icons/lucide_icons.dart'; import 'package:go_router/go_router.dart'; import '../../../../core/theme/design_tokens.dart'; import '../../data/services/mock_calendar_service.dart'; import '../../data/models/schedule_item_model.dart'; import '../widgets/create_event_sheet.dart'; class CalendarEventDetailScreen extends StatefulWidget { final String eventId; const CalendarEventDetailScreen({super.key, required this.eventId}); @override State createState() => _CalendarEventDetailScreenState(); } class _CalendarEventDetailScreenState extends State { ScheduleItemModel? _event; @override void initState() { super.initState(); _loadEvent(); } void _loadEvent() { _event = CalendarService().getEventById(widget.eventId); } @override Widget build(BuildContext context) { if (_event == null) { return Scaffold( body: Center(child: Text('Event not found', style: TextStyle(color: AppColors.slate600))), ); } // ... 其余代码使用 _event 而不是硬编码数据 } } ``` **Step 2: 修改 router 传递 eventId** ```dart // in app_router.dart GoRoute( path: '/calendar/events/:id', builder: (context, state) => CalendarEventDetailScreen( eventId: state.pathParameters['id']!, ), ), ``` **Step 3: 添加编辑和删除功能** 在详情页的编辑和删除按钮上添加: ```dart // 编辑按钮 GestureDetector( onTap: () => CreateEventSheet.edit(context, _event!), child: Container(...), ), // 删除按钮 GestureDetector( onTap: () => _showDeleteConfirmation(), child: Container(...), ), void _showDeleteConfirmation() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('删除日程'), content: const Text('确定要删除这个日程吗?'), actions: [ TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')), TextButton( onPressed: () { CalendarService().deleteEvent(widget.eventId); Navigator.pop(context); context.pop(); }, child: Text('删除', style: TextStyle(color: AppColors.red500)), ), ], ), ); } ``` **Step 4: 提交** ```bash git add apps/lib/features/calendar/ui/screens/calendar_event_detail_screen.dart git add apps/lib/core/router/app_router.dart git commit -m "feat(calendar): 详情页支持编辑和删除事件" ``` --- ## Task 9: 运行测试并验证 **Step 1: 运行 Flutter 测试** ```bash cd apps flutter test ``` **Step 2: 验证构建** ```bash flutter build apk --debug ``` **Step 3: 提交** ```bash git commit -m "chore(calendar): 测试通过并验证构建" ``` --- ## 执行选项 **Plan complete and saved to `docs/plans/2026-03-02-calendar-create-event-design.md`. Two execution options:** **1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration **2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints **Which approach?**