Files
AI-Health/health_app/lib/pages/medication/medication_checkin_page.dart
MingNian 63d2092c24 feat: UI全面改造 + 后端多项修复
【后端修复】
- 删除diet/consultation agent假工具声明
- 修复DayOfWeek实体注释
- 修复Vision API content序列化
- cleanup_service级联删除修复
- 用药提醒时区偏差修复
- 统一DateTime处理(UtcNow+8)
- 新增UTC DateTime JSON转换器

【前端UI重构】
- 配色体系全面更新(#8B5CF6淡紫+#F0ECFF背景)
- 登录页重设计
- 首页重设计(透明顶栏、渐变背景、胶囊输入区)
- 聊天卡片加白蓝边框、渐变标题
- 侧边栏重构(渐变背景、合并顶部、删除底部设置)
- 确认卡片可编辑字段恢复
- 所有子页面加返回按钮
- catch异常加日志
- 删除后refresh provider缓存
2026-06-10 18:27:38 +08:00

146 lines
6.6 KiB
Dart
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.

import 'package:flutter/material.dart';
import '../../core/app_colors.dart';
import '../../core/app_theme.dart';
import '../../core/navigation_provider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/auth_provider.dart';
import '../../providers/data_providers.dart';
class MedicationCheckInPage extends ConsumerStatefulWidget {
final String? medId; // 可选指定药品null 显示全部
const MedicationCheckInPage({super.key, this.medId});
@override ConsumerState<MedicationCheckInPage> createState() => _MedicationCheckInPageState();
}
class _MedicationCheckInPageState extends ConsumerState<MedicationCheckInPage> {
Future<List<Map<String, dynamic>>>? _future;
@override void initState() { super.initState(); _load(); }
void _load() => setState(() { _future = ref.read(medicationReminderProvider.future); });
Future<void> _toggleDose(String medId, String time, bool taken) async {
try {
final api = ref.read(apiClientProvider);
if (taken) {
// 取消打卡:删掉对应 log
await api.delete('/api/medications/$medId/confirm-dose/$time');
} else {
// 打卡
await api.post('/api/medications/$medId/confirm-dose', data: {'scheduledTime': time, 'status': 'taken'});
}
ref.invalidate(medicationReminderProvider);
ref.invalidate(medicationListProvider);
_load();
} catch (e) { debugPrint('[MedCheckIn] 打卡失败: $e'); }
}
@override Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppTheme.bg,
appBar: AppBar(leading: IconButton(icon: const Icon(Icons.arrow_back), onPressed: () => popRoute(ref)), title: const Text('服药打卡'), centerTitle: true),
body: FutureBuilder<List<Map<String, dynamic>>>(
future: _future,
builder: (ctx, snap) {
if (snap.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator(color: AppTheme.primary));
}
final reminders = snap.data ?? [];
if (reminders.isEmpty) {
return Center(
child: Column(mainAxisSize: MainAxisSize.min, children: [
Icon(Icons.check_circle_outline, size: 64, color: AppTheme.textHint),
const SizedBox(height: 12),
const Text('今天所有用药已打卡', style: TextStyle(fontSize: 19, color: AppColors.textSecondary)),
]),
);
}
// 如果指定了 medId只显示该药品
var filtered = reminders;
if (widget.medId != null) {
filtered = reminders.where((r) => r['id']?.toString() == widget.medId).toList();
}
// 按药品分组
final grouped = <String, List<Map<String, dynamic>>>{};
for (final r in filtered) {
final name = r['name']?.toString() ?? '药品';
grouped.putIfAbsent(name, () => []).add(r);
}
return RefreshIndicator(
onRefresh: () async => _load(),
child: ListView(
padding: const EdgeInsets.all(14),
children: grouped.entries.map((entry) {
final medName = entry.key;
final doses = entry.value;
final dosage = doses.first['dosage']?.toString() ?? '';
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: AppTheme.surface,
borderRadius: BorderRadius.circular(AppTheme.rLg),
boxShadow: [AppTheme.shadowCard],
),
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(children: [
Container(
width: 40, height: 40,
decoration: BoxDecoration(color: AppColors.warningLight, borderRadius: BorderRadius.circular(10)),
child: const Center(child: Text('💊', style: TextStyle(fontSize: 23))),
),
const SizedBox(width: 12),
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(medName, style: const TextStyle(fontSize: 19, fontWeight: FontWeight.w700)),
if (dosage.isNotEmpty) Text(dosage, style: const TextStyle(fontSize: 16, color: AppColors.textSecondary)),
]),
]),
const SizedBox(height: 14),
...doses.map((d) {
final time = d['scheduledTime']?.toString() ?? '';
final status = d['status']?.toString() ?? 'pending';
final isTaken = status == 'taken';
final medId = d['id']?.toString() ?? '';
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Row(children: [
Icon(
isTaken ? Icons.check_circle : Icons.radio_button_unchecked,
size: 23,
color: isTaken ? AppTheme.success : AppTheme.border,
),
const SizedBox(width: 10),
Text(time, style: TextStyle(
fontSize: 18, fontWeight: FontWeight.w500,
color: isTaken ? AppTheme.textSub : AppTheme.text,
)),
const Spacer(),
GestureDetector(
onTap: () => _toggleDose(medId, time, isTaken),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: isTaken ? AppColors.successLight : AppTheme.primary,
borderRadius: BorderRadius.circular(20),
),
child: Text(isTaken ? '已打卡' : '打卡',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600,
color: isTaken ? AppTheme.success : AppColors.textOnGradient)),
),
),
]),
);
}),
]),
);
}).toList(),
),
);
},
),
);
}
}