在这里插入图片描述

血糖监测对于糖尿病患者和高危人群来说至关重要。本文将介绍如何在Flutter for OpenHarmony应用中实现血糖记录功能,包括不同测量时段的记录、趋势分析、健康状态评估等实用功能。

设计思路

血糖记录与血压记录有相似之处,但也有其特殊性。血糖值会因测量时间不同而有不同的正常范围,比如空腹血糖和餐后血糖的标准就不一样。因此我们需要在记录时标注测量时段,并根据不同时段采用不同的判断标准。

页面采用简洁的卡片式设计,顶部是成员选择器,中间展示趋势图和最新记录,底部列出历史数据。血糖值使用大号字体居中显示,让用户一眼就能看到关键信息。

页面整体结构

页面使用Consumer2同时监听家庭成员和健康数据的变化:


Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('血糖记录')),
    body: Consumer2<FamilyProvider, HealthProvider>(
      builder: (context, familyProvider, healthProvider, child) {
        if (_selectedMemberId == null && familyProvider.members.isNotEmpty) {
          _selectedMemberId = familyProvider.members.first.id;
        }

        final records = _selectedMemberId != null
            ? healthProvider.getRecordsByMemberAndType(_selectedMemberId!, 'blood_sugar')
            : <HealthRecord>[];

        return SingleChildScrollView(
          padding: EdgeInsets.all(16.w),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              _buildMemberSelector(familyProvider),
              SizedBox(height: 16.h),
              if (records.isNotEmpty) _buildChart(records),
              SizedBox(height: 16.h),
              _buildLatestRecord(records),
              SizedBox(height: 16.h),
              _buildRecordList(records),
            ],
          ),
        );
      },
    ),
    floatingActionButton: FloatingActionButton(
      onPressed: () => _showAddDialog(context),
      backgroundColor: const Color(0xFF00897B),
      child: const Icon(Icons.add),
    ),
  );
}

这里通过getRecordsByMemberAndType方法筛选出当前成员的血糖记录,类型参数传入'blood_sugar'。页面结构清晰,各个模块通过SizedBox进行间隔,视觉上更加舒适。

血糖趋势图表

血糖趋势图使用单条曲线展示,并添加了渐变填充效果:

Widget _buildChart(List<HealthRecord> records) {
  final chartRecords = records.take(7).toList().reversed.toList();

  return Container(
    height: 200.h,
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),
      boxShadow: [
        BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2)),
      ],
    ),
    child: LineChart(
      LineChartData(
        gridData: FlGridData(show: false),
        titlesData: FlTitlesData(show: false),
        borderData: FlBorderData(show: false),
        lineBarsData: [
          LineChartBarData(
            spots: chartRecords.asMap().entries.map((e) {
              return FlSpot(e.key.toDouble(), (e.value.data['value'] as double));
            }).toList(),
            isCurved: true,
            color: Colors.blue,
            barWidth: 2,
            dotData: FlDotData(show: true),
            belowBarData: BarAreaData(show: true, color: Colors.blue.withOpacity(0.1)),
          ),
        ],
      ),
    ),
  );
}

与血压图表不同,血糖图表只有一条曲线,但增加了belowBarData配置,在曲线下方填充半透明的蓝色区域,让图表更有层次感。这种设计既美观又能突出数据趋势。

最新记录展示

最新记录卡片是页面的核心,需要根据测量时段判断血糖是否正常:

Widget _buildLatestRecord(List<HealthRecord> records) {
  if (records.isEmpty) {
    return Container(
      padding: EdgeInsets.all(20.w),
      decoration: BoxDecoration(color: Colors.grey[100], borderRadius: BorderRadius.circular(12.r)),
      child: Center(child: Text('暂无血糖记录', style: TextStyle(color: Colors.grey[500]))),
    );
  }

  final latest = records.first;
  final value = latest.data['value'] as double;
  final measureTime = latest.data['measureTime'] as String;

  String status = '正常';
  Color statusColor = Colors.green;
  if (measureTime == '空腹') {
    if (value >= 7.0) {
      status = '偏高';
      statusColor = Colors.red;
    } else if (value < 3.9) {
      status = '偏低';
      statusColor = Colors.orange;
    }
  } else {
    if (value >= 11.1) {
      status = '偏高';
      statusColor = Colors.red;
    }
  }

  return Container(
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),
      boxShadow: [
        BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2)),
      ],
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text('最新记录', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
            Container(
              padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
              decoration: BoxDecoration(
                color: statusColor.withOpacity(0.1),
                borderRadius: BorderRadius.circular(10.r),
              ),
              child: Text(status, style: TextStyle(color: statusColor, fontSize: 12.sp)),
            ),
          ],
        ),
        SizedBox(height: 16.h),
        Center(
          child: Column(
            children: [
              Text(
                value.toStringAsFixed(1),
                style: TextStyle(fontSize: 48.sp, fontWeight: FontWeight.bold, color: Colors.blue),
              ),
              Text('mmol/L', style: TextStyle(fontSize: 14.sp, color: Colors.grey[600])),
              SizedBox(height: 8.h),
              Container(
                padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 4.h),
                decoration: BoxDecoration(
                  color: Colors.blue.withOpacity(0.1),
                  borderRadius: BorderRadius.circular(10.r),
                ),
                child: Text(measureTime, style: TextStyle(color: Colors.blue, fontSize: 12.sp)),
              ),
            ],
          ),
        ),
        SizedBox(height: 8.h),
        Text(
          DateFormat('yyyy-MM-dd HH:mm').format(latest.recordDate),
          style: TextStyle(fontSize: 12.sp, color: Colors.grey[500]),
        ),
      ],
    ),
  );
}

这段代码实现了智能的血糖判断逻辑。空腹血糖正常范围是3.9-7.0 mmol/L,餐后血糖不应超过11.1 mmol/L。血糖值使用48号超大字体居中显示,配合单位和测量时段标签,让信息层次分明。

历史记录列表

历史记录以列表形式展示最近10条数据:

Widget _buildRecordList(List<HealthRecord> records) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text('历史记录', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
      SizedBox(height: 8.h),
      ...records.take(10).map((record) {
        return Container(
          margin: EdgeInsets.only(bottom: 8.h),
          padding: EdgeInsets.all(12.w),
          decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(8.r)),
          child: Row(
            children: [
              Text(DateFormat('MM-dd').format(record.recordDate),
                  style: TextStyle(fontSize: 14.sp, color: Colors.grey[600])),
              SizedBox(width: 16.w),
              Text('${(record.data['value'] as double).toStringAsFixed(1)} mmol/L',
                  style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.w500)),
              const Spacer(),
              Text(record.data['measureTime'] as String,
                  style: TextStyle(fontSize: 14.sp, color: Colors.grey[600])),
            ],
          ),
        );
      }).toList(),
    ],
  );
}

每条记录横向排列,左侧显示日期,中间显示血糖值和单位,右侧显示测量时段。使用toStringAsFixed(1)保留一位小数,让数据显示更加规范。

添加记录对话框

添加血糖记录时需要选择测量时段,这里使用StatefulBuilder实现对话框内的状态管理:

void _showAddDialog(BuildContext context) {
  final valueController = TextEditingController();
  String measureTime = '空腹';

  showDialog(
    context: context,
    builder: (context) => StatefulBuilder(
      builder: (context, setDialogState) => AlertDialog(
        title: const Text('添加血糖记录'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextField(
              controller: valueController,
              keyboardType: const TextInputType.numberWithOptions(decimal: true),
              decoration: const InputDecoration(labelText: '血糖值 (mmol/L)'),
            ),
            SizedBox(height: 16.h),
            DropdownButtonFormField<String>(
              value: measureTime,
              decoration: const InputDecoration(labelText: '测量时间'),
              items: ['空腹', '餐后1小时', '餐后2小时', '睡前'].map((t) {
                return DropdownMenuItem(value: t, child: Text(t));
              }).toList(),
              onChanged: (v) => setDialogState(() => measureTime = v!),
            ),
          ],
        ),
        actions: [
          TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')),
          TextButton(
            onPressed: () {
              if (_selectedMemberId != null) {
                final member = context.read<FamilyProvider>().getMemberById(_selectedMemberId!);
                if (member != null) {
                  final record = HealthRecord(
                    id: const Uuid().v4(),
                    memberId: member.id,
                    memberName: member.name,
                    recordDate: DateTime.now(),
                    type: 'blood_sugar',
                    data: {
                      'value': double.tryParse(valueController.text) ?? 0.0,
                      'measureTime': measureTime,
                    },
                  );
                  context.read<HealthProvider>().addRecord(record);
                }
              }
              Navigator.pop(context);
              Get.snackbar('成功', '血糖记录已添加', snackPosition: SnackPosition.BOTTOM);
            },
            child: const Text('保存'),
          ),
        ],
      ),
    ),
  );
}

对话框包含血糖值输入框和测量时段下拉选择器。使用TextInputType.numberWithOptions(decimal: true)允许输入小数,因为血糖值通常带有小数点。StatefulBuilder让下拉选择器的变化能够实时反映在UI上。

成员选择器实现

成员选择器让用户可以切换查看不同家庭成员的血糖数据:

Widget _buildMemberSelector(FamilyProvider provider) {
  return Container(
    padding: EdgeInsets.symmetric(horizontal: 12.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(8.r),
      border: Border.all(color: Colors.grey[300]!),
    ),
    child: DropdownButtonHideUnderline(
      child: DropdownButton<String>(
        value: _selectedMemberId,
        isExpanded: true,
        items: provider.members.map((m) {
          return DropdownMenuItem(value: m.id, child: Text(m.name));
        }).toList(),
        onChanged: (value) => setState(() => _selectedMemberId = value),
      ),
    ),
  );
}

选择器采用白色背景和浅灰色边框,与页面整体风格保持一致。当用户切换成员时,通过setState触发页面重建,自动加载新成员的血糖数据。

技术要点

分时段判断:血糖的正常范围因测量时段而异,代码中实现了空腹和餐后的不同判断标准,让健康评估更加准确。这种细致的判断逻辑体现了对医学知识的尊重。

小数处理:血糖值通常是小数,使用double类型存储,输入时允许小数点,显示时保留一位小数。这些细节处理让应用更加专业。

状态管理:对话框中使用StatefulBuilder实现局部状态管理,避免了创建新的StatefulWidget,代码更加简洁高效。

数据可视化:趋势图使用渐变填充效果,让图表更有视觉冲击力。最新记录使用超大字体居中显示,突出关键信息。

总结

血糖记录功能通过分时段记录和智能判断,为糖尿病患者提供了专业的健康管理工具。图表可视化让血糖变化趋势一目了然,帮助用户更好地控制血糖水平。这种数据驱动的健康管理方式,对慢性病管理具有重要意义。

血糖目标设置

支持设置个性化的血糖控制目标:

double _fastingTarget = 6.0;
double _postprandialTarget = 8.0;

Widget _buildTargetSettings() {
  return Container(
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),
      boxShadow: [
        BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2)),
      ],
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('血糖目标', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
        SizedBox(height: 12.h),
        _buildTargetRow('空腹血糖目标', _fastingTarget, (v) => setState(() => _fastingTarget = v)),
        SizedBox(height: 8.h),
        _buildTargetRow('餐后血糖目标', _postprandialTarget, (v) => setState(() => _postprandialTarget = v)),
      ],
    ),
  );
}

Widget _buildTargetRow(String label, double value, ValueChanged<double> onChanged) {
  return Row(
    children: [
      Expanded(child: Text(label, style: TextStyle(fontSize: 14.sp))),
      Text('${value.toStringAsFixed(1)} mmol/L', style: TextStyle(fontSize: 14.sp, color: Colors.blue)),
      IconButton(
        icon: const Icon(Icons.edit, size: 16),
        onPressed: () => _showTargetDialog(label, value, onChanged),
      ),
    ],
  );
}

血糖目标设置让用户可以根据医生建议设置个性化的控制目标。空腹和餐后血糖有不同的目标值,系统会根据这些目标进行健康评估。

血糖统计分析

提供血糖数据的统计分析:

Widget _buildStatistics(List<HealthRecord> records) {
  if (records.isEmpty) return const SizedBox.shrink();
  
  final values = records.map((r) => r.data['value'] as double).toList();
  final average = values.reduce((a, b) => a + b) / values.length;
  final max = values.reduce((a, b) => a > b ? a : b);
  final min = values.reduce((a, b) => a < b ? a : b);
  
  final inRangeCount = records.where((r) {
    final value = r.data['value'] as double;
    final measureTime = r.data['measureTime'] as String;
    if (measureTime == '空腹') {
      return value >= 3.9 && value < 7.0;
    } else {
      return value < 11.1;
    }
  }).length;
  
  final inRangeRate = inRangeCount / records.length * 100;
  
  return Container(
    padding: EdgeInsets.all(16.w),
    decoration: BoxDecoration(
      color: Colors.white,
      borderRadius: BorderRadius.circular(12.r),
      boxShadow: [
        BoxShadow(color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2)),
      ],
    ),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('统计分析', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
        SizedBox(height: 12.h),
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            _buildStatItem('平均值', '${average.toStringAsFixed(1)}', Colors.blue),
            _buildStatItem('最高值', '${max.toStringAsFixed(1)}', Colors.red),
            _buildStatItem('最低值', '${min.toStringAsFixed(1)}', Colors.green),
            _buildStatItem('达标率', '${inRangeRate.toStringAsFixed(0)}%', Colors.orange),
          ],
        ),
      ],
    ),
  );
}

统计分析显示平均值、最高值、最低值和达标率,帮助用户全面了解血糖控制情况。达标率是衡量血糖管理效果的重要指标。

饮食记录关联

支持记录血糖时关联饮食信息:

String? _mealNote;

Widget _buildMealInput() {
  return TextField(
    decoration: InputDecoration(
      labelText: '饮食备注(可选)',
      hintText: '记录本次测量前的饮食情况',
      border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.r)),
    ),
    onChanged: (v) => _mealNote = v,
  );
}

饮食记录功能让用户可以记录测量前的饮食情况,帮助分析血糖波动的原因。这对于调整饮食习惯很有帮助。

异常值提醒

检测到异常血糖值时显示提醒:

Widget _buildAbnormalAlert(double value, String measureTime) {
  String? alertMessage;
  
  if (measureTime == '空腹') {
    if (value < 3.9) {
      alertMessage = '血糖偏低,请注意补充糖分,如有不适请及时就医';
    } else if (value >= 7.0) {
      alertMessage = '空腹血糖偏高,建议咨询医生调整治疗方案';
    }
  } else {
    if (value >= 11.1) {
      alertMessage = '餐后血糖偏高,建议控制饮食并咨询医生';
    }
  }
  
  if (alertMessage == null) return const SizedBox.shrink();
  
  return Container(
    padding: EdgeInsets.all(12.w),
    decoration: BoxDecoration(
      color: Colors.red.withOpacity(0.1),
      borderRadius: BorderRadius.circular(8.r),
      border: Border.all(color: Colors.red.withOpacity(0.3)),
    ),
    child: Row(
      children: [
        Icon(Icons.warning, color: Colors.red, size: 20.sp),
        SizedBox(width: 8.w),
        Expanded(
          child: Text(alertMessage, style: TextStyle(fontSize: 12.sp, color: Colors.red)),
        ),
      ],
    ),
  );
}

异常值提醒功能在检测到血糖异常时显示警告信息,提醒用户注意并采取相应措施。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐