1. 项目概述:家庭药箱管理App的服药记录功能
作为一名长期关注健康科技领域的开发者,我发现服药依从性(Medication Adherence)是影响治疗效果的关键因素。根据世界卫生组织的数据,约50%的慢性病患者存在服药不规律问题。这正是我们开发家庭药箱管理App中服药记录功能的初衷——通过技术手段帮助用户建立科学的用药习惯。
这个基于Flutter for OpenHarmony的解决方案,核心目标是解决三个实际问题:
- 用药记录不完整:传统纸质记录容易遗漏
- 状态识别不直观:难以快速了解服药情况
- 数据分析困难:缺乏系统的依从性评估
2. 核心功能设计思路
2.1 数据模型设计哲学
服药记录的数据结构设计遵循"5W1H"原则:
- Who:服药人(memberName)
- What:药品名称(medicineName)
- When:计划时间(scheduledTime)和实际时间(actualTime)
- Where:关联的家庭药箱(通过reminderId关联)
- Why:用药备注(notes)
- How:服药状态(status)
dart复制class MedicationRecord {
final String id; // UUID保证唯一性
final String reminderId; // 关联提醒项
final String medicineName; // 药品全称
final String memberName; // 服药成员
final DateTime scheduledTime; // 计划服药时间
final DateTime? actualTime; // 实际服药时间(可为空)
final String status; // 状态枚举:taken/delayed/missed
final String? notes; // 用药备注
// 构造函数省略...
}
设计要点:actualTime设为nullable是为了准确区分"延迟服药"和"漏服"两种状态。当status为missed时,actualTime应为null。
2.2 状态机设计
服药状态流转是核心业务逻辑,我们采用有限状态机模型:
code复制计划服药时间到达
├─ 用户按时记录 → taken(绿色)
├─ 超时30分钟内记录 → delayed(橙色)
└─ 超时未记录 → missed(红色)
状态判断逻辑实现:
dart复制bool _isDelayed(DateTime scheduled, DateTime actual) {
final diff = actual.difference(scheduled).inMinutes;
return diff > 30; // 医学上通常认为30分钟是合理误差范围
}
3. 界面实现与交互设计
3.1 列表性能优化方案
面对可能上千条的服药记录,我们采用:
- 按日期分组预处理
- ListView.builder懒加载
- 使用Consumer局部刷新
dart复制final groupedRecords = <String, List<MedicationRecord>>{};
for (var record in records) {
final dateKey = DateFormat('yyyy-MM-dd').format(record.scheduledTime);
groupedRecords.putIfAbsent(dateKey, () => []);
groupedRecords[dateKey]!.add(record);
}
3.2 视觉编码系统
采用医疗行业通用的颜色语义:
- 绿色 (#4CAF50):安全/已完成
- 橙色 (#FF9800):警告/需注意
- 红色 (#F44336):危险/未完成
卡片设计细节:
dart复制Container(
decoration: BoxDecoration(
border: Border(left: BorderSide(
color: statusColor,
width: 4.w, // 响应式宽度
)),
),
child: Row(
children: [
// 状态图标容器
Container(
decoration: BoxDecoration(
color: statusColor.withOpacity(0.1),
shape: BoxShape.circle,
),
child: Icon(statusIcon, color: statusColor),
),
// 药品信息区
Expanded(child: ...),
// 时间状态区
Column(
children: [
Text(DateFormat('HH:mm').format(...)),
Chip(
label: Text(statusText),
backgroundColor: statusColor.withOpacity(0.1),
)
]
)
]
)
)
4. 业务逻辑实现
4.1 自动记录生成机制
采用定时任务检查未记录的服药提醒:
dart复制void _checkMissedRecords() {
final now = DateTime.now();
final cutoffTime = now.subtract(Duration(minutes: 30));
_reminders.where((r) => r.isActive).forEach((reminder) {
reminder.reminderTimes.where((t) => t.isBefore(cutoffTime)).forEach((time) {
if (!_existsRecord(reminder.id, time)) {
_addMissedRecord(reminder, time);
}
});
});
}
bool _existsRecord(String reminderId, DateTime time) {
return _medicationRecords.any((r) =>
r.reminderId == reminderId &&
r.scheduledTime.year == time.year &&
r.scheduledTime.month == time.month &&
r.scheduledTime.day == time.day);
}
4.2 依从性算法
采用医学研究常用的MMAS-8量表简化算法:
dart复制double get adherenceRate {
final total = _medicationRecords.length;
if (total == 0) return 0;
final effective = _medicationRecords.where((r) =>
r.status == 'taken' ||
(r.status == 'delayed' &&
r.actualTime!.difference(r.scheduledTime).inHours < 2)
).length;
return (effective / total).clamp(0.0, 1.0);
}
5. 高级功能实现
5.1 智能补服提醒
基于用药特性给出补服建议:
dart复制void _showMakeUpSuggestion(MedicationRecord record) {
final medicine = _getMedicineInfo(record.medicineName);
final now = DateTime.now();
String suggestion;
if (medicine.halfLife > 24.hours) {
suggestion = '该药品半衰期较长,建议跳过本次剂量';
} else if (now.difference(record.scheduledTime) > 2.hours) {
suggestion = '已超过建议补服时间,请咨询医生';
} else {
suggestion = '建议立即补服全剂量';
}
// 显示带用药建议的对话框
}
5.2 数据导出方案
支持多种格式导出:
dart复制enum ExportFormat {
csv,
pdf,
healthKit, // 苹果健康数据格式
fhir // 医疗数据交换标准
}
Future<void> exportRecords(ExportFormat format) async {
switch (format) {
case ExportFormat.csv:
return _exportToCSV();
case ExportFormat.pdf:
return _generatePDFReport();
case ExportFormat.healthKit:
return _syncWithHealthKit();
case ExportFormat.fhir:
return _convertToFHIR();
}
}
6. 性能优化实践
6.1 数据库索引策略
针对常用查询字段建立复合索引:
dart复制// 在Hive或SQLite中的索引配置
@HiveType(typeId: 1)
class MedicationRecord {
@HiveField(0)
final String id;
@HiveField(1)
@Index(composite: [CompositeIndex('scheduledTime')])
final String reminderId;
@HiveField(2)
@Index()
final DateTime scheduledTime;
}
6.2 内存管理技巧
使用弱引用缓存日期格式化器:
dart复制final _dateFormatCache = Expando<DateFormat>();
DateFormat _getDateFormatter(String pattern) {
return _dateFormatCache[pattern] ??= DateFormat(pattern);
}
7. 测试方案设计
7.1 边界条件测试用例
dart复制test('跨时区时间处理', () {
final utcTime = DateTime.utc(2023, 1, 1, 8);
final localTime = utcTime.toLocal();
final record = MedicationRecord(
scheduledTime: utcTime,
actualTime: localTime,
// 其他参数省略...
);
expect(record.status, isNot(equals('missed')));
});
test('闰秒处理', () {
final leapSecond = DateTime(2023, 6, 30, 23, 59, 60);
final record = MedicationRecord(
scheduledTime: leapSecond,
// 其他参数省略...
);
expect(() => record.scheduledTime.minute, returnsNormally);
});
8. 实际部署经验
8.1 时区处理陷阱
我们曾遇到用户出国旅行时记录混乱的问题,最终解决方案:
dart复制DateTime _parseTimeWithTimezone(DateTime scheduled, String timezone) {
final tz = timezone ?? 'Asia/Shanghai';
return TZDateTime.from(scheduled, _getLocation(tz));
}
8.2 药品名称标准化
通过对接医药数据库实现自动补全:
dart复制final _medicineDatabase = MedicineDatabase();
TextFormField(
decoration: InputDecoration(labelText: '药品名称'),
onChanged: (value) => _updateSuggestions(value),
validator: (value) => _medicineDatabase.validateName(value),
)
9. 扩展方向建议
- 用药知识图谱:关联药品相互作用检查
- 语音记录功能:支持"我刚吃了阿司匹林"语音记录
- 家属监督模式:用药异常时通知家庭成员
- 处方OCR识别:通过拍照添加用药计划
这个服药记录模块的开发经历让我深刻体会到:好的健康类应用应该在准确记录数据的基础上,通过智能分析提供有价值的用药建议。后续我们计划加入机器学习模块,基于历史数据预测最佳服药时间。