fix: 修复 Flutter 前端多项功能 + 后端运动计划 API

- Android 添加相机/存储权限,拍照和相册功能可用
- AI 回复支持 Markdown 渲染(加粗/表格不再显示**乱码)
- 附件按钮接线,支持拍照/相册/文件选择
- 智能体面板按钮全部接线(拍照/上传/手动录入/导航)
- 侧边栏 AI 录入后自动刷新健康数据
- 运动计划页增加创建按钮 + 打卡功能
- 后端运动计划支持 AI 创建和打卡(Tool Calling)
- 修复 CreateExercisePlanRequest JSON 反序列化
This commit is contained in:
MingNian
2026-06-02 16:34:36 +08:00
parent df263baa5d
commit 498708e568
11 changed files with 212 additions and 18 deletions

View File

@@ -46,9 +46,14 @@ class ExercisePlanPage extends ConsumerWidget {
final plan = ref.watch(currentExercisePlanProvider);
return Scaffold(
appBar: AppBar(title: const Text('运动计划')),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => _createDefaultPlan(ref),
icon: const Icon(Icons.add),
label: const Text('创建本周计划'),
),
body: plan.when(
data: (data) {
if (data == null) return _empty(context, '运动计划', '暂无运动计划');
if (data == null || data.isEmpty) return _empty(context, '运动计划', '暂无运动计划,点击右下角创建');
final items = (data['items'] as List?)?.cast<Map<String, dynamic>>() ?? [];
final weekDays = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
return ListView.builder(
@@ -61,16 +66,39 @@ class ExercisePlanPage extends ConsumerWidget {
return ListTile(
leading: Icon(isDone ? Icons.check_circle : Icons.circle_outlined, color: isDone ? const Color(0xFF43A047) : Colors.grey),
title: Text('${weekDays[day]} ${isRest ? '休息日' : '${item['exerciseType']} ${item['durationMinutes']}分钟'}'),
trailing: isDone ? const Text('✅ 已完成', style: TextStyle(fontSize: 14, color: Color(0xFF43A047))) : const Text('待完成', style: TextStyle(fontSize: 14, color: Color(0xFF999999))),
trailing: isDone ? null : IconButton(icon: const Icon(Icons.check, color: Color(0xFF43A047)), onPressed: () { _checkIn(ref, item['id']); }),
);
},
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (_, _) => _empty(context, '运动计划', '暂无运动计划'),
error: (_, __) => _empty(context, '运动计划', '暂无运动计划,点击右下角创建'),
),
);
}
void _createDefaultPlan(WidgetRef ref) async {
final service = ref.read(exerciseServiceProvider);
final today = DateTime.now();
final monday = today.subtract(Duration(days: today.weekday - 1));
final items = List.generate(7, (i) => {
'dayOfWeek': i,
'exerciseType': i == 2 || i == 5 ? '休息' : '散步',
'durationMinutes': i == 2 || i == 5 ? 0 : 30,
'isRestDay': i == 2 || i == 5,
});
await service.createPlan({
'weekStartDate': '${monday.year}-${monday.month.toString().padLeft(2, '0')}-${monday.day.toString().padLeft(2, '0')}',
'items': items,
});
ref.invalidate(currentExercisePlanProvider);
}
void _checkIn(WidgetRef ref, String itemId) async {
final service = ref.read(exerciseServiceProvider);
await service.checkIn(itemId);
ref.invalidate(currentExercisePlanProvider);
}
}
/// 复查列表