Initial commit: 健康管家 AI 健康陪伴助手

- Backend: .NET 10 Minimal API + EF Core + PostgreSQL
- Frontend: Flutter + Riverpod + GoRouter + Dio
- AI: DeepSeek LLM + Qwen VLM (OpenAI-compatible)
- Auth: SMS + JWT (access/refresh tokens)
- Features: AI chat, health tracking, medication management, diet analysis, exercise plans, doctor consultations, report analysis
This commit is contained in:
MingNian
2026-06-02 11:11:29 +08:00
commit 14d7c30d3d
144 changed files with 11436 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../providers/data_providers.dart';
/// 用药列表页
class MedicationListPage extends ConsumerWidget {
const MedicationListPage({super.key});
@override Widget build(BuildContext context, WidgetRef ref) {
final meds = ref.watch(medicationListProvider);
return Scaffold(
appBar: AppBar(title: const Text('我的用药')),
body: meds.when(
data: (list) {
if (list.isEmpty) return _empty(context);
return ListView.builder(
itemCount: list.length,
itemBuilder: (ctx, i) {
final m = list[i];
final times = (m['timeOfDay'] as List?)?.cast<String>() ?? [];
return Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: ListTile(
leading: const Icon(Icons.medication, color: Color(0xFF635BFF)),
title: Text('${m['name']} ${m['dosage'] ?? ''}', style: const TextStyle(fontSize: 16)),
subtitle: Text('每天 ${times.join("")}', style: const TextStyle(fontSize: 14, color: Color(0xFF666666))),
trailing: IconButton(icon: const Icon(Icons.check_circle_outline, color: Color(0xFF43A047)), onPressed: () async {
await ref.read(medicationServiceProvider).confirm(m['id']);
ref.invalidate(medicationListProvider);
}),
),
);
},
);
},
loading: () => const Center(child: CircularProgressIndicator()),
error: (_, _) => _empty(context),
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () => context.push('/medications/add').then((_) => ref.invalidate(medicationListProvider)),
icon: const Icon(Icons.add), label: const Text('添加药品'),
),
);
}
Widget _empty(BuildContext context) => Center(child: Column(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.medication, size: 64, color: Colors.grey[300]),
const SizedBox(height: 12), Text('暂无用药计划', style: Theme.of(context).textTheme.bodyMedium),
const SizedBox(height: 8), Text('可通过 AI 对话或手动添加', style: Theme.of(context).textTheme.labelMedium),
]));
}
/// 编辑用药页
class MedicationEditPage extends ConsumerStatefulWidget {
final String? id;
const MedicationEditPage({super.key, this.id});
@override ConsumerState<MedicationEditPage> createState() => _MedicationEditPageState();
}
class _MedicationEditPageState extends ConsumerState<MedicationEditPage> {
final _nameCtrl = TextEditingController(); final _dosageCtrl = TextEditingController(); final _timeCtrl = TextEditingController();
@override void dispose() { _nameCtrl.dispose(); _dosageCtrl.dispose(); _timeCtrl.dispose(); super.dispose(); }
Future<void> _save() async {
await ref.read(medicationServiceProvider).create({
'name': _nameCtrl.text, 'dosage': _dosageCtrl.text,
'frequency': 'Daily', 'timeOfDay': [if (_timeCtrl.text.isNotEmpty) _timeCtrl.text],
'source': 'Manual', 'startDate': DateTime.now().toIso8601String().substring(0, 10),
});
if (mounted) context.pop();
}
@override Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('添加药品')),
body: ListView(padding: const EdgeInsets.all(16), children: [
TextField(controller: _nameCtrl, decoration: const InputDecoration(labelText: '药品名称', hintText: '如:阿司匹林')),
const SizedBox(height: 16), TextField(controller: _dosageCtrl, decoration: const InputDecoration(labelText: '剂量', hintText: '100mg')),
const SizedBox(height: 16), TextField(controller: _timeCtrl, decoration: const InputDecoration(labelText: '服药时间', hintText: '08:00:00')),
const SizedBox(height: 32), SizedBox(width: double.infinity, height: 48, child: ElevatedButton(onPressed: _save, child: const Text('保存'))),
]),
);
}