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,174 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/chat_provider.dart';
import '../../widgets/agent_bar.dart';
import '../../widgets/health_drawer.dart';
import 'widgets/chat_messages_view.dart';
/// 首页——主界面
class HomePage extends ConsumerStatefulWidget {
const HomePage({super.key});
@override
ConsumerState<HomePage> createState() => _HomePageState();
}
class _HomePageState extends ConsumerState<HomePage> {
final _textCtrl = TextEditingController();
final _scrollCtrl = ScrollController();
bool _taskCardsExpanded = true;
@override
void dispose() {
_textCtrl.dispose();
_scrollCtrl.dispose();
super.dispose();
}
void _sendMessage() {
final text = _textCtrl.text.trim();
if (text.isEmpty) return;
_textCtrl.clear();
ref.read(chatProvider.notifier).sendMessage(text);
}
@override
Widget build(BuildContext context) {
final chatState = ref.watch(chatProvider);
final selectedAgent = ref.watch(selectedAgentProvider);
return Scaffold(
drawer: const HealthDrawer(),
body: SafeArea(
child: Column(children: [
_buildHeader(context),
if (_taskCardsExpanded) _buildTaskCards(chatState),
Expanded(child: ChatMessagesView(scrollCtrl: _scrollCtrl, messages: chatState.messages)),
if (selectedAgent != null) _buildAgentPanel(context, selectedAgent),
const AgentBar(),
_buildInputBar(),
]),
),
);
}
Widget _buildHeader(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(children: [
Builder(builder: (ctx) => IconButton(
icon: const Icon(Icons.menu, size: 24),
onPressed: () => Scaffold.of(ctx).openDrawer(),
)),
const Spacer(),
Text('健康管家', style: Theme.of(context).textTheme.titleLarge),
const Spacer(),
const SizedBox(width: 48),
]),
);
}
Widget _buildTaskCards(ChatState chatState) {
return GestureDetector(
onVerticalDragUpdate: (d) { if (d.delta.dy < -10) setState(() => _taskCardsExpanded = false); },
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: const Color(0xFFEDEBFF),
borderRadius: BorderRadius.circular(12),
),
child: Column(children: [
Row(children: [
const Icon(Icons.wb_sunny, size: 18, color: Color(0xFF635BFF)),
const SizedBox(width: 8),
const Text('早上好!', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
const Spacer(),
GestureDetector(
onTap: () => setState(() => _taskCardsExpanded = false),
child: const Icon(Icons.keyboard_arrow_up, size: 20, color: Color(0xFF666666)),
),
]),
if (chatState.noticeText != null)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Text(chatState.noticeText!, style: const TextStyle(fontSize: 14, color: Color(0xFF666666))),
),
]),
),
);
}
Widget _buildAgentPanel(BuildContext context, ActiveAgent agent) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.vertical(top: Radius.circular(16)),
boxShadow: [BoxShadow(color: const Color(0xFF635BFF).withAlpha(20), blurRadius: 8, offset: const Offset(0, -2))],
),
child: Column(mainAxisSize: MainAxisSize.min, children: _getAgentButtons(agent)),
);
}
List<Widget> _getAgentButtons(ActiveAgent agent) {
final buttons = <Widget>[];
if (agent == ActiveAgent.health) {
buttons.add(_panelBtn('手动录入血压', Icons.favorite));
buttons.add(_panelBtn('手动录入血糖', Icons.bloodtype));
buttons.add(_panelBtn('手动录入心率', Icons.monitor_heart));
} else if (agent == ActiveAgent.diet) {
buttons.add(_panelBtn('拍照', Icons.camera_alt));
buttons.add(_panelBtn('上传照片', Icons.photo_library));
} else if (agent == ActiveAgent.medication) {
buttons.add(_panelBtn('用药管理', Icons.medication));
buttons.add(_panelBtn('用药提醒', Icons.alarm));
} else if (agent == ActiveAgent.consultation) {
buttons.add(_panelBtn('找医生', Icons.person_search));
} else if (agent == ActiveAgent.exercise) {
buttons.add(_panelBtn('查看本周计划', Icons.calendar_view_week));
buttons.add(_panelBtn('创建新计划', Icons.add_circle_outline));
}
return buttons;
}
Widget _panelBtn(String label, IconData icon) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
onPressed: () {},
icon: Icon(icon, size: 20),
label: Text(label),
style: OutlinedButton.styleFrom(
foregroundColor: const Color(0xFF635BFF),
side: const BorderSide(color: Color(0xFF635BFF)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
);
}
Widget _buildInputBar() {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Colors.grey.shade200)),
),
child: Row(children: [
IconButton(icon: const Icon(Icons.attach_file, size: 24, color: Color(0xFF666666)), onPressed: () {}),
Expanded(
child: TextField(
controller: _textCtrl,
decoration: const InputDecoration(hintText: '输入你想说的...', contentPadding: EdgeInsets.symmetric(horizontal: 12), border: InputBorder.none),
onSubmitted: (_) => _sendMessage(),
),
),
IconButton(icon: const Icon(Icons.send, size: 24, color: Color(0xFF635BFF)), onPressed: _sendMessage),
]),
);
}
}