diff --git a/assets/mhlangs/zh_CN.json b/assets/mhlangs/zh_CN.json index db843b2..25d1928 100644 --- a/assets/mhlangs/zh_CN.json +++ b/assets/mhlangs/zh_CN.json @@ -208,5 +208,9 @@ "解除分享": "解除分享", "最高分": "最高分", "最低分": "最低分", - "本周平均分":"本周平均分" + "本周平均分":"本周平均分", + "本月平均分":"本月平均分", + "每日得分":"每日得分", + "每日得分介绍":"每日得分介绍", + "与上月对比":"与上月对比" } \ No newline at end of file diff --git a/lib/component/NullDataComponentWidget.dart b/lib/component/NullDataComponentWidget.dart index b4de066..693ab11 100644 --- a/lib/component/NullDataComponentWidget.dart +++ b/lib/component/NullDataComponentWidget.dart @@ -18,57 +18,50 @@ class _TestWidgetState extends State { Widget build(BuildContext context) { ThemeController themeController = Get.find(); return GestureDetector( - child: Scaffold( - backgroundColor: Colors.transparent, - key: scaffoldKey, - body: SafeArea( - top: true, - child: Container( - decoration: BoxDecoration( - color: Colors.transparent, + child: Container( + decoration: BoxDecoration( + color: Colors.transparent, + ), + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height * 1, + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0), + child: Container( + width: 56.rpx, + height: 69.rpx, + // width: double.infinity, + decoration: BoxDecoration(), + child: SvgPicture.asset( + 'assets/img/icon/nulldata.svg', + fit: BoxFit.cover, + color: themeController.currentColor.sc4.withOpacity(0.5), + ), + ), ), - width: MediaQuery.sizeOf(context).width, - height: MediaQuery.sizeOf(context).height * 1, - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: EdgeInsetsDirectional.fromSTEB(0, 0.rpx, 0, 0), - child: Container( - width: 56.rpx, - height: 69.rpx, - // width: double.infinity, - decoration: BoxDecoration(), - child: SvgPicture.asset( - 'assets/img/icon/nulldata.svg', - fit: BoxFit.cover, - color: themeController.currentColor.sc4.withOpacity(0.5), - ), + Container( + width: MediaQuery.sizeOf(context).width, + height: MediaQuery.sizeOf(context).height * 0.04, + constraints: BoxConstraints( + minHeight: 40, + ), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Text( + '暂无数据'.tr, + style: TextStyle( + fontFamily: "PingFangSC", + letterSpacing: 0.0, + fontSize: 26.rpx, + color: themeController.currentColor.sc4, ), ), - Container( - width: MediaQuery.sizeOf(context).width, - height: MediaQuery.sizeOf(context).height * 0.04, - constraints: BoxConstraints( - minHeight: 40, - ), - child: Align( - alignment: AlignmentDirectional(0, 0), - child: Text( - '暂无数据'.tr, - style: TextStyle( - fontFamily: "PingFangSC", - letterSpacing: 0.0, - fontSize: 26.rpx, - color: themeController.currentColor.sc4, - ), - ), - ), - ), - ], + ), ), - ), + ], ), ), ); diff --git a/lib/pages/common/selectDialog.dart b/lib/pages/common/selectDialog.dart index eedb6d1..c4bfc48 100644 --- a/lib/pages/common/selectDialog.dart +++ b/lib/pages/common/selectDialog.dart @@ -90,7 +90,7 @@ getOnePickers( ? const Color(0xFF011D33) // ✅ 选中项颜色 : Color(0xFF9AA0B3), // 未选中颜色 fontSize: 30.rpx, - fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + fontWeight: FontWeight.normal, ), ), ); @@ -197,7 +197,8 @@ Future showDateSelectionDialog(BuildContext context, alignment: Alignment.center, child: Text("确定", style: TextStyle( - fontSize: 30.rpx, color: stringToColor("#84F5FF"))), + fontSize: 30.rpx, + color: stringToColor("#84F5FF"))), ), ), ], @@ -744,7 +745,8 @@ Future showDayTimeSelectionDialog( alignment: Alignment.center, child: Text("确定".tr, style: TextStyle( - fontSize: 30.rpx, color: stringToColor("#84F5FF"))), + fontSize: 30.rpx, + color: stringToColor("#84F5FF"))), ), ), ], @@ -881,7 +883,8 @@ Future showOneSelectionDialog( height: 60.rpx, child: Text("确定".tr, style: TextStyle( - fontSize: 30.rpx, color: stringToColor("#84F5FF"))), + fontSize: 30.rpx, + color: stringToColor("#84F5FF"))), ), ), ], diff --git a/lib/pages/main_bottom/component/main_page_b_bottom_change.dart b/lib/pages/main_bottom/component/main_page_b_bottom_change.dart index 12d1f4e..2ce2153 100644 --- a/lib/pages/main_bottom/component/main_page_b_bottom_change.dart +++ b/lib/pages/main_bottom/component/main_page_b_bottom_change.dart @@ -1,5 +1,6 @@ import 'package:ef/ef.dart'; import 'package:flutter/material.dart'; +import 'package:vbvs_app/component/NullDataComponentWidget.dart'; import 'package:vbvs_app/pages/common/bezier_bottom_navigation_bar.dart'; import 'package:vbvs_app/pages/mh_page/MattressControl.dart'; import 'package:vbvs_app/pages/mh_page/homepage/mht_sleep_report_page.dart'; @@ -48,6 +49,7 @@ class _HomePageState extends State final List pages = [ NewHomePage(), + // NullDataWidget(), // PeopleInfoPage(), // Text('报告'), // RegisterPage(), diff --git a/lib/pages/mh_page/bluetooth.dart b/lib/pages/mh_page/bluetooth.dart index dd11fca..36411bc 100644 --- a/lib/pages/mh_page/bluetooth.dart +++ b/lib/pages/mh_page/bluetooth.dart @@ -33,6 +33,7 @@ class _BluetoothPageState extends State { Widget build(BuildContext context) { return LayoutBuilder(builder: (context, cc) { bodysize = cc; + final isBind = obsData['bind_type'] == 1; return GestureDetector( onTap: () => FocusScope.of(context).unfocus(), child: Container( @@ -158,7 +159,6 @@ class _BluetoothPageState extends State { const SizedBox(height: 24), - // 按钮列表 Expanded( child: ListView( padding: EdgeInsets.symmetric(horizontal: 30.rpx), @@ -166,23 +166,25 @@ class _BluetoothPageState extends State { _buildMenuButton( context, '详情', "/devicePeopleInfo", arguments: obsData), - _buildMenuButton( - context, - '人员资料', - "/peopleInfoPage", - arguments: obsData, - ), - _buildMenuButton( - context, '房间选择', "/roomPickerPage", - arguments: obsData), - _buildMenuButton(context, '设备校准', ""), - _buildMenuButton(context, '体征传感器', ""), - _buildMenuButton(context, 'WIFI配置', ""), - _buildMenuButton( - context, '睡眠习惯', "/sleepHabitPage"), - _buildMenuButton( - context, '分享设备', "/deviceSharePage", - arguments: obsData), + if (isBind) ...[ + _buildMenuButton( + context, + '人员资料', + "/peopleInfoPage", + arguments: obsData, + ), + _buildMenuButton( + context, '房间选择', "/roomPickerPage", + arguments: obsData), + _buildMenuButton(context, '设备校准', ""), + _buildMenuButton(context, '体征传感器', ""), + _buildMenuButton(context, 'WIFI配置', ""), + _buildMenuButton( + context, '睡眠习惯', "/sleepHabitPage"), + _buildMenuButton( + context, '分享设备', "/deviceSharePage", + arguments: obsData), + ], _buildMenuButton( context, obsData['bind_type'] == 1 ? '解绑' : '删除', @@ -243,6 +245,104 @@ class _BluetoothPageState extends State { ], ), ), + + // Expanded( + // child: Align( + // alignment: Alignment.bottomCenter, + // child: SingleChildScrollView( + // reverse: true, // 👈 optional,如果你想要滚动时内容从底部弹出 + // child: Column( + // crossAxisAlignment: + // CrossAxisAlignment.stretch, + // mainAxisSize: MainAxisSize.min, + + // children: [ + // _buildMenuButton( + // context, '详情', "/devicePeopleInfo", + // arguments: obsData), + // if (isBind) ...[ + // _buildMenuButton( + // context, + // '人员资料', + // "/peopleInfoPage", + // arguments: obsData, + // ), + // _buildMenuButton( + // context, '房间选择', "/roomPickerPage", + // arguments: obsData), + // _buildMenuButton(context, '设备校准', ""), + // _buildMenuButton(context, '体征传感器', ""), + // _buildMenuButton(context, 'WIFI配置', ""), + // _buildMenuButton( + // context, '睡眠习惯', "/sleepHabitPage"), + // _buildMenuButton( + // context, '分享设备', "/deviceSharePage", + // arguments: obsData), + // ], + // _buildMenuButton( + // context, + // obsData['bind_type'] == 1 ? '解绑' : '删除', + // "", + // onTap: () { + // if (obsData['bind_type'] == 1) { + // // 解绑弹窗 + // showUnbindConfirmDialog( + // context: context, + // title: "是否进行解绑?", + // onConfirm: () async { + // await deviceListController + // .unbindDevice(obsData); + // await deviceListController + // .getDeviceList(); + // MHTHomeController homeController = + // Get.find(); + // homeController + // .selectDevcie.value = ""; + // try { + // WebviewTestController + // webviewTestController = + // Get.find(); + // webviewTestController + // .web.jsbridge?.dart + // .unBindDevice(); + // } catch (e) { + // ef.log("[h5]通知列表更新报错:$e"); + // } + // Get.toNamed( + // "/mianPageBottomChange"); + // // 执行解绑逻辑 + // }, + // onCancel: () { + // // 点击取消后的逻辑 + // }, + // ); + // } else if (obsData['bind_type'] == 2) { + // // 删除弹窗 + // showDeleteDeviceConfirmDialog( + // context: context, + // title: "是否进行删除?", + // onConfirm: () async { + // await deviceListController + // .unbindDevice( + // obsData, + // ); + // await deviceListController + // .getDeviceList(); + // Get.toNamed( + // "/mianPageBottomChange"); + // }, + // onCancel: () { + // // 点击取消后的逻辑 + // }, + // ); + // } + // }, + // ), + // ], + // ), + // ), + // ), + // ), ], ), )), diff --git a/lib/pages/mh_page/component/easychart.dart b/lib/pages/mh_page/component/easychart.dart new file mode 100644 index 0000000..a80d77b --- /dev/null +++ b/lib/pages/mh_page/component/easychart.dart @@ -0,0 +1,191 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:ef/base/chart/drawer.dart'; +import 'package:ef/base/chart/easychart.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; + +enum ChartDisplayMode { line, bar, both, dualBar } + +class LineView extends StatelessWidget { + final List xLabels; + final List yLabels; + final double barWidth; + final int xCount; + final int yCount; + final List points; + final ChartDisplayMode displayMode; + final List? barColors; + final List>? dualBarPoints; + final String? xUnit; + final List tips; + final double bottomPadding; + const LineView({ + super.key, + required this.xLabels, + required this.yLabels, + required this.xCount, + required this.yCount, + required this.points, + required this.barWidth, + required this.tips, + this.xUnit = '', + this.bottomPadding = 100, + this.displayMode = ChartDisplayMode.bar, + this.barColors, // 添加进构造函数 + this.dualBarPoints, + }); + + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: (a, b) { + return Container( + alignment: Alignment.topCenter, + margin: EdgeInsets.only( + left: 43.rpx, + bottom: bottomPadding.rpx, + ), + child: EasyChartView( + size: Size(b.maxWidth, 220.rpx), + drawer: ChartDrawer( + xAxis: ChartAxis( + intent: 44, + entintent: 15, + labels: xLabels, + count: xCount, + ), + yAxis: ChartAxis( + color: Colors.transparent, + isX: false, + intent: 0, + entintent: 45.rpx.toInt(), + labels: yLabels, + count: yCount, + ), + ondrawer: (canvas, size, drawer) { + canvas.scale(1, -1); + ChartLables.drawText( + canvas, + xUnit!, + Offset(drawer.yAxis.labels.first.offset.dx, + -drawer.drawsize.height + 0), + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx, + ), + ); + canvas.scale(1, -1); + // 虚线 + for (var i = 1; + i < drawer.yAxis.labels.first.labels.length; + i++) { + drawer.drawHorizontalDashedLine( + canvas, + drawer.yAxis.labels.first.steps[i].dy, + drawer.drawsize.width, + dashWidth: 3, + dashSpace: 3, + paint: Paint()..color = Colors.grey, + ); + } + + if (displayMode == ChartDisplayMode.line || + displayMode == ChartDisplayMode.both) { + drawer.drawcurveline( + canvas, + points, + Paint() + ..color = Color(0xFF00C1AA) + ..strokeWidth = 1.5, + ); + drawer.drawpoints( + canvas, + points, + 5, + Paint()..color = Color(0Xff00C1AA), + values: tips, + ); + } + + if (displayMode == ChartDisplayMode.bar || + displayMode == ChartDisplayMode.both) { + for (var i = 0; i < points.length; i++) { + final offset = points[i]; + // final color = (barColors != null && barColors!.length > i) + // ? barColors![i] + // : Colors.yellow.withAlpha(100 + i * 10); // 默认回退 + drawer.drawBar( + canvas, + Offset(offset.dx, 0), + barWidth, + offset.dy, + + Paint() + ..color = barColors!.length == 0 + ? Colors.white + : stringToColor(barColors![i]), + value: tips[i], + ); + } + } + if ((displayMode == ChartDisplayMode.dualBar || + displayMode == ChartDisplayMode.both) && + dualBarPoints != null) { + for (int i = 0; i < dualBarPoints!.length; i++) { + final List bar = dualBarPoints![i]; + if (bar.length < 3) continue; + + final Offset deep = bar[0]; // 深睡:起点 -> deep.dy + final Offset light = bar[1]; // 浅睡:起点 -> light.dy + final Offset total = bar[2]; // 总睡眠:起点 -> total.dy + + const String deepColor = "#21AD5D"; // 深睡 + const String lightColor = "#45D989"; // 浅睡 + const String remainColor = "#D3D3D3"; // 剩余 + + // 画深睡 + if (deep.dy > 0) { + drawer.drawBar( + canvas, + Offset(deep.dx, 0), + barWidth, + deep.dy, + value: tips[i], + Paint()..color = stringToColor(deepColor), + ); + } + + // 画浅睡(从 deep.dy 开始) + final double lightHeight = light.dy - deep.dy; + if (lightHeight > 0) { + drawer.drawBar( + canvas, + Offset(light.dx, deep.dy), + barWidth, + lightHeight, + Paint()..color = stringToColor(lightColor), + ); + } + + // 判断是否需要画灰色剩余段和文字 + final double remainHeight = total.dy - light.dy; + if (remainHeight > 0.01) { + // 灰色部分 + drawer.drawBar( + canvas, + Offset(total.dx, light.dy), + barWidth, + remainHeight, + Paint() + ..color = stringToColor(remainColor).withOpacity(0.8), + ); + } + // 若已满,不绘制灰色段、也不显示文字(不做任何处理) + } + } + }, + ), + )); + }); + } +} diff --git a/lib/pages/mh_page/edit_bed.dart b/lib/pages/mh_page/edit_bed.dart index 622d1c0..39d26d8 100644 --- a/lib/pages/mh_page/edit_bed.dart +++ b/lib/pages/mh_page/edit_bed.dart @@ -66,7 +66,7 @@ class _EditBedPageState extends State { children: [ // 中间居中的标题 Text( - '智能床名称', + '智能设备名称', textAlign: TextAlign.center, style: TextStyle( color: Colors.white, @@ -123,7 +123,7 @@ class _EditBedPageState extends State { textAlign: TextAlign.center, decoration: InputDecoration( - hintText: "请输入床的名称", + hintText: "请输入设备的名称", contentPadding: const EdgeInsetsDirectional .fromSTEB(10, 0, 10, 0), diff --git a/lib/pages/mh_page/test/WebviewTestModel.dart b/lib/pages/mh_page/test/WebviewTestModel.dart index a9d1e2b..5a29b91 100644 --- a/lib/pages/mh_page/test/WebviewTestModel.dart +++ b/lib/pages/mh_page/test/WebviewTestModel.dart @@ -32,8 +32,8 @@ class WebviewTestController extends GetControllerEx { WebviewTestController() : super(WebviewTestModel()) { web = WebviewHelper( jsbridge: buildsdk( - father: this, - clientId: '494641114', + // father: this, + // clientId: '494641114', // dbgserverUrl: 'ws://192.168.1.2:9001', ), settings: buildsettings(), diff --git a/lib/pages/sleep_report/chart/SegmentedCirclePainter.dart b/lib/pages/sleep_report/chart/SegmentedCirclePainter.dart index 79c3abe..9d5de36 100644 --- a/lib/pages/sleep_report/chart/SegmentedCirclePainter.dart +++ b/lib/pages/sleep_report/chart/SegmentedCirclePainter.dart @@ -88,7 +88,7 @@ class SegmentedCircleWithCenterWidget extends StatelessWidget { ), centerWidget, // 放置自定义的中心 Widget Positioned( - bottom: 200.rpx, + bottom: 140.rpx, right: 60.rpx, // 放置在右侧 child: SvgPicture.asset( _getTrendIcon(trend), diff --git a/lib/pages/sleep_report/component/DailyDataWidget.dart b/lib/pages/sleep_report/component/DailyDataWidget.dart new file mode 100644 index 0000000..ae71b4a --- /dev/null +++ b/lib/pages/sleep_report/component/DailyDataWidget.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/pages/sleep_report/component/AIAdviceWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/BreatheCard.dart'; +import 'package:vbvs_app/pages/sleep_report/component/BreathePauseNewWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/BreatheStandardWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/CompareSleepWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/DiseasePercentsWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/HeartChangeWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/HeartHealthWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/HeartPointWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/HeartRateCard.dart'; +import 'package:vbvs_app/pages/sleep_report/component/HeartRateStandardWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SkinPercentWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SleepCard.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SleepScoreWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SleepView.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SnoreViewWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart'; + +Widget DailyDataWidget( + Map sleepReport, + GlobalKey sleepCardKey, + GlobalKey heartRateCardKey, + GlobalKey breatheCardKey, + dynamic data, +) { + List _buildSectionList() { + EdgeInsetsDirectional padding = + EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 25.rpx); + + return [ + SleepScoreWidget(sleepReport: sleepReport), + SleepViewWidget(sleepReport: sleepReport), + SleepCard( + key: sleepCardKey, + sleepReport: sleepReport, + highlightItem: data['itemName'], + ), + CompareSleepWidget(sleepReport: sleepReport), + HeartPointWidget(sleepReport: sleepReport), + AIAdviceWidget(sleepReport: sleepReport), + HeartRateStandardWidget(sleepReport: sleepReport), + HeartRateCard( + key: heartRateCardKey, + sleepReport: sleepReport, + highlightItem: data['itemName'], + ), + HeartChangeWidget(sleepReport: sleepReport),//心率变异性 + BreatheStandardWidget(sleepReport: sleepReport), + BreatheCard( + key: breatheCardKey, + sleepReport: sleepReport, + highlightItem: data['itemName'], + ), + SnoreViewWidgetWidget(sleepReport: sleepReport), + BreathePauseNewWidget(sleepReport: sleepReport), + HeartHealthWidget(sleepReport: sleepReport), + DiseasePercentsWidget(sleepReport: sleepReport), + ZiZhuShenJingPercentWidget(sleepReport: sleepReport), + SkinPercentWidget(sleepReport: sleepReport), + ] + .map((widget) => Padding( + padding: padding, + child: SizedBox(width: double.infinity, child: widget), + )) + .toList(); + } + + return Column( + children: _buildSectionList(), + ); +} diff --git a/lib/pages/sleep_report/component/MonthDataWidget.dart b/lib/pages/sleep_report/component/MonthDataWidget.dart new file mode 100644 index 0000000..5b4ef4a --- /dev/null +++ b/lib/pages/sleep_report/component/MonthDataWidget.dart @@ -0,0 +1,757 @@ +import 'package:ef/base/chart/drawer.dart'; +import 'package:flutter/material.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/pages/mh_page/component/easychart.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SleepCard.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SleepChartWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/TrendDataTablePage.dart'; +import 'package:vbvs_app/pages/sleep_report/component/TrendDataTextPage.dart'; +import 'package:vbvs_app/pages/sleep_report/component/WeekSleepScoreWidget.dart'; + +Widget MonthDataWidget( + Map sleepReport, + dynamic data, +) { + List _buildSectionList() { + EdgeInsetsDirectional padding = + EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 25.rpx); + + return [ + AvgSleepScoreWidget( + sleepReport: sleepReport, + mediumLabel: "本月平均分", + ), //睡眠评分 + SleepChartContainer( + title: "每日得分", + tipText: "每日得分介绍", + sleepReport: sleepReport, + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: buildMonthlyChartData( + sleepReport['scoreList'])['daysInMonth'], //最大值30 + q: buildMonthlyChartData(sleepReport['scoreList'])[ + 'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: List.from( + sleepReport['scoreList']['xLable'].map((e) => e['name']), + ), + indexs: (sleepReport['scoreList']['xLable'] as List) + .map((e) => e['index'] as int) + .toList(), + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: sleepReport['scoreList']['yRange']['min'] ?? 0.0, + max: sleepReport['scoreList']['yRange']['max'] ?? 100.0, + q: 100, + labels: List.from( + sleepReport['scoreList']['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-15.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['scoreList']['data'], '分', 1), + xCount: buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth'] + .toInt(), + yCount: sleepReport['scoreList']['yLable'].length, + points: buildMonthlyChartData(sleepReport['scoreList'])['points'], + displayMode: ChartDisplayMode.bar, + barColors: buildMonthlyChartData(sleepReport['scoreList'])['colors'], + xUnit: sleepReport['scoreList']['yUnit'], + barWidth: 0.5, + ), + showLabel: sleepReport['scoreList']['type'], + ), + SleepCard( + sleepReport: sleepReport, + highlightItem: data['itemName'], + ), + IndicatorCompareCard( + title: "与上月对比", + headers: ["名称", "上月", "本月", "参考范围"], + tooltip: "与上月对比提示", + rows: (sleepReport['cwl'] ?? []).map>((item) { + return [ + Text( + item['name']?.toString() ?? '-', + style: TextStyle(color: Color(0xFFFFFFFF), fontSize: 26.rpx), + ), + Text( + item['lastCurr']?.toString() ?? '-', + style: TextStyle(color: Color(0xFFFFFFFF), fontSize: 26.rpx), + ), + Text( + item['curr']?.toString() ?? '-', + style: TextStyle( + color: item["currColor"] ?? Color(0xFFFFFFFF), + fontSize: 26.rpx, + ), + ), + Text( + item['range']?.toString() ?? '-', + style: TextStyle(color: Color(0xFFFFFFFF), fontSize: 26.rpx), + ), + ]; + }).toList(), + ), + SleepChartContainer( + title: "本月睡眠时长", + tipText: "每日得分介绍", + sleepReport: sleepReport, + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: buildMonthlyChartData( + sleepReport['scoreList'])['daysInMonth'], //最大值30 + q: buildMonthlyChartData(sleepReport['scoreList'])[ + 'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: List.from( + sleepReport['scoreList']['xLable'].map((e) => e['name']), + ), + indexs: (sleepReport['scoreList']['xLable'] as List) + .map((e) => e['index'] as int) + .toList(), + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: sleepReport['scoreList']['yRange']['min'] ?? 0.0, + max: sleepReport['scoreList']['yRange']['max'] ?? 10.0, + q: 10, + labels: List.from( + sleepReport['csd']['yLable'].map((e) => e['name'].toString()), + ), + offset: Offset(-15.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['scoreList']['data'], '小时', 1), + xCount: buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth'] + .toInt(), + yCount: sleepReport['csd']['yLable'].length, + points: [], + dualBarPoints: buildTripleBarData(sleepReport['csd']), + displayMode: ChartDisplayMode.dualBar, + xUnit: sleepReport['csd']['yUnit'], + barWidth: 0.5, + ), + showLabel: sleepReport['csd']['type'], + ), + + TrendDataTablePage( + title: sleepReport['dysp'][0]['name'], + tipText: "入睡时间趋势提示", + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: buildMonthlyChartData( + sleepReport['scoreList'])['daysInMonth'], //最大值30 + q: buildMonthlyChartData(sleepReport['scoreList'])[ + 'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: List.from( + sleepReport['scoreList']['xLable'].map((e) => e['name']), + ), + indexs: (sleepReport['scoreList']['xLable'] as List) + .map((e) => e['index'] as int) + .toList(), + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: 0, + max: 5, + q: 5, + labels: List.from( + sleepReport['dysp'][0]['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-30.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['dysp'][0]['value'], '入睡时间:', 0), + xCount: + buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth'] + .toInt(), + yCount: sleepReport['dysp'][0]['yLable'].length, + points: buildGeneralPoints(sleepReport['dysp'][0]), + displayMode: ChartDisplayMode.line, + xUnit: sleepReport['dysp'][0]['yUnit'], + barWidth: 0.5, + bottomPadding: 16, + ), + padding: 45.rpx), + TrendDataTablePage( + title: sleepReport['dysp'][1]['name'], + tipText: "起床时间趋势提示", + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: buildMonthlyChartData( + sleepReport['scoreList'])['daysInMonth'], //最大值30 + q: buildMonthlyChartData(sleepReport['scoreList'])[ + 'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: List.from( + sleepReport['scoreList']['xLable'].map((e) => e['name']), + ), + indexs: (sleepReport['scoreList']['xLable'] as List) + .map((e) => e['index'] as int) + .toList(), + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: 0, + max: 4, + q: 4, + labels: List.from( + sleepReport['dysp'][1]['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-30.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['dysp'][1]['value'], '起床时间:', 0), + xCount: + buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth'] + .toInt(), + yCount: sleepReport['dysp'][1]['yLable'].length, + points: buildGeneralPoints(sleepReport['dysp'][1]), + displayMode: ChartDisplayMode.line, + xUnit: sleepReport['dysp'][1]['yUnit'], + barWidth: 0.5, + bottomPadding: 16, + ), + padding: 45.rpx), + TrendDataTablePage( + title: sleepReport['dysp'][2]['name'], + tipText: "心率基准趋势提示", + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: buildMonthlyChartData( + sleepReport['scoreList'])['daysInMonth'], //最大值30 + q: buildMonthlyChartData(sleepReport['scoreList'])[ + 'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: List.from( + sleepReport['scoreList']['xLable'].map((e) => e['name']), + ), + indexs: (sleepReport['scoreList']['xLable'] as List) + .map((e) => e['index'] as int) + .toList(), + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: 0, + max: 4, + q: 4, + labels: List.from( + sleepReport['dysp'][2]['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-15.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['dysp'][2]['value'], '次', 1), + xCount: + buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth'] + .toInt(), + yCount: sleepReport['dysp'][2]['yLable'].length, + points: buildGeneralPoints(sleepReport['dysp'][2]), + displayMode: ChartDisplayMode.line, + xUnit: sleepReport['dysp'][2]['yUnit'], + barWidth: 0.5, + bottomPadding: 16, + ), + padding: 45.rpx), + TrendDataTablePage( + title: sleepReport['dysp'][3]['name'], + tipText: "心率变异性趋势提示", + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: buildMonthlyChartData( + sleepReport['scoreList'])['daysInMonth'], //最大值30 + q: buildMonthlyChartData(sleepReport['scoreList'])[ + 'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: List.from( + sleepReport['scoreList']['xLable'].map((e) => e['name']), + ), + indexs: (sleepReport['scoreList']['xLable'] as List) + .map((e) => e['index'] as int) + .toList(), + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: 0, + max: 5, + q: 5, + labels: List.from( + sleepReport['dysp'][3]['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-15.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['dysp'][3]['value'], '毫秒', 1), + xCount: + buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth'] + .toInt(), + yCount: sleepReport['dysp'][3]['yLable'].length, + points: buildGeneralPoints(sleepReport['dysp'][3]), + displayMode: ChartDisplayMode.line, + xUnit: sleepReport['dysp'][3]['yUnit'], + barWidth: 0.5, + bottomPadding: 16, + ), + padding: 45.rpx), + TrendDataTablePage( + title: sleepReport['dysp'][4]['name'], + tipText: "呼吸基准趋势提示", + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: buildMonthlyChartData( + sleepReport['scoreList'])['daysInMonth'], //最大值30 + q: buildMonthlyChartData(sleepReport['scoreList'])[ + 'daysInMonth'], //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: List.from( + sleepReport['scoreList']['xLable'].map((e) => e['name']), + ), + indexs: (sleepReport['scoreList']['xLable'] as List) + .map((e) => e['index'] as int) + .toList(), + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: 0, + max: 5, + q: 5, + labels: List.from( + sleepReport['dysp'][4]['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-15.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['dysp'][4]['value'], '次/分', 1), + xCount: + buildMonthlyChartData(sleepReport['scoreList'])['daysInMonth'] + .toInt(), + yCount: sleepReport['dysp'][4]['yLable'].length, + points: buildGeneralPoints(sleepReport['dysp'][4]), + displayMode: ChartDisplayMode.line, + xUnit: sleepReport['dysp'][4]['yUnit'], + barWidth: 0.5, + bottomPadding: 16, + ), + padding: 45.rpx), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("MAC号:${data['mac']}", + style: TextStyle( + color: Color(0xFFD3D3D3).withOpacity(0.2), fontSize: 18.rpx)) + ], + ), + ] + .map((widget) => Padding( + padding: padding, + child: SizedBox(width: double.infinity, child: widget), + )) + .toList(); + } + + return Column( + children: _buildSectionList(), + ); +} + +List buildValueTexts( + List data, + String unit, + int direction, // 0 表示左侧,1 表示右侧 +) { + if (data.isEmpty) return []; + + return data + .where((item) => item.containsKey('value') && item.containsKey('st')) + .map((item) { + final val = item['value'].toString(); + + // 单位位置 + final prefix = direction == 1 ? '' : unit; + final suffix = direction == 1 ? unit : ''; + + // 中文年月日格式 + DateTime date = DateTime.fromMillisecondsSinceEpoch(item['st']); + final dateStr = "${date.year}年 ${date.month}月 ${date.day}日"; + + return "$prefix$val$suffix\n$dateStr"; + }).toList(); +} + +Map buildMonthlyChartData(Map dyspData) { + final List> data = + (dyspData['data'] as List?)?.whereType>().toList() ?? + []; + final List> yLabels = (dyspData['yLable'] as List?) + ?.whereType>() + .toList() ?? + []; + final List> types = + (dyspData['type'] as List?)?.whereType>().toList() ?? + []; + + if (data.isEmpty || yLabels.length < 2) { + return { + 'points': [], + 'colors': [], + 'daysInMonth': 0.0, + }; + } + + // 解析 yLabel 为数值 + List yValues = + yLabels.map((e) => double.tryParse(e['name'].toString()) ?? 0).toList(); + double minY = yValues.first; + double maxY = yValues.last; + double yRange = maxY - minY; + double yStepCount = (yValues.length - 1).toDouble(); // 👈 用 double + + // 获取当前月的总天数(以第一条数据为准) + DateTime baseDate = DateTime.fromMillisecondsSinceEpoch(data.first['st']); + double daysInMonth = DateTime(baseDate.year, baseDate.month + 1, 0) + .day + .toDouble(); // 👈 改为 double + + List points = []; + List colors = []; + + for (var item in data) { + final int st = item['st']; + final int level = item['level']; + final double value = (item['value'] as num).toDouble(); + + DateTime date = DateTime.fromMillisecondsSinceEpoch(st); + double day = date.day.toDouble(); + + // x: day - 1(0 起始) + double x = day - 1; + + // // y: 归一化 + // double y = ((value - minY) / yRange) * yStepCount; + + // 获取 color + String color = types.firstWhere( + (e) => e['level'] == level, + orElse: () => {'color': '#CCCCCC'}, + )['color']; + + points.add(Offset(x, value)); + colors.add(color); + } + + return { + 'points': points, + 'colors': colors, + 'daysInMonth': daysInMonth, + }; +} + +List buildGeneralPoints(Map dyspData) { + final values = + (dyspData['value'] as List?)?.whereType>().toList(); + final yLabels = + (dyspData['yLable'] as List?)?.whereType>().toList(); + if (values == null || values.isEmpty || yLabels == null || yLabels.length < 2) + return []; + + bool isTimeAxis = yLabels.first['name'].toString().contains(":"); + + double labelToValue(String name) { + if (isTimeAxis) { + final parts = name.split(':'); + int hour = int.parse(parts[0]); + int minute = int.parse(parts[1]); + return hour * 60 + minute * 1.0; + } else { + return double.tryParse(name) ?? 0; + } + } + + List labelValues = + yLabels.map((e) => labelToValue(e['name'])).toList(); + + // 处理时间轴跨午夜 + bool crossMidnight = false; + if (isTimeAxis && labelValues.last < labelValues.first) { + crossMidnight = true; + double base = labelValues.first; + for (int i = 1; i < labelValues.length; i++) { + if (labelValues[i] < base) { + labelValues[i] += 1440; // +24小时,单位分钟 + } + } + } + + List points = []; + + for (var item in values) { + DateTime date = DateTime.fromMillisecondsSinceEpoch(item['st']); + double x = (date.day - 1).toDouble(); // 👈 按照“日”定位 X 坐标 + + dynamic rawValue = item['value']; + double valueMinBased; + + if (isTimeAxis && rawValue is String) { + final parts = rawValue.split(':'); + int hour = int.parse(parts[0]); + int minute = int.parse(parts[1]); + valueMinBased = hour * 60 + minute * 1.0; + + if (crossMidnight && valueMinBased < labelValues.first) { + valueMinBased += 1440; + } + } else if (!isTimeAxis && rawValue is num) { + valueMinBased = rawValue.toDouble(); + } else { + continue; + } + + if (valueMinBased < labelValues.first) valueMinBased = labelValues.first; + if (valueMinBased > labelValues.last) valueMinBased = labelValues.last; + + double y = getYPositionBySegmentedLabels(labelValues, valueMinBased); + + points.add(Offset(x, y)); + } + + return points; +} + +double getYPositionBySegmentedLabels(List labels, double value) { + if (value <= labels.first) return 0.0; + if (value >= labels.last) return labels.length - 1.0; + + for (int i = 0; i < labels.length - 1; i++) { + double low = labels[i]; + double high = labels[i + 1]; + if (value >= low && value <= high) { + double ratio = (value - low) / (high - low); + return i + ratio; + } + } + // 不应该走到这里,默认返回0 + return 0.0; +} + +//多色柱状图坐标 +List> buildTripleBarData(Map csd) { + final List> data = + (csd['data'] as List?)?.whereType>().toList() ?? []; + + final List> yLabels = + (csd['yLable'] as List?)?.whereType>().toList() ?? + []; + + if (data.isEmpty || yLabels.isEmpty) return []; + + double minY = double.tryParse(yLabels.first['name'].toString()) ?? 0; + double maxY = double.tryParse(yLabels.last['name'].toString()) ?? 10; + + List> points = []; + + for (var item in data) { + double dst = (item['dst'] as num?)?.toDouble() ?? 0; + double lst = (item['lst'] as num?)?.toDouble() ?? 0; + double slt = (item['slt'] as num?)?.toDouble() ?? 0; + + // 限制 y 值范围 + dst = dst.clamp(minY, maxY); + lst = lst.clamp(minY, maxY); + slt = slt.clamp(minY, maxY); + + // 计算 X:本月第几天(从 0 开始) + DateTime date = DateTime.fromMillisecondsSinceEpoch(item['st']); + int dayOfMonth = date.day; // 1 ~ 31 + double x = (dayOfMonth - 1).toDouble(); // 转为从 0 开始 + + points.add([ + Offset(x, dst), // 深睡终点 + Offset(x, dst + lst), // 浅睡+深睡终点 + Offset(x, slt), // 总睡眠终点(第三段) + ]); + } + + return points; +} diff --git a/lib/pages/sleep_report/component/SleepChartWidget.dart b/lib/pages/sleep_report/component/SleepChartWidget.dart new file mode 100644 index 0000000..5b0fc3c --- /dev/null +++ b/lib/pages/sleep_report/component/SleepChartWidget.dart @@ -0,0 +1,114 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/GradientLine.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/SnoreWaveform.dart'; +import 'package:EasyDartModule/EasyDartModule.dart' as es; + +class SleepChartContainer extends StatelessWidget { + final Map sleepReport; + final String title; + final String tipText; + final Widget? chartContent; + final List showLabel; + const SleepChartContainer( + {Key? key, + required this.sleepReport, + required this.title, + required this.tipText, + required this.chartContent, + required this.showLabel}) + : super(key: key); + + @override + Widget build(BuildContext context) { + + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: themeController.currentColor.sc5, + borderRadius: BorderRadius.circular(25.rpx), + ), + padding: EdgeInsets.fromLTRB(26.rpx, 29.rpx, 26.rpx, 45.rpx), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: TextStyle( + color: Colors.white, + fontSize: 30.rpx, + ), + ), + GestureDetector( + onTap: () { + showTipDialog( + context, + Container( + child: Text( + tipText, + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: Container( + width: 30.rpx, + height: 30.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + color: Color(0xFFD3D3D3), + ), + )), + ], + ), + const SizedBox(height: 16), + Container( + child: chartContent ?? Container(), + ), + Wrap( + spacing: 32.rpx, + runSpacing: 20.rpx, + children: showLabel.map((item) { + return Container( + padding: EdgeInsets.all(5), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 20.rpx, + height: 20.rpx, + decoration: BoxDecoration( + color: stringToColor("${item["color"]}"), + borderRadius: BorderRadius.circular(10.rpx), + ), + ), + SizedBox(width: 17.rpx), + Text( + item["name"], + style: TextStyle( + color: Colors.white, + fontSize: 24.rpx, + ), + ), + ], + ), + ); + }).toList(), + ), + ], + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/SleepScoreWidget.dart b/lib/pages/sleep_report/component/SleepScoreWidget.dart index 32c4479..651144e 100644 --- a/lib/pages/sleep_report/component/SleepScoreWidget.dart +++ b/lib/pages/sleep_report/component/SleepScoreWidget.dart @@ -33,166 +33,263 @@ class _SleepScoreWidgetState extends State { @override Widget build(BuildContext context) { - try { + try { if (widget.sleepReport == null || - widget.sleepReport is! Map || - widget.sleepReport.isEmpty) { - return Container(); - } - - List showLabel = widget.sleepReport['score']['type']; - List stages = widget.sleepReport['score']['stages']; - List segments = parseSegments(widget.sleepReport); + widget.sleepReport is! Map || + widget.sleepReport.isEmpty) { + return Container(); + } - return Container( - width: double.infinity, - // decoration: BoxDecoration(color: Colors.green), - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx), - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - width: double.infinity, - decoration: BoxDecoration(), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - decoration: BoxDecoration(), - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - constraints: BoxConstraints(maxWidth: 150.rpx), - child: Text( - '30天平均分'.tr, - style: TextStyle( - color: Color(0xFFD3D3D3), - fontSize: 26.rpx, - letterSpacing: 0.0, - ), - maxLines: 1, + List showLabel = widget.sleepReport['score']['type']; + List stages = widget.sleepReport['score']['stages']; + List segments = parseSegments(widget.sleepReport); + + return Container( + width: double.infinity, + // decoration: BoxDecoration(color: Colors.green), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Stack( + alignment: Alignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + constraints: BoxConstraints(maxWidth: 150.rpx), + child: Text( + '30天平均分'.tr, + style: TextStyle( + color: Color(0xFFD3D3D3), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + maxLines: 1, + ), + ), + Text( + '${widget.sleepReport['score']?['avg']}', + style: TextStyle( + color: Colors.white, + fontSize: 48.rpx, + letterSpacing: 0.0, + ), + ), + ].divide(SizedBox(height: 56.rpx)), ), - ), - Text( - '${widget.sleepReport['score']?['avg']}', - style: TextStyle( - color: Colors.white, - fontSize: 48.rpx, - letterSpacing: 0.0, - ), - ), - ].divide(SizedBox(height: 56.rpx)), - ), - ), - Expanded( - child: Container( - decoration: BoxDecoration(), - child: SegmentedCircleWithCenterWidget( - // segments: [ - // SegmentData(color: Colors.red, value: 30), - // SegmentData(color: Colors.green, value: 20), - // SegmentData(color: Colors.blue, value: 40), - // SegmentData(color: Colors.orange, value: 10), - // ], - segments: segments, - strokeWidth: 8, - gapAngle: 8, - centerWidget: Container( - child: Column( + Column( + mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - "睡眠评分".tr, + '睡眠等级'.tr, style: TextStyle( - color: stringToColor("#FFFFFF"), - fontSize: - AppConstants().normal_text_fontSize), + color: Color(0xFFD3D3D3), + fontSize: 26.rpx, + letterSpacing: 0.0, + ), ), Text( - '${widget.sleepReport['score']?['score']}', + '${getSleepLevel(widget.sleepReport)}', style: TextStyle( - color: stringToColor("#FF9F66"), - fontSize: 100.rpx), + color: stringToColor("#FF9F66"), + fontSize: 48.rpx, + letterSpacing: 0.0, + ), ), - ], + ].divide(SizedBox(height: 56.rpx)), ), - ), - trend: widget.sleepReport['score']?['trend'], + ], ), - ), - ), - Container( - decoration: BoxDecoration(), - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '睡眠等级'.tr, - style: TextStyle( - color: Color(0xFFD3D3D3), - fontSize: 26.rpx, - letterSpacing: 0.0, + SizedBox( + width: 256.rpx, + height: 256.rpx, + child: SegmentedCircleWithCenterWidget( + // segments: [ + // SegmentData(color: Colors.red, value: 30), + // SegmentData(color: Colors.green, value: 20), + // SegmentData(color: Colors.blue, value: 40), + // SegmentData(color: Colors.orange, value: 10), + // ], + segments: segments, + strokeWidth: 6.rpx, + gapAngle: 8, + centerWidget: Container( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "睡眠评分".tr, + style: TextStyle( + color: stringToColor("#FFFFFF"), + fontSize: + AppConstants().normal_text_fontSize), + ), + Text( + '${widget.sleepReport['score']?['score']}', + style: TextStyle( + color: stringToColor("#FF9F66"), + fontSize: 100.rpx), + ), + ], + ), ), + trend: widget.sleepReport['score']?['trend'], ), - Text( - '${getSleepLevel(widget.sleepReport)}', - style: TextStyle( - color: stringToColor("#FF9F66"), - fontSize: 48.rpx, - letterSpacing: 0.0, - ), - ), - ].divide(SizedBox(height: 56.rpx)), - ), - ), - ].divide(SizedBox( - width: 33.rpx, - )), - ), - ), - Wrap( - spacing: 32.rpx, - runSpacing: 20.rpx, - children: showLabel.map((item) { - return Container( - padding: EdgeInsets.all(5.rpx), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 20.rpx, - height: 20.rpx, - decoration: BoxDecoration( - color: stringToColor("${item["color"]}"), - borderRadius: BorderRadius.circular(10.rpx), - ), - ), - SizedBox(width: 17.rpx), - Text( - item["name"] + '(' + item['range'] + ")", - style: TextStyle( - color: Colors.white, - fontSize: 24.rpx, - ), - ), + ) ], + ) + + // Row( + // mainAxisSize: MainAxisSize.max, + // children: [ + // Container( + // decoration: BoxDecoration(), + // child: Column( + // mainAxisSize: MainAxisSize.max, + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Container( + // constraints: BoxConstraints(maxWidth: 150.rpx), + // child: Text( + // '30天平均分'.tr, + // style: TextStyle( + // color: Color(0xFFD3D3D3), + // fontSize: 26.rpx, + // letterSpacing: 0.0, + // ), + // maxLines: 1, + // ), + // ), + // Text( + // '${widget.sleepReport['score']?['avg']}', + // style: TextStyle( + // color: Colors.white, + // fontSize: 48.rpx, + // letterSpacing: 0.0, + // ), + // ), + // ].divide(SizedBox(height: 56.rpx)), + // ), + // ), + // Expanded( + // child: Container( + // decoration: BoxDecoration(), + // child: SegmentedCircleWithCenterWidget( + // // segments: [ + // // SegmentData(color: Colors.red, value: 30), + // // SegmentData(color: Colors.green, value: 20), + // // SegmentData(color: Colors.blue, value: 40), + // // SegmentData(color: Colors.orange, value: 10), + // // ], + // segments: segments, + // strokeWidth: 8, + // gapAngle: 8, + // centerWidget: Container( + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Text( + // "睡眠评分".tr, + // style: TextStyle( + // color: stringToColor("#FFFFFF"), + // fontSize: + // AppConstants().normal_text_fontSize), + // ), + // Text( + // '${widget.sleepReport['score']?['score']}', + // style: TextStyle( + // color: stringToColor("#FF9F66"), + // fontSize: 100.rpx), + // ), + // ], + // ), + // ), + // trend: widget.sleepReport['score']?['trend'], + // ), + // ), + // ), + // Container( + // decoration: BoxDecoration(), + // child: Column( + // mainAxisSize: MainAxisSize.max, + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // Text( + // '睡眠等级'.tr, + // style: TextStyle( + // color: Color(0xFFD3D3D3), + // fontSize: 26.rpx, + // letterSpacing: 0.0, + // ), + // ), + // Text( + // '${getSleepLevel(widget.sleepReport)}', + // style: TextStyle( + // color: stringToColor("#FF9F66"), + // fontSize: 48.rpx, + // letterSpacing: 0.0, + // ), + // ), + // ].divide(SizedBox(height: 56.rpx)), + // ), + // ), + // ].divide(SizedBox( + // width: 33.rpx, + // )), + // ), + ), - ); - }).toList(), - ), - ], + SizedBox(height: 50.rpx), + Wrap( + spacing: 32.rpx, + runSpacing: 20.rpx, + children: showLabel.map((item) { + return Container( + padding: EdgeInsets.all(5.rpx), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 20.rpx, + height: 20.rpx, + decoration: BoxDecoration( + color: stringToColor("${item["color"]}"), + borderRadius: BorderRadius.circular(10.rpx), + ), + ), + SizedBox(width: 17.rpx), + Text( + item["name"] + '(' + item['range'] + ")", + style: TextStyle( + color: Colors.white, + fontSize: 24.rpx, + ), + ), + ], + ), + ); + }).toList(), + ), + ], + ), ), - ), - ); - - } catch (e) { + ); + } catch (e) { es.EasyDartModule.logger.error("打鼾监测绘制异常${e}"); - return Container(); - } + return Container(); + } } //获取睡眠等级 diff --git a/lib/pages/sleep_report/component/SleepView.dart b/lib/pages/sleep_report/component/SleepView.dart index c71e906..deb4003 100644 --- a/lib/pages/sleep_report/component/SleepView.dart +++ b/lib/pages/sleep_report/component/SleepView.dart @@ -100,7 +100,7 @@ class _SleepViewWidgetState extends State { context, Container( child: Text( - "睡眠规律性介绍。", + "睡眠规律性介绍", style: TextStyle( fontSize: 26.rpx, color: themeController.currentColor.sc3, diff --git a/lib/pages/sleep_report/component/TrendDataTablePage.dart b/lib/pages/sleep_report/component/TrendDataTablePage.dart new file mode 100644 index 0000000..370db8d --- /dev/null +++ b/lib/pages/sleep_report/component/TrendDataTablePage.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; + +class TrendDataTablePage extends StatefulWidget { + final String title; + final String tipText; + final Widget? chartContent; + final double? padding; + + const TrendDataTablePage({ + Key? key, + required this.title, + required this.tipText, + required this.chartContent, + required this.padding, + }) : super(key: key); + + @override + State createState() => _TrendDataTablePageState(); +} + +class _TrendDataTablePageState extends State { + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: themeController.currentColor.sc5, + borderRadius: BorderRadius.circular(25.rpx)), + padding: + EdgeInsets.fromLTRB(26.rpx, 29.rpx, 26.rpx, widget.padding ?? 45.rpx), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.title, + style: TextStyle( + color: Colors.white, + fontSize: 30.rpx, + ), + ), + GestureDetector( + onTap: () { + showTipDialog( + context, + Container( + child: Text( + widget.tipText, + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: Container( + width: 30.rpx, + height: 30.rpx, + child: SvgPicture.asset('assets/img/icon/explain.svg', + color: Color(0xFFD3D3D3)), + )), + ], + ), + SizedBox(height: 47.rpx), + widget.chartContent ?? Container(), + ], + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/TrendDataTextPage.dart b/lib/pages/sleep_report/component/TrendDataTextPage.dart new file mode 100644 index 0000000..a772ac3 --- /dev/null +++ b/lib/pages/sleep_report/component/TrendDataTextPage.dart @@ -0,0 +1,123 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/DataShowWidget.dart'; +import 'package:EasyDartModule/EasyDartModule.dart' as es; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class IndicatorCompareCard extends StatelessWidget { + final String title; + final String? tooltip; + final List headers; // 表头 ["名称", "测量值", "参考范围", "趋势"] + final List> rows; // 每一行的 4 个 widget + final double spacing; + + const IndicatorCompareCard({ + super.key, + required this.title, + this.tooltip, + required this.headers, + required this.rows, + this.spacing = 31, + }); + + @override + Widget build(BuildContext context) { + 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( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// 标题行 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title.tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().title_text_fontSize, + ), + ), + if (tooltip != null) + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.white, + padding: + EdgeInsetsDirectional.fromSTEB(14.rpx, 0, 14.rpx, 0), + borderRadius: 0.rpx, + onTap: () { + showTipDialog( + context, + Text( + tooltip!.tr, + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ); + }, + child: Container( + width: 30.rpx, + height: 30.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + color: Color(0xFFD3D3D3), + ), + )), + ], + ), + SizedBox(height: spacing.rpx), + + /// 表头 + DataShowWidget( + alignment: MainAxisAlignment.center, + widget1: _buildHeader(headers, 0), + widget2: _buildHeader(headers, 1), + widget3: _buildHeader(headers, 2), + widget4: _buildHeader(headers, 3), + ), + + /// 数据行 + Column( + children: rows.map((row) { + return DataShowWidget( + alignment: MainAxisAlignment.center, + widget1: row[0], + widget2: row[1], + widget3: row[2], + widget4: row[3], + ).paddingOnly(bottom: 0.rpx); + }).toList(), + ), + ], + ), + ), + ); + } + + Widget _buildHeader(List headers, int index) { + return Text( + headers.length > index ? headers[index].tr : "", + style: TextStyle( + color: themeController.currentColor.sc4, + fontSize: AppConstants().normal_text_fontSize, + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/WeekDataWidget.dart b/lib/pages/sleep_report/component/WeekDataWidget.dart new file mode 100644 index 0000000..f01819e --- /dev/null +++ b/lib/pages/sleep_report/component/WeekDataWidget.dart @@ -0,0 +1,852 @@ +import 'package:ef/base/chart/drawer.dart'; +import 'package:flutter/material.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/pages/mh_page/component/easychart.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SleepCard.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SleepChartWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/TrendDataTablePage.dart'; +import 'package:vbvs_app/pages/sleep_report/component/TrendDataTextPage.dart'; + +import 'package:vbvs_app/pages/sleep_report/component/WeekSleepScoreWidget.dart'; + +Widget WeekDataWidget( + Map sleepReport, + dynamic data, +) { + List _buildSectionList() { + EdgeInsetsDirectional padding = + EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 25.rpx); + + return [ + AvgSleepScoreWidget( + sleepReport: sleepReport, + ), //睡眠评分 + SleepChartContainer( + title: "每日得分", + tipText: "睡眠规律性介绍", + sleepReport: sleepReport, + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: + buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ChartLables( + //X轴标签系列2 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -50.rpx), //标签相对于原点的偏移,下方60像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: sleepReport['scoreList']['yRange']['min'] ?? 0.0, + max: sleepReport['scoreList']['yRange']['max'] ?? 100.0, + q: 100, + labels: List.from( + sleepReport['scoreList']['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-15.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + xCount: 7, + yCount: sleepReport['scoreList']['yLable'].length, + points: buildWeekDatesAndPoints(sleepReport['scoreList'])['points'], + displayMode: ChartDisplayMode.bar, + barColors: + buildWeekDatesAndPoints(sleepReport['scoreList'])['colors'], + tips: buildValueTexts(sleepReport['scoreList']['data'], '分', 1), + xUnit: sleepReport['scoreList']['yUnit'], + barWidth: 0.1, + ), + showLabel: sleepReport['scoreList']['type'], + ), + + SleepCard( + sleepReport: sleepReport, + highlightItem: data['itemName'], + ), + IndicatorCompareCard( + title: "与上周对比", + headers: ["名称", "上周", "本周", "参考范围"], + tooltip: "与上周对比提示", + rows: (sleepReport['cwl'] ?? []).map>((item) { + return [ + Text( + item['name']?.toString() ?? '-', + style: TextStyle(color: Color(0xFFFFFFFF), fontSize: 26.rpx), + ), + Text( + item['lastCurr']?.toString() ?? '-', + style: TextStyle(color: Color(0xFFFFFFFF), fontSize: 26.rpx), + ), + Text( + item['curr']?.toString() ?? '-', + style: TextStyle( + color: item["currColor"] ?? Color(0xFFFFFFFF), + fontSize: 26.rpx, + ), + ), + Text( + item['range']?.toString() ?? '-', + style: TextStyle(color: Color(0xFFFFFFFF), fontSize: 26.rpx), + ), + ]; + }).toList(), + ), + SleepChartContainer( + title: "本周睡眠时长", + tipText: "本周睡眠时长介绍", + sleepReport: sleepReport, + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: + buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ChartLables( + //X轴标签系列2 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -50.rpx), //标签相对于原点的偏移,下方60像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: sleepReport['csd']['yRange']['min'] ?? 0.0, + max: sleepReport['csd']['yRange']['max'] ?? 10.0, + q: 10, + labels: List.from( + sleepReport['csd']['yLable'].map((e) => e['name'].toString()), + ), + offset: Offset(-15.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + xCount: 7, + yCount: sleepReport['csd']['yLable'].length, + points: [], + dualBarPoints: buildTripleBarData(sleepReport['csd']), + displayMode: ChartDisplayMode.dualBar, + xUnit: sleepReport['csd']['yUnit'], + tips: buildSleepValueTexts(sleepReport['csd']['data'], '小时', 1), + barWidth: 0.1, + ), + showLabel: sleepReport['csd']['type'], + ), + TrendDataTablePage( + title: sleepReport['dysp'][0]['name'], + tipText: "入睡时间趋势提示", + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: + buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ChartLables( + //X轴标签系列2 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -50.rpx), //标签相对于原点的偏移,下方60像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: 0, + max: 5, + q: 5, + labels: List.from( + sleepReport['dysp'][0]['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-30.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['dysp'][0]['value'], '入睡时间:', 0), + xCount: 7, + yCount: sleepReport['dysp'][0]['yLable'].length, + points: buildGeneralPoints(sleepReport['dysp'][0]), + displayMode: ChartDisplayMode.line, + xUnit: sleepReport['dysp'][0]['yUnit'], + barWidth: 0.1, + bottomPadding: 50, + ), + padding: 45.rpx), + TrendDataTablePage( + title: sleepReport['dysp'][1]['name'], + tipText: "起床时间趋势提示", + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: + buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ChartLables( + //X轴标签系列2 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -50.rpx), //标签相对于原点的偏移,下方60像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: 0, + max: 4, + q: 4, + labels: List.from( + sleepReport['dysp'][1]['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-30.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['dysp'][1]['value'], '起床时间:', 0), + xCount: 7, + yCount: sleepReport['dysp'][1]['yLable'].length, + points: buildGeneralPoints(sleepReport['dysp'][1]), + displayMode: ChartDisplayMode.line, + xUnit: sleepReport['dysp'][1]['yUnit'], + barWidth: 0.1, + bottomPadding: 50, + ), + padding: 45.rpx), + TrendDataTablePage( + title: sleepReport['dysp'][2]['name'], + tipText: "心率基准趋势提示", + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: + buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ChartLables( + //X轴标签系列2 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -50.rpx), //标签相对于原点的偏移,下方60像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: 0, + max: 4, + q: 4, + labels: List.from( + sleepReport['dysp'][2]['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-15.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['dysp'][2]['value'], '次', 1), + xCount: 7, + yCount: sleepReport['dysp'][2]['yLable'].length, + points: buildGeneralPoints(sleepReport['dysp'][2]), + displayMode: ChartDisplayMode.line, + xUnit: sleepReport['dysp'][2]['yUnit'], + barWidth: 0.1, + bottomPadding: 50, + ), + padding: 45.rpx), + TrendDataTablePage( + title: sleepReport['dysp'][3]['name'], + tipText: "心率变异性趋势提示", + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: + buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ChartLables( + //X轴标签系列2 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -50.rpx), //标签相对于原点的偏移,下方60像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: 0, + max: 5, + q: 5, + labels: List.from( + sleepReport['dysp'][3]['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-15.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['dysp'][3]['value'], '毫秒', 1), + xCount: 7, + yCount: sleepReport['dysp'][3]['yLable'].length, + points: buildGeneralPoints(sleepReport['dysp'][3]), + displayMode: ChartDisplayMode.line, + xUnit: sleepReport['dysp'][3]['yUnit'], + barWidth: 0.1, + bottomPadding: 50, + ), + padding: 45.rpx), + TrendDataTablePage( + title: sleepReport['dysp'][4]['name'], + tipText: "呼吸基准趋势提示", + chartContent: LineView( + xLabels: [ + ChartLables( + //标签系列1 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: + buildWeekDatesAndPoints(sleepReport['scoreList'])['dates'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -16.rpx), //标签相对于原点的偏移,下方20像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XffD3D3D3), fontSize: 18.rpx), + ); + }, + ), + ChartLables( + //X轴标签系列2 + min: 0, //最小值0 + max: 7, //最大值30 + q: 6, //labels第一个与最后一个的真实距离,也就是30-1 = 29 + labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'], + indexs: [0, 1, 2, 3, 4, 5, 6], //每一个标签的对应在X轴的真实位置 + offset: Offset(0, -50.rpx), //标签相对于原点的偏移,下方60像素位置 + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: + TextStyle(color: Color(0XFFFFFFFF), fontSize: 26.rpx), + ); + }, + ), + ], + yLabels: [ + ChartLables( + min: 0, + max: 5, + q: 5, + labels: List.from( + sleepReport['dysp'][4]['yLable'] + .map((e) => e['name'].toString()), + ), + offset: Offset(-20.rpx, 0), + ondrawer: (canvas, offset, index, label, align, style) { + ChartLables.drawText( + canvas, + label, + offset, + textAlign: TextAlign.center, + style: TextStyle( + color: Color(0xFFFFFFFF).withOpacity(0.6), + fontSize: 18.rpx), + ); + }, + ), + ], + tips: buildValueTexts(sleepReport['dysp'][4]['value'], '次/分', 1), + xCount: 7, + yCount: sleepReport['dysp'][4]['yLable'].length, + points: buildGeneralPoints(sleepReport['dysp'][4]), + displayMode: ChartDisplayMode.line, + xUnit: sleepReport['dysp'][4]['yUnit'], + barWidth: 0.1, + bottomPadding: 50, + ), + padding: 45.rpx), + + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("MAC号:${data['mac']}", + style: TextStyle( + color: Color(0xFFD3D3D3).withOpacity(0.2), fontSize: 18.rpx)) + ], + ) + ] + .map((widget) => Padding( + padding: padding, + child: SizedBox(width: double.infinity, child: widget), + )) + .toList(); + } + + return Column( + children: _buildSectionList(), + ); +} + +// 计算非均匀标签的 y 坐标 +double getYPositionBySegmentedLabels(List labels, double value) { + if (value <= labels.first) return 0.0; + if (value >= labels.last) return labels.length - 1.0; + + for (int i = 0; i < labels.length - 1; i++) { + double low = labels[i]; + double high = labels[i + 1]; + if (value >= low && value <= high) { + double ratio = (value - low) / (high - low); + return i + ratio; + } + } + // 不应该走到这里,默认返回0 + return 0.0; +} + +//关键点标签 +List buildValueTexts( + List data, + String unit, + int direction, // 0 表示左侧,1 表示右侧 +) { + if (data.isEmpty) return []; + + return data.where((item) => item.containsKey('value')).map((item) { + final val = item['value'].toString(); + return direction == 1 ? "$val$unit" : "$unit$val"; + }).toList(); +} + +//多个关键点标签 +List buildSleepValueTexts( + List data, + String unit, + int direction, // 0 左侧,1 右侧 +) { + if (data.isEmpty) return []; + + return data.map((item) { + final dst = (item['dst'] ?? 0).toString(); + final lst = (item['lst'] ?? 0).toString(); + final slt = (item['slt'] ?? 0).toString(); + + final prefix = direction == 1 ? '' : unit; + final suffix = direction == 1 ? unit : ''; + + var q = [ + "睡眠时长:$prefix$slt$suffix", + "深睡:$prefix$dst$suffix", + "浅睡:$prefix$lst$suffix", + ].join("\n"); + print(q); + return q; + }).toList(); +} + +int getWeekdayX(DateTime current, DateTime first) { + final firstMonday = first.subtract(Duration(days: first.weekday - 1)); + final currentMonday = current.subtract(Duration(days: current.weekday - 1)); + final weekOffset = currentMonday.difference(firstMonday).inDays ~/ 7; + return weekOffset * 7 + (current.weekday - 1); +} + +Map buildWeekDatesAndPoints(Map scoreList) { + if (!scoreList.containsKey('data') || (scoreList['data'] as List).isEmpty) { + return { + 'dates': [], + 'points': [], + 'colors': [], + }; + } + + List> data = (scoreList['data'] as List) + .where((item) => item is Map) + .cast>() + .toList(); + + // 提取 level -> color 映射表 + Map levelColorMap = {}; + if (scoreList.containsKey('type')) { + List typeList = scoreList['type']; + for (var item in typeList) { + if (item is Map && + item.containsKey('level') && + item.containsKey('color')) { + levelColorMap[item['level']] = item['color']; + } + } + } + + DateTime baseDate = DateTime.fromMillisecondsSinceEpoch(data.first['st']); + int weekday = baseDate.weekday; + DateTime monday = baseDate.subtract(Duration(days: weekday - 1)); + List dates = List.generate(7, (i) { + DateTime d = monday.add(Duration(days: i)); + String month = d.month.toString().padLeft(2, '0'); + String day = d.day.toString().padLeft(2, '0'); + return "$month/$day"; + }); + + List points = []; + List colors = []; + + for (var item in data) { + DateTime dt = DateTime.fromMillisecondsSinceEpoch(item['st']); + double x = getWeekdayX(dt, baseDate).toDouble(); + double y = (item['value'] as num?)?.toDouble() ?? 0; + int level = item['level']; + String color = levelColorMap[level] ?? "#FFFF00"; + + points.add(Offset(x, y)); + colors.add(color); + } + + return { + 'dates': dates, + 'points': points, + 'colors': colors, + }; +} + +List buildGeneralPoints(Map dyspData) { + final values = + (dyspData['value'] as List?)?.whereType>().toList(); + final yLabels = + (dyspData['yLable'] as List?)?.whereType>().toList(); + if (values == null || values.isEmpty || yLabels == null || yLabels.length < 2) + return []; + + bool isTimeAxis = yLabels.first['name'].toString().contains(":"); + + double labelToValue(String name) { + if (isTimeAxis) { + final parts = name.split(':'); + int hour = int.parse(parts[0]); + int minute = int.parse(parts[1]); + return hour * 60 + minute * 1.0; + } else { + return double.tryParse(name) ?? 0; + } + } + + List labelValues = + yLabels.map((e) => labelToValue(e['name'])).toList(); + + bool crossMidnight = false; + if (isTimeAxis && labelValues.last < labelValues.first) { + crossMidnight = true; + double base = labelValues.first; + for (int i = 1; i < labelValues.length; i++) { + if (labelValues[i] < base) { + labelValues[i] += 1440; + } + } + } + + DateTime baseDate = DateTime.fromMillisecondsSinceEpoch(values.first['st']); + List points = []; + + for (var item in values) { + DateTime date = DateTime.fromMillisecondsSinceEpoch(item['st']); + double x = getWeekdayX(date, baseDate).toDouble(); + + dynamic rawValue = item['value']; + double valueMinBased; + + if (isTimeAxis && rawValue is String) { + final parts = rawValue.split(':'); + int hour = int.parse(parts[0]); + int minute = int.parse(parts[1]); + valueMinBased = hour * 60 + minute * 1.0; + if (crossMidnight && valueMinBased < labelValues.first) { + valueMinBased += 1440; + } + } else if (!isTimeAxis && rawValue is num) { + valueMinBased = rawValue.toDouble(); + } else { + continue; + } + + if (valueMinBased < labelValues.first) valueMinBased = labelValues.first; + if (valueMinBased > labelValues.last) valueMinBased = labelValues.last; + + double y = getYPositionBySegmentedLabels(labelValues, valueMinBased); + points.add(Offset(x, y)); + } + + return points; +} + +List> buildTripleBarData(Map csd) { + final data = + (csd['data'] as List?)?.whereType>().toList() ?? []; + final yLabels = + (csd['yLable'] as List?)?.whereType>().toList() ?? + []; + + if (data.isEmpty || yLabels.isEmpty) return []; + + double minY = double.tryParse(yLabels.first['name'].toString()) ?? 0; + double maxY = double.tryParse(yLabels.last['name'].toString()) ?? 10; + + DateTime firstDate = DateTime.fromMillisecondsSinceEpoch(data.first['st']); + + List> points = []; + + for (var item in data) { + double dst = (item['dst'] as num?)?.toDouble() ?? 0; + double lst = (item['lst'] as num?)?.toDouble() ?? 0; + double slt = (item['slt'] as num?)?.toDouble() ?? 0; + + dst = dst.clamp(minY, maxY); + lst = lst.clamp(minY, maxY); + slt = slt.clamp(minY, maxY); + + DateTime date = DateTime.fromMillisecondsSinceEpoch(item['st']); + double x = getWeekdayX(date, firstDate).toDouble(); + + points.add([ + Offset(x, dst), + Offset(x, dst + lst), + Offset(x, slt), + ]); + } + + return points; +} diff --git a/lib/pages/sleep_report/component/WeekSleepScoreWidget.dart b/lib/pages/sleep_report/component/WeekSleepScoreWidget.dart new file mode 100644 index 0000000..c4e5b4b --- /dev/null +++ b/lib/pages/sleep_report/component/WeekSleepScoreWidget.dart @@ -0,0 +1,217 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.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/pages/sleep_report/chart/SegmentedCirclePainter.dart'; +import 'package:EasyDartModule/EasyDartModule.dart' as es; + +class AvgSleepScoreWidget extends StatelessWidget { + final Map sleepReport; + final String leftLabel; + final String mediumLabel; + final String rightLabel; + final String unknownText; + + const AvgSleepScoreWidget({ + Key? key, + required this.sleepReport, + this.leftLabel = '最低分', + this.mediumLabel = '本周评分', + this.rightLabel = '最高分', + this.unknownText = '未知数据', + }) : super(key: key); + + @override + Widget build(BuildContext context) { + try { + if (sleepReport == null || sleepReport is! Map || sleepReport.isEmpty) { + return Container(); + } + + List showLabel = sleepReport['score']['type']; + List segments = parseSegments(sleepReport); + + return Container( + width: double.infinity, + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + // height: 300.rpx, // 自定义一个高度,确保有空间放置中间内容 + child: Stack( + alignment: Alignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // 左侧 min + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + constraints: BoxConstraints(maxWidth: 150.rpx), + child: Text( + leftLabel.tr, + style: TextStyle( + color: Color(0xFFD3D3D3), + fontSize: 26.rpx, + ), + maxLines: 1, + ), + ), + Text( + sleepReport['score']?['min']?.toString() ?? '--', + style: TextStyle( + color: Colors.white, + fontSize: 48.rpx, + ), + ), + ].divide(SizedBox(height: 56.rpx)), + ), + + // 右侧 max + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + rightLabel.tr, + style: TextStyle( + color: Color(0xFFD3D3D3), + fontSize: 26.rpx, + ), + ), + Text( + sleepReport['score']?['max']?.toString() ?? '--', + style: TextStyle( + color: stringToColor("#FF9F66"), + fontSize: 48.rpx, + ), + ), + ].divide(SizedBox(height: 56.rpx)), + ), + ].divide(SizedBox(width: 33.rpx)), + ), + + // 中间圆环绝对居中 + SizedBox( + width: 256.rpx, + height: 256.rpx, + child: SegmentedCircleWithCenterWidget( + segments: segments, + strokeWidth: 6.rpx, + gapAngle: 0, + centerWidget: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + mediumLabel.tr, + style: TextStyle( + color: Colors.white, + fontSize: AppConstants().normal_text_fontSize, + ), + ), + Text( + sleepReport['score']?['avg']?.toString() ?? '--', + style: TextStyle( + color: segments[0].color, + fontSize: 99.rpx, + ), + ), + ], + ), + trend: sleepReport['score']?['trend'], + ), + ) + ], + ), + ), + SizedBox(height: 50.rpx), + Wrap( + spacing: 32.rpx, + runSpacing: 20.rpx, + children: showLabel.map((item) { + return Container( + padding: EdgeInsets.all(5), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 20.rpx, + height: 20.rpx, + decoration: BoxDecoration( + color: stringToColor("${item["color"]}"), + borderRadius: BorderRadius.circular(10.rpx), + ), + ), + SizedBox(width: 17.rpx), + Text( + item["name"] + '(' + item['range'] + ")", + style: TextStyle( + color: Colors.white, + fontSize: 24.rpx, + ), + ), + ], + ), + ); + }).toList(), + ), + ], + ), + ), + ); + } catch (e) { + print("SleepScoreWidget Error: $e"); + return Container(); + } + } + + String getSleepLevel(Map sleepReport) { + if (sleepReport['score'] == null || + sleepReport['score']['level'] == null || + sleepReport['score']['type'] == null) { + return unknownText.tr; + } + + int level = sleepReport['score']['level']; + List typeList = sleepReport['score']['type']; + + var matched = typeList.firstWhere( + (item) => item['level'] == level, + orElse: () => null, + ); + + if (matched != null && matched['name'] != null) { + return matched['name']; + } + return unknownText.tr; + } + + List parseSegments(Map sleepReport) { + try { + final score = sleepReport['score'] ?? {}; + final int level = score['level'] ?? 4; + final typeList = score['type'] ?? []; + + // 尝试获取匹配 level 的颜色 + final matchedType = typeList.firstWhere( + (item) => item['level'] == level, + orElse: () => null, + ); + + final color = matchedType != null && matchedType['color'] != null + ? stringToColor(matchedType['color']) + : Colors.grey; + + return [SegmentData(color: color, value: 100)]; + } catch (e, st) { + print('parseSegments error: $e\n$st'); + return []; + } + } +} diff --git a/lib/pages/sleep_report/new_sleep_report_page copy.dart b/lib/pages/sleep_report/new_sleep_report_page copy.dart index 8f61913..53becb0 100644 --- a/lib/pages/sleep_report/new_sleep_report_page copy.dart +++ b/lib/pages/sleep_report/new_sleep_report_page copy.dart @@ -122,7 +122,7 @@ class _NewSleepReportPageState extends State { /// 左边返回按钮 Positioned( - left: 20, + left: 0, child: returnIconButtomNew, ), ], @@ -199,103 +199,94 @@ class _NewSleepReportPageState extends State { ], ), ), - - // Obx(() { - // return ClickableContainer( - // backgroundColor: Colors.transparent, - // highlightColor: - // themeController.currentColor.sc3, - // borderRadius: 8.rpx, - // padding: EdgeInsets.all(0), - // onTap: () async { - // sleepReportController.model.type = - // 2; - // sleepReportController.updateAll(); - // }, - // child: Column( - // mainAxisSize: MainAxisSize.max, - // children: [ - // Container( - // width: 115.rpx, // 固定宽度为 160.rpx - // alignment: - // Alignment.center, // 文字居中 - // child: Text( - // '周报'.tr, - // style: FlutterFlowTheme.of( - // context) - // .bodyMedium - // .override( - // fontFamily: 'Inter', - // fontSize: AppConstants() - // .title_text_fontSize, - // letterSpacing: 0.0, - // color: - // sleepReportController - // .model - // .type == - // 2 - // ? themeController - // .currentColor - // .sc2 - // : themeController - // .currentColor - // .sc3, - // ), - // ), - // ), - // SizedBox(height: 10.rpx), - // ], - // ), - // ); - // }), - // Obx(() { - // return ClickableContainer( - // backgroundColor: Colors.transparent, - // highlightColor: - // themeController.currentColor.sc3, - // borderRadius: 8.rpx, - // padding: EdgeInsets.all(0), - // onTap: () async { - // sleepReportController.model.type = - // 3; - // sleepReportController.updateAll(); - // }, - // child: Column( - // mainAxisSize: MainAxisSize.max, - // children: [ - // Container( - // width: 115.rpx, // 固定宽度为 160.rpx - // alignment: - // Alignment.center, // 文字居中 - // child: Text( - // '月报'.tr, - // style: FlutterFlowTheme.of( - // context) - // .bodyMedium - // .override( - // fontFamily: 'Inter', - // fontSize: AppConstants() - // .title_text_fontSize, - // letterSpacing: 0.0, - // color: - // sleepReportController - // .model - // .type == - // 3 - // ? themeController - // .currentColor - // .sc2 - // : themeController - // .currentColor - // .sc3, - // ), - // ), - // ), - // SizedBox(height: 10.rpx), - // ], - // ), - // ); - // }), + Obx(() { + return ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: themeController + .currentColor.sc3, + borderRadius: 8.rpx, + padding: EdgeInsets.all(0), + onTap: () async { + sleepReportController.model.type = + 2; + sleepReportController.updateAll(); + }, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: + 115.rpx, // 固定宽度为 160.rpx + alignment: + Alignment.center, // 文字居中 + child: Text('周报'.tr, + style: TextStyle( + fontFamily: 'Inter', + fontSize: AppConstants() + .title_text_fontSize, + letterSpacing: 0.0, + color: + sleepReportController + .model + .type == + 2 + ? themeController + .currentColor + .sc2 + : themeController + .currentColor + .sc3, + )), + ), + SizedBox(height: 10.rpx), + ], + ), + ); + }), + Obx(() { + return ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: themeController + .currentColor.sc3, + borderRadius: 8.rpx, + padding: EdgeInsets.all(0), + onTap: () async { + sleepReportController.model.type = + 3; + sleepReportController.updateAll(); + }, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: + 115.rpx, // 固定宽度为 160.rpx + alignment: + Alignment.center, // 文字居中 + child: Text('月报'.tr, + style: TextStyle( + fontFamily: 'Inter', + fontSize: AppConstants() + .title_text_fontSize, + letterSpacing: 0.0, + color: + sleepReportController + .model + .type == + 3 + ? themeController + .currentColor + .sc2 + : themeController + .currentColor + .sc3, + )), + ), + SizedBox(height: 10.rpx), + ], + ), + ); + }), ], ), AnimatedPositioned( diff --git a/lib/pages/sleep_report/new_sleep_report_page.dart b/lib/pages/sleep_report/new_sleep_report_page.dart index 2e69418..343b217 100644 --- a/lib/pages/sleep_report/new_sleep_report_page.dart +++ b/lib/pages/sleep_report/new_sleep_report_page.dart @@ -14,23 +14,12 @@ import 'package:vbvs_app/controller/sleep/sleep_report_controller.dart'; import 'package:vbvs_app/language/AppLanguage.dart'; import 'package:vbvs_app/pages/common/selectDialog.dart'; import 'package:vbvs_app/pages/main_bottom/component/main_page_b_bottom_change.dart'; -import 'package:vbvs_app/pages/sleep_report/component/AIAdviceWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/BreatheCard.dart'; -import 'package:vbvs_app/pages/sleep_report/component/BreathePauseNewWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/BreatheStandardWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/CompareSleepWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/DiseasePercentsWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/HeartChangeWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/HeartHealthWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/HeartPointWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/HeartRateCard.dart'; -import 'package:vbvs_app/pages/sleep_report/component/HeartRateStandardWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/SkinPercentWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/SleepCard.dart'; -import 'package:vbvs_app/pages/sleep_report/component/SleepScoreWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/SleepView.dart'; -import 'package:vbvs_app/pages/sleep_report/component/SnoreViewWidget.dart'; -import 'package:vbvs_app/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart'; + +import 'package:vbvs_app/pages/sleep_report/component/DailyDataWidget.dart'; + +import 'package:vbvs_app/pages/sleep_report/component/MonthDataWidget.dart'; + +import 'package:vbvs_app/pages/sleep_report/component/WeekDataWidget.dart'; class NewSleepReportPage extends StatefulWidget { var data; @@ -267,6 +256,53 @@ class _NewSleepReportPageState extends State { onTap: () async { sleepReportController.model.type = 1; + + String data = MyUtils.formatDate( + calendarController + .selectedDate.value!); + await requestWithLog( + logTitle: "查询睡眠报告", + method: MyHttpMethod.get, + queryUrl: + "https://sleepdata.he-info.com/api/analysis/sleep/analysis?mac=${widget.data['mac']}&time=${data}&type=${sleepReportController.model.type}", + onSuccess: (res) { + print(res); + sleepReportController + .sleepReport + .value = res.data; + sleepReportController + .updateAll(); + }, + onFailure: (res) { + if (MainPageBBottomChange + .getCurrentIndex() != + null) { + if (MainPageBBottomChange + .getCurrentIndex() == + 1) { + TopSlideNotification.show( + context, + text: res.msg!, + textColor: + themeController + .currentColor + .sc9); + } + } else { + TopSlideNotification.show( + context, + text: res.msg!, + textColor: + themeController + .currentColor + .sc9); + } + sleepReportController + .sleepReport.value = {}; + sleepReportController + .updateAll(); + print(res); + }); sleepReportController.updateAll(); }, child: Column( @@ -297,103 +333,185 @@ class _NewSleepReportPageState extends State { ], ), ), + Obx(() { + return ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: themeController + .currentColor.sc3, + borderRadius: 8.rpx, + padding: EdgeInsets.all(0), + onTap: () async { + sleepReportController.model.type = + 2; + String data = MyUtils.formatDate( + calendarController + .selectedDate.value!); + await requestWithLog( + logTitle: "查询睡眠报告", + method: MyHttpMethod.get, + queryUrl: + "https://sleepdata.he-info.com/api/analysis/sleep/analysis?mac=${widget.data['mac']}&time=${data}&type=${sleepReportController.model.type}", + onSuccess: (res) { + print(res); + sleepReportController + .sleepReport + .value = res.data; + sleepReportController + .updateAll(); + }, + onFailure: (res) { + if (MainPageBBottomChange + .getCurrentIndex() != + null) { + if (MainPageBBottomChange + .getCurrentIndex() == + 1) { + TopSlideNotification.show( + context, + text: res.msg!, + textColor: + themeController + .currentColor + .sc9); + } + } else { + TopSlideNotification.show( + context, + text: res.msg!, + textColor: + themeController + .currentColor + .sc9); + } + sleepReportController + .sleepReport.value = {}; + sleepReportController + .updateAll(); + print(res); + }); + sleepReportController.updateAll(); + }, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: + 115.rpx, // 固定宽度为 160.rpx + alignment: + Alignment.center, // 文字居中 + child: Text('周报'.tr, + style: TextStyle( + fontFamily: 'Inter', + fontSize: AppConstants() + .title_text_fontSize, + letterSpacing: 0.0, + color: + sleepReportController + .model + .type == + 2 + ? themeController + .currentColor + .sc2 + : themeController + .currentColor + .sc3, + )), + ), + SizedBox(height: 10.rpx), + ], + ), + ); + }), + Obx(() { + return ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: themeController + .currentColor.sc3, + borderRadius: 8.rpx, + padding: EdgeInsets.all(0), + onTap: () async { + sleepReportController.model.type = + 3; + String data = MyUtils.formatDate( + calendarController + .selectedDate.value!); + await requestWithLog( + logTitle: "查询睡眠报告", + method: MyHttpMethod.get, + queryUrl: + "https://sleepdata.he-info.com/api/analysis/sleep/analysis?mac=${widget.data['mac']}&time=${data}&type=${sleepReportController.model.type}", + onSuccess: (res) { + print(res); + sleepReportController + .sleepReport + .value = res.data; + sleepReportController + .updateAll(); + }, + onFailure: (res) { + if (MainPageBBottomChange + .getCurrentIndex() != + null) { + if (MainPageBBottomChange + .getCurrentIndex() == + 1) { + TopSlideNotification.show( + context, + text: res.msg!, + textColor: + themeController + .currentColor + .sc9); + } + } else { + TopSlideNotification.show( + context, + text: res.msg!, + textColor: + themeController + .currentColor + .sc9); + } + sleepReportController + .sleepReport.value = {}; + sleepReportController + .updateAll(); + print(res); + }); - // Obx(() { - // return ClickableContainer( - // backgroundColor: Colors.transparent, - // highlightColor: - // themeController.currentColor.sc3, - // borderRadius: 8.rpx, - // padding: EdgeInsets.all(0), - // onTap: () async { - // sleepReportController.model.type = - // 2; - // sleepReportController.updateAll(); - // }, - // child: Column( - // mainAxisSize: MainAxisSize.max, - // children: [ - // Container( - // width: 115.rpx, // 固定宽度为 160.rpx - // alignment: - // Alignment.center, // 文字居中 - // child: Text( - // '周报'.tr, - // style: FlutterFlowTheme.of( - // context) - // .bodyMedium - // .override( - // fontFamily: 'Inter', - // fontSize: AppConstants() - // .title_text_fontSize, - // letterSpacing: 0.0, - // color: - // sleepReportController - // .model - // .type == - // 2 - // ? themeController - // .currentColor - // .sc2 - // : themeController - // .currentColor - // .sc3, - // ), - // ), - // ), - // SizedBox(height: 10.rpx), - // ], - // ), - // ); - // }), - // Obx(() { - // return ClickableContainer( - // backgroundColor: Colors.transparent, - // highlightColor: - // themeController.currentColor.sc3, - // borderRadius: 8.rpx, - // padding: EdgeInsets.all(0), - // onTap: () async { - // sleepReportController.model.type = - // 3; - // sleepReportController.updateAll(); - // }, - // child: Column( - // mainAxisSize: MainAxisSize.max, - // children: [ - // Container( - // width: 115.rpx, // 固定宽度为 160.rpx - // alignment: - // Alignment.center, // 文字居中 - // child: Text( - // '月报'.tr, - // style: FlutterFlowTheme.of( - // context) - // .bodyMedium - // .override( - // fontFamily: 'Inter', - // fontSize: AppConstants() - // .title_text_fontSize, - // letterSpacing: 0.0, - // color: - // sleepReportController - // .model - // .type == - // 3 - // ? themeController - // .currentColor - // .sc2 - // : themeController - // .currentColor - // .sc3, - // ), - // ), - // ), - // SizedBox(height: 10.rpx), - // ], - // ), - // ); - // }), + sleepReportController.updateAll(); + }, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: + 115.rpx, // 固定宽度为 160.rpx + alignment: + Alignment.center, // 文字居中 + child: Text( + '月报'.tr, + style: TextStyle( + fontFamily: 'Inter', + fontSize: AppConstants() + .title_text_fontSize, + letterSpacing: 0.0, + color: sleepReportController + .model.type == + 3 + ? themeController + .currentColor.sc2 + : themeController + .currentColor.sc3, + ), + ), + ), + SizedBox(height: 10.rpx), + ], + ), + ); + }), ], ), AnimatedPositioned( @@ -420,21 +538,6 @@ class _NewSleepReportPageState extends State { ), ], ), - // Padding( - // padding: EdgeInsetsDirectional.fromSTEB( - // 0, 0.rpx, 0.rpx, 0), - // child: Container( - // width: 28.rpx, - // height: 28.rpx, - // // width: double.infinity, - // decoration: BoxDecoration(), - // child: SvgPicture.asset( - // 'assets/img/icon/share.svg', - // fit: BoxFit.cover, - // color: themeController.currentColor.sc3, - // ), - // ), - // ), ], ), ), @@ -450,7 +553,7 @@ class _NewSleepReportPageState extends State { ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 58.rpx), + 30.rpx, 0.rpx, 30.rpx, 51.rpx), child: ClickableContainer( backgroundColor: widget.data['backgroundColor'] != null @@ -616,176 +719,25 @@ class _NewSleepReportPageState extends State { ), ), ), - if (sleepReport.value == null || - sleepReport.value.isEmpty) - Container( - height: 500.rpx, - child: NullDataWidget(), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: - SleepScoreWidget(sleepReport: sleepReport.value), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: SleepViewWidget( - sleepReport: sleepReport, - ), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - key: sleepCardKey, - width: double.infinity, - child: SleepCard( - sleepReport: sleepReport, - highlightItem: widget.data['itemName'] ?? null, - ), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: CompareSleepWidget(sleepReport: sleepReport), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: HeartPointWidget( - sleepReport: sleepReport, - ), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: AIAdviceWidget(sleepReport: sleepReport), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: - HeartRateStandardWidget(sleepReport: sleepReport), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - key: heartRateCardKey, - width: double.infinity, - child: HeartRateCard( - sleepReport: sleepReport, - highlightItem: widget.data['itemName'] ?? null, - ), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: HeartChangeWidget(sleepReport: sleepReport), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: - BreatheStandardWidget(sleepReport: sleepReport), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - key: breatheCardKey, - width: double.infinity, - child: BreatheCard( - sleepReport: sleepReport, - highlightItem: widget.data['itemName'] ?? null, - ), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: - SnoreViewWidgetWidget(sleepReport: sleepReport), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: - BreathePauseNewWidget(sleepReport: sleepReport), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: HeartHealthWidget(sleepReport: sleepReport), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: - DiseasePercentsWidget(sleepReport: sleepReport), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: SkinPercentWidget(sleepReport: sleepReport), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 0.rpx, 30.rpx, 0), - child: Container( - width: double.infinity, - child: ZiZhuShenJingPercentWidget( - sleepReport: sleepReport), - ), - ), + (sleepReport.value == null || sleepReport.value.isEmpty) + ? Container( + child: NullDataWidget(), + ) + : (sleepReportController.model.type == 1 + ? DailyDataWidget(sleepReport, sleepCardKey, + heartRateCardKey, breatheCardKey, widget.data) + : sleepReportController.model.type == 2 + ? WeekDataWidget(sleepReport, widget.data) + : sleepReportController.model.type == 3 + ? MonthDataWidget( + sleepReport, widget.data) + : NullDataWidget()), ].divide(SizedBox( height: 25.rpx, )), ); }), + ), ), ), @@ -929,6 +881,57 @@ class _NewSleepReportPageState extends State { calendarController.updateAll(); } + + void onChangeArrowTap() { + if (type == 1) { + sleepReportController.selectedDate.value = + selectedDate.subtract(const Duration(days: 1)); + } else if (type == 2) { + sleepReportController.selectedDate.value = + selectedDate.subtract(const Duration(days: 7)); + } else if (type == 3) { + sleepReportController.selectedDate.value = DateTime( + selectedDate.year, + selectedDate.month - 1, + selectedDate.day, + ); + } + calendarController.selectedDate.value = + sleepReportController.selectedDate.value; + // String date = MyUtils.formatToDate(widget.data['date']); + String data = MyUtils.formatDate(calendarController.selectedDate.value!); + requestWithLog( + logTitle: "查询睡眠报告", + method: MyHttpMethod.get, + queryUrl: + "https://sleepdata.he-info.com/api/analysis/sleep/analysis?mac=${widget.data['mac']}&time=${data}&type=${sleepReportController.model.type}", + onSuccess: (res) { + print(res); + sleepReportController.sleepReport.value = res.data; + sleepReportController.updateAll(); + }, + onFailure: (res) { + if (MainPageBBottomChange.getCurrentIndex() != null) { + if (MainPageBBottomChange.getCurrentIndex() == 1) { + TopSlideNotification.show(context, + text: res.msg!, + textColor: themeController.currentColor.sc9); + } + } else { + TopSlideNotification.show(context, + text: res.msg!, textColor: themeController.currentColor.sc9); + } + sleepReportController.sleepReport.value = {}; + sleepReportController.updateAll(); + print(res); + }); + + sleepReportController.updateAll(); + calendarController.updateAll(); + } + + + return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/pages/user/about_us_page copy.dart b/lib/pages/user/about_us_page copy.dart deleted file mode 100644 index 9e6d658..0000000 --- a/lib/pages/user/about_us_page copy.dart +++ /dev/null @@ -1,113 +0,0 @@ -// import 'package:ef/ef.dart'; -// import 'package:flutter/material.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/CustomCard.dart'; -// import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; -// import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; -// import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; -// import 'package:vbvs_app/controller/user_info_controller.dart'; -// // import 'package:easydevice/easydevice.dart'; - - -// class AboutUsPage extends StatefulWidget { -// const AboutUsPage({super.key}); - -// @override -// State createState() => _AboutUsPageState(); -// } - -// class _AboutUsPageState extends State { -// GlobalController globalController = Get.find(); -// UserInfoController userInfoController = Get.find(); -// BlueteethBindController blueteethBindController = Get.find(); -// ThemeController themeController = Get.find(); - -// @override -// void initState() { -// super.initState(); -// } - -// @override -// Widget build(BuildContext context) { -// return LayoutBuilder( -// builder: (context, bodySize) => GestureDetector( -// onTap: () => FocusScope.of(context).unfocus(), -// child: Container( -// decoration: BoxDecoration( -// image: DecorationImage( -// image: AssetImage('assets/img/bgNoImg.png'), // 本地图片 -// fit: BoxFit.fill, // 填满整个 Container -// ), -// ), -// child: Scaffold( -// backgroundColor: Colors.transparent, // 加上这一行 -// appBar: AppBar( -// backgroundColor: themeController.currentColor.sc17, -// automaticallyImplyLeading: false, -// iconTheme: IconThemeData( -// color: themeController.currentColor.sc3, -// ), -// titleSpacing: 0, -// // leading: returnIconButtom, -// title: Container( -// width: double.infinity, -// height: 180.rpx, -// child: Stack( -// alignment: Alignment.center, -// children: [ -// /// 居中标题 -// Text( -// '关于我们.标题'.tr, -// style: TextStyle( -// fontFamily: 'Readex Pro', -// color: themeController.currentColor.sc3, -// letterSpacing: 0, -// fontSize: 30.rpx, -// ), -// ), - -// /// 左边返回按钮 -// Positioned( -// left: 0, -// child: returnIconButtom, -// ), -// ], -// ), -// ), - -// actions: [], -// centerTitle: false, -// ), -// body: SafeArea( -// top: true, -// child: Padding( -// padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), -// child: SingleChildScrollView( -// child: Column( -// mainAxisSize: MainAxisSize.max, -// children: [ - -// SizedBox( -// height: 30.rpx, -// ), -// Text( -// "企业简介\n\n\n嘉兴太和信息技术有限公司成立于2013年,是一家以传感技术、室内定位技术和人工智能技术为基础的国家高新技术企业,AI非接触生命体征传感器、高精度室内外一体定位平台、AI视频分析系统、射频消融等技术成果,目前已经拥有30多类知识产权证书,多项专利技术处于行业领先水平。\n\n\n我司研发的“非接触式生命体征传感器”是一款基于BCG信号原理,通过检测人体心脏搏动引起的微小振动的传感器系统。传感器系统通过将人体微弱的心跳、呼吸信号转换未电信号,进行相关生命体征分析。该传感器可为用户提供高灵敏度和精确度检测结构,适用于需要非接触式、高分辨率的监测场景。该系统的硬件、软件及生产维护均由我司自主开发和管理,拥有完全自主知识产权,并已申请多项国家专利,可依据用户需求定制个性化方案。\n\n\n该产品置于床垫下方使用,全程完全无感。采集的体征数据通过睡眠健康管理平台实时显示用户的健康状态,并对每次的睡眠报告进行系统化归档管理,支持长期查询。一旦用户在使用过程中出现异常情况,系统可及时做出判断并反馈预警信息和建议。目前,心率监测的准确度可达97%以上,呼吸监测的准确度可达95%以上,其他生理指标的监测精度也显著优于同类产品。该产品主体材质均采用符合国家标准的环保材料,部分硅胶配件达到食品级安全标准。产品尺寸可根据需求进行定制,适用于单人床、双人床、婴儿床、椅子及枕头等多种场景。\n\n\n睡眠健康管理平台通过实时预警与远程管理,提升睡眠质量及慢病干预效率,助力养老院、月子中心、康复中心、智能寝具等行业降本增效,实现精准健康的科学管理。", -// style: TextStyle( -// fontSize: AppConstants().normal_text_fontSize, -// color: themeController.currentColor.sc3), -// ), -// ], -// ), -// ), -// ), -// ), -// ), -// ), -// ), -// ); -// } - - -// } diff --git a/lib/pages/user/about_us_page.dart b/lib/pages/user/about_us_page.dart index df41923..e5f4e47 100644 --- a/lib/pages/user/about_us_page.dart +++ b/lib/pages/user/about_us_page.dart @@ -20,7 +20,8 @@ class _AboutUsPageState extends State { // pdfController.loadPdf(); widget.webView = MyWebView( - url: AppConstants().ent_type == 1 + url: + AppConstants().ent_type == 1 ? "https://mp.weixin.qq.com/s/IAr4RNBy0hGJXGKyMxe7eQ" : "https://mp.weixin.qq.com/s/7BvvprVDqX1eOzM3Lms8dg", onLoad: () {