fix: 全面修复 - 实时通讯、报告AI流程、健康概览、用药运动提醒
- SignalR Hub消息持久化+防重复+跨端广播 - 报告VLM+LLM真实AI流程(验证→提取→解读) - 医生审核页重构(严重程度/建议模板/综合评语) - 健康概览五合一趋势图(fl_chart+指标切换) - 用药提醒时区修复+跨午夜+过期提醒 - 运动打卡DayOfWeek映射修复+计划覆盖查询 - 体重与其他四指标同级(Abnormal检查/确认卡牌) - AI Agent支持多种类多时段批量录入 - 删除硬编码假数据(张三/假医生/假AI解读) - 随访/报告审核数据链路全部打通
This commit is contained in:
@@ -274,68 +274,90 @@ class _ExercisePlanCreatePageState extends ConsumerState<ExercisePlanCreatePage>
|
||||
}
|
||||
}
|
||||
|
||||
/// 复查列表
|
||||
class FollowUpListPage extends ConsumerWidget {
|
||||
/// 复查列表(从服务器读取)
|
||||
class FollowUpListPage extends ConsumerStatefulWidget {
|
||||
const FollowUpListPage({super.key});
|
||||
@override Widget build(BuildContext context, WidgetRef ref) {
|
||||
@override ConsumerState<FollowUpListPage> createState() => _FollowUpListPageState();
|
||||
}
|
||||
|
||||
class _FollowUpListPageState extends ConsumerState<FollowUpListPage> {
|
||||
Future<List<Map<String, dynamic>>>? _future;
|
||||
|
||||
@override void initState() { super.initState(); _load(); }
|
||||
void _load() => setState(() { _future = ref.read(followUpServiceProvider).getList(); });
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('复查随访'), centerTitle: true),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: () => _showAddDialog(context),
|
||||
child: const Icon(Icons.add),
|
||||
backgroundColor: const Color(0xFF8B9CF7),
|
||||
),
|
||||
body: ListView(children: _mockFollowUps.map((item) => _FollowUpItem(item: item)).toList()),
|
||||
);
|
||||
}
|
||||
|
||||
void _showAddDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (ctx) => AlertDialog(
|
||||
title: const Text('添加复查提醒'),
|
||||
content: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
TextField(decoration: const InputDecoration(labelText: '医院名称')),
|
||||
const SizedBox(height: 12),
|
||||
TextField(decoration: const InputDecoration(labelText: '科室')),
|
||||
const SizedBox(height: 12),
|
||||
TextField(decoration: const InputDecoration(labelText: '日期', hintText: 'YYYY-MM-DD')),
|
||||
const SizedBox(height: 12),
|
||||
TextField(decoration: const InputDecoration(labelText: '备注')),
|
||||
]),
|
||||
actions: [
|
||||
TextButton(onPressed: () => Navigator.pop(ctx), child: const Text('取消')),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(ctx);
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content: Text('复查提醒已添加 ✅'),
|
||||
backgroundColor: Color(0xFF8B9CF7),
|
||||
));
|
||||
},
|
||||
child: const Text('保存'),
|
||||
),
|
||||
],
|
||||
body: FutureBuilder<List<Map<String, dynamic>>>(
|
||||
future: _future,
|
||||
builder: (ctx, snap) {
|
||||
if (snap.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator(color: Color(0xFF8B9CF7)));
|
||||
}
|
||||
final list = snap.data ?? [];
|
||||
if (list.isEmpty) {
|
||||
return Center(
|
||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||
Icon(Icons.event_note_outlined, size: 64, color: Colors.grey[300]),
|
||||
const SizedBox(height: 12),
|
||||
Text('暂无随访安排', style: Theme.of(context).textTheme.bodyMedium),
|
||||
const SizedBox(height: 4),
|
||||
const Text('医生创建随访后将显示在这里', style: TextStyle(fontSize: 13, color: Color(0xFF999999))),
|
||||
]),
|
||||
);
|
||||
}
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
children: list.map((item) => _FollowUpItem(item: item)).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final _mockFollowUps = [
|
||||
{'id': '1', 'hospital': '协和医院', 'department': '心内科', 'date': '2025-01-20', 'type': '复诊', 'status': 'upcoming', 'notes': '常规复查,带齐病历'},
|
||||
{'id': '2', 'hospital': '人民医院', 'department': '骨科', 'date': '2025-01-25', 'type': '复查', 'status': 'upcoming', 'notes': '术后3个月复查'},
|
||||
{'id': '3', 'hospital': '协和医院', 'department': '心内科', 'date': '2024-12-15', 'type': '复诊', 'status': 'completed', 'notes': '已完成'},
|
||||
];
|
||||
|
||||
class _FollowUpItem extends StatelessWidget {
|
||||
final Map<String, dynamic> item;
|
||||
const _FollowUpItem({required this.item});
|
||||
|
||||
String _statusLabel(String? status) {
|
||||
switch (status) {
|
||||
case 'Upcoming': return '即将到来';
|
||||
case 'Completed': return '已完成';
|
||||
case 'Cancelled': return '已取消';
|
||||
default: return status ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
Color _statusColor(String? status) {
|
||||
switch (status) {
|
||||
case 'Upcoming': return const Color(0xFF4F6EF7);
|
||||
case 'Completed': return const Color(0xFF43A047);
|
||||
case 'Cancelled': return const Color(0xFFE53935);
|
||||
default: return const Color(0xFF999999);
|
||||
}
|
||||
}
|
||||
|
||||
Color _statusBg(String? status) {
|
||||
switch (status) {
|
||||
case 'Upcoming': return const Color(0xFFEDF2FF);
|
||||
case 'Completed': return const Color(0xFFDCFCE7);
|
||||
case 'Cancelled': return const Color(0xFFFFF5F5);
|
||||
default: return const Color(0xFFF5F5F5);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isCompleted = item['status'] == 'completed';
|
||||
final status = item['status']?.toString();
|
||||
final label = _statusLabel(status);
|
||||
final color = _statusColor(status);
|
||||
final bg = _statusBg(status);
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
@@ -346,29 +368,32 @@ class _FollowUpItem extends StatelessWidget {
|
||||
Row(children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: isCompleted ? const Color(0xFFDCFCE7) : const Color(0xFFFEFCE8),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Text(
|
||||
isCompleted ? '已完成' : '待就诊',
|
||||
style: TextStyle(fontSize: 12, color: isCompleted ? const Color(0xFF43A047) : const Color(0xFFF59E0B)),
|
||||
),
|
||||
decoration: BoxDecoration(color: bg, borderRadius: BorderRadius.circular(8)),
|
||||
child: Text(label, style: TextStyle(fontSize: 12, color: color, fontWeight: FontWeight.w500)),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(item['type']?.toString() ?? '', style: TextStyle(fontSize: 14, color: Colors.grey[500])),
|
||||
const Spacer(),
|
||||
if (item['doctorName'] != null)
|
||||
Text('👨⚕️ ${item['doctorName']}', style: const TextStyle(fontSize: 12, color: Color(0xFF999999))),
|
||||
]),
|
||||
const SizedBox(height: 12),
|
||||
Text(item['hospital']?.toString() ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
Text(item['title']?.toString() ?? '', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
||||
const SizedBox(height: 4),
|
||||
Text('${item['department']} · ${item['date']}', style: TextStyle(fontSize: 14, color: Colors.grey[500])),
|
||||
if (item['scheduledAt'] != null)
|
||||
Text(_formatDateTime(item['scheduledAt']?.toString()), style: TextStyle(fontSize: 14, color: Colors.grey[500])),
|
||||
if ((item['notes']?.toString() ?? '').isNotEmpty) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(item['notes']?.toString() ?? '', style: TextStyle(fontSize: 14, color: Colors.grey[600])),
|
||||
Text(item['notes']?.toString() ?? '', style: TextStyle(fontSize: 13, color: Colors.grey[600])),
|
||||
],
|
||||
]),
|
||||
);
|
||||
}
|
||||
|
||||
String _formatDateTime(String? iso) {
|
||||
if (iso == null) return '';
|
||||
final dt = DateTime.tryParse(iso);
|
||||
if (dt == null) return iso;
|
||||
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} ${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
|
||||
}
|
||||
}
|
||||
|
||||
/// 健康档案(可编辑)
|
||||
|
||||
Reference in New Issue
Block a user