Files
tuiche/lib/pages/sleep_report/qc_report/QcHeartRateStandardWidget.dart
2026-03-10 12:01:00 +08:00

365 lines
13 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:EasyDartModule/EasyDartModule.dart' as es;
import 'package:ef/ef.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:flutterflow_ui/flutterflow_ui.dart';
import 'package:vbvs_app/common/color/appConstants.dart';
import 'package:vbvs_app/common/util/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/component/tool/ClickableContainer.dart';
import 'package:vbvs_app/enum/APPPackageType.dart';
import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart';
import 'package:vbvs_app/pages/sleep_report/chart/QcTimeSeriesChart.dart';
//心率基准
class QcHeartRateStandardWidget extends StatefulWidget {
var reportData;
QcHeartRateStandardWidget({super.key, required this.reportData});
@override
State<QcHeartRateStandardWidget> createState() =>
_QcHeartRateStandardWidgetState();
}
class _QcHeartRateStandardWidgetState extends State<QcHeartRateStandardWidget> {
@override
void setState(VoidCallback callback) {
super.setState(callback);
}
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
// 计算y轴的最大最小值
(double, double) _calculateYMinMax(List<QcTimeSeriesPoint> dataPoints) {
if (dataPoints.isEmpty) {
return (50.0, 150.0);
}
// 过滤掉无效数据点(值为-1的
final validPoints = dataPoints.where((point) => point.value >= 0).toList();
if (validPoints.isEmpty) {
return (50.0, 150.0);
}
// 找出数据中的实际最小值和最大值
double dataMin =
validPoints.map((point) => point.value).reduce((a, b) => a < b ? a : b);
double dataMax =
validPoints.map((point) => point.value).reduce((a, b) => a > b ? a : b);
// 计算最小值向下取整到10的倍数
double yMin = (dataMin / 10).floor() * 10.0;
// 如果最小值小于0设为0
if (yMin < 0) yMin = 0;
// 计算最大值向上取整到10的倍数
double yMax = (dataMax / 10).ceil() * 10.0;
// 确保至少有20的差值
if (yMax - yMin < 20) {
yMax = yMin + 20;
}
return (yMin, yMax);
}
@override
Widget build(BuildContext context) {
try {
if (widget.reportData == null || widget.reportData is! Map) {
return Container();
}
// 从reportData中获取hr数据
Map<String, dynamic> hrData = widget.reportData['hr'] ?? {};
if (hrData.isEmpty) {
return Container();
}
// 获取心率数据点
List<dynamic> dataList = hrData['data'] ?? [];
List<QcTimeSeriesPoint> dataPoints = [];
// 构建数据点(只保留值,不需要时间戳)
for (int i = 0; i < dataList.length; i++) {
dynamic value = dataList[i];
if (value == null || value == '') {
dataPoints.add(QcTimeSeriesPoint(-1));
} else {
double y = (value as num).toDouble();
dataPoints.add(QcTimeSeriesPoint(y));
}
}
// 计算动态的y轴范围
final (yMin, yMax) = _calculateYMinMax(dataPoints);
// 构建心率统计数据
Map<String, dynamic> avgHeartRate = {
'name': '平均心率'.tr,
'value': hrData['avg'].toInt() ?? 0,
'unit': '次/分'.tr,
};
Map<String, dynamic> baseHeartRate = {
'name': '基准心率'.tr,
'value': hrData['base'].toInt() ?? 0,
'unit': '次/分'.tr,
};
Map<String, dynamic> minHeartRate = {
'name': '最低心率'.tr,
'value': hrData['min'].toInt() ?? 0,
'unit': '次/分'.tr,
};
Map<String, dynamic> maxHeartRate = {
'name': '最高心率'.tr,
'value': hrData['max'].toInt() ?? 0,
'unit': '次/分'.tr,
};
// 构建正常范围字符串
String range = '';
if (baseHeartRate['value'] != 0) {
int baseValue = baseHeartRate['value'];
range = '${baseValue - 10}~${baseValue + 10}';
} else {
range = '60~100';
}
return Container(
width: double.infinity,
decoration: BoxDecoration(
color: themeController.currentColor.sc5,
borderRadius:
BorderRadius.circular(AppConstants().normal_container_radius),
),
child: Padding(
padding:
EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx),
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"心率数据".tr,
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: AppConstants().title_text_fontSize),
),
ClickableContainer(
backgroundColor: Colors.transparent,
highlightColor: Colors.white,
padding: EdgeInsetsDirectional.fromSTEB(
14.rpx, 10.rpx, 14.rpx, 10.rpx),
borderRadius: 0.rpx,
onTap: () {
if (AppConstants().ent_type ==
APPPackageType.MHT.code) {
showTipDialog(
context,
Container(
child: Text(
"心率数据是指用户在睡眠过程中基本心率数据,可初步判断睡眠中的心血管负荷及自主神经功能状态,为睡眠健康评估提供重要依据。"
.tr,
style: TextStyle(
fontSize: 26.rpx,
color: Colors.black,
),
),
),
backgroundColor: Color(0xFFFFFFFF),
colors: [
Color(0XFF1592AA),
Color(0xFF0C83A7),
Color(0xFF006FA3)
],
);
} else {
showTipDialog(
context,
Container(
child: Text(
"心率数据是指用户在睡眠过程中基本心率数据,可初步判断睡眠中的心血管负荷及自主神经功能状态,为睡眠健康评估提供重要依据。"
.tr,
style: TextStyle(
fontSize: 26.rpx,
color: themeController.currentColor.sc3,
),
),
),
backgroundColor: themeController.currentColor.sc17,
colors: AppConstants().thNormalButton,
);
}
},
child: Container(
padding:
EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0.rpx, 0),
width: 28.rpx,
height: 28.rpx,
child: SvgPicture.asset(
'assets/img/icon/explain.svg',
fit: BoxFit.cover,
color: themeController.currentColor.sc4,
),
),
),
],
),
),
SizedBox(
height: 31.rpx,
),
Padding(
padding:
EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 0.rpx, 0.rpx),
child: Column(
children: [
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
0.rpx, 0.rpx, 30.rpx, 0.rpx),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
width: 14.rpx,
height: 14.rpx,
decoration: BoxDecoration(
color: themeController.currentColor.sc2,
shape: BoxShape.circle,
),
),
SizedBox(width: 15.rpx),
Text(
'正常范围 '.tr + range,
style: TextStyle(
fontSize: AppConstants().smaller_text_fontSize,
color: themeController.currentColor.sc3,
),
),
],
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
0.rpx, 0.rpx, 30.rpx, 0.rpx),
child: Container(
width: double.infinity,
child: QcTimeSeriesChart(
yMin: yMin,
yMax: yMax,
dataPoints: dataPoints,
xSegmentCount: 11,
baseValue: hrData['base'].toDouble(), // 传入基准值
// baseValue: 65, // 传入基准值
baseLabel: '基准', // 可选的自定义标签
yAxisPadding: 20,
),
),
),
Padding(
padding: EdgeInsetsDirectional.fromSTEB(
0.rpx, 0.rpx, 0.rpx, 0.rpx),
child: Row(
children: [
_buildHeartRateItem(avgHeartRate),
_buildHeartRateItem(baseHeartRate),
_buildHeartRateItem(minHeartRate),
_buildHeartRateItem(maxHeartRate),
],
),
),
].divide(SizedBox(
height: 18.rpx,
)),
),
),
],
),
),
);
} catch (e) {
es.EasyDartModule.logger.error("心率基准绘制异常${e}");
return Container();
}
}
Widget _buildHeartRateItem(Map<String, dynamic> data) {
return Expanded(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 4.rpx, vertical: 4.rpx),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: Text(
"${data['name']}",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: AppConstants().normal_text_fontSize,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
SizedBox(height: 4.rpx),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
fit: FlexFit.loose,
child: Text(
"${data['value']}",
style: TextStyle(
color: themeController.currentColor.sc2,
fontSize: AppConstants().normal_text_fontSize,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
SizedBox(width: 6.rpx),
Flexible(
fit: FlexFit.loose,
child: Text(
"${data['unit']}",
style: TextStyle(
color: themeController.currentColor.sc3,
fontSize: AppConstants().small_text_fontSize,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
),
],
),
),
],
),
),
);
}
}