Files
social-app/docs/plans/2026-03-02-calendar-create-event-plan.md
T

1133 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 日历事件创建功能实现计划
> **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<Attachment>? attachments;
ScheduleMetadata({
this.color,
this.location,
this.notes,
this.attachments,
});
ScheduleMetadata copyWith({
String? color,
String? location,
String? notes,
List<Attachment>? 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<ScheduleItemModel> _events = [];
List<ScheduleItemModel> get events => List.unmodifiable(_events);
List<ScheduleItemModel> 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<ScheduleItemModel> 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<ScheduleItemModel> getEventsForDay(DateTime date) {
if (Env.isMockApi) {
return _mock.getEventsForDay(date);
}
throw UnimplementedError('Real API not implemented');
}
List<ScheduleItemModel> 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<void> show(BuildContext context, {DateTime? initialDate}) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => CreateEventSheet(initialDate: initialDate),
);
}
static Future<void> edit(BuildContext context, ScheduleItemModel event) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) => CreateEventSheet(editingEvent: event),
);
}
@override
State<CreateEventSheet> createState() => _CreateEventSheetState();
}
class _CreateEventSheetState extends State<CreateEventSheet> {
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 = <Widget>[];
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<ScheduleItemModel> 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<CalendarEventDetailScreen> createState() => _CalendarEventDetailScreenState();
}
class _CalendarEventDetailScreenState extends State<CalendarEventDetailScreen> {
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?**