exam_blood_pressure.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. import 'dart:convert';
  2. import 'package:flutter/material.dart';
  3. import 'package:get/get.dart';
  4. import 'package:vitalapp/architecture/app_parameters.dart';
  5. import 'package:vitalapp/managers/device_controller_manager.dart';
  6. import 'package:vitalapp/managers/interfaces/organization.dart';
  7. import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_blood_pressure.dart';
  8. import 'package:vitalapp/pages/medical/widgets/device_status_position.dart';
  9. import 'package:vitalapp/pages/medical/widgets/exam_card.dart';
  10. import 'package:vitalapp/pages/medical/widgets/switch_button.dart';
  11. import 'package:vnote_device_plugin/consts/types.dart';
  12. import 'package:vnote_device_plugin/devices/nibp.dart';
  13. import 'package:vnote_device_plugin/models/exams/nibp.dart';
  14. import 'package:fis_common/logger/logger.dart';
  15. import 'package:vitalapp/managers/interfaces/models/device.dart';
  16. import 'package:vitalapp/pages/medical/controller.dart';
  17. import 'package:vitalapp/pages/medical/models/item.dart';
  18. import 'package:vitalapp/pages/medical/models/worker.dart';
  19. import 'package:vitalapp/pages/medical/widgets/device_status.dart';
  20. // ignore: must_be_immutable
  21. class ExamBloodPressure extends StatefulWidget {
  22. const ExamBloodPressure({
  23. super.key,
  24. });
  25. @override
  26. State<ExamBloodPressure> createState() => _ExamBloodPressureState();
  27. }
  28. class _ExamBloodPressureState extends State<ExamBloodPressure> {
  29. var controller = Get.find<MedicalController>();
  30. bool get isPureSoftwareMode => AppParameters.data.isPureSoftwareMode;
  31. PressureDeviceStatus pressureDeviceStatus = PressureDeviceStatus.start;
  32. bool isConnectFail = false;
  33. DeviceControllerManager? nibp;
  34. NibpDeviceWorker? worker;
  35. int liveValue = 0;
  36. int errorCount = 0;
  37. PressureStatus pressureStatus = PressureStatus.left;
  38. bool isShowError = false;
  39. String errorMessage = '';
  40. WorkerStatus _connectStatus = WorkerStatus.connecting;
  41. // 左侧收缩压(高压)
  42. var leftHightValue = '';
  43. // 左侧舒张压(低压)
  44. var leftLowValue = '';
  45. // 右侧收缩压(高压)
  46. var rightHightValue = '';
  47. // 右侧舒张压(低压)
  48. var rightLowValue = '';
  49. @override
  50. void initState() {
  51. WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
  52. currentDevice();
  53. await initData();
  54. });
  55. super.initState();
  56. }
  57. @override
  58. void didUpdateWidget(ExamBloodPressure oldWidget) {
  59. super.didUpdateWidget(oldWidget);
  60. }
  61. Future<void> currentDevice() async {
  62. DeviceModel? device = await controller.getDevice(DeviceTypes.NIBP);
  63. if (device == null) {
  64. _connectStatus = WorkerStatus.unboundDevice;
  65. worker = null;
  66. setState(() {});
  67. return;
  68. }
  69. nibp = DeviceControllerManager(DeviceTypes.NIBP, device.model, device.mac);
  70. worker = nibp!.worker as NibpDeviceWorker;
  71. _connectStatus = nibp!.connectStatus;
  72. loadListeners();
  73. connect();
  74. }
  75. Future<void> initData() async {
  76. /// 体检系统 基础检查的特殊处理
  77. if (controller.diagnosisDataValue['Sbp_Left'] != null ||
  78. controller.diagnosisDataValue['Dbp_Left'] != null ||
  79. controller.diagnosisDataValue['Sbp_Right'] != null ||
  80. controller.diagnosisDataValue['Dbp_Right'] != null) {
  81. leftHightValue = controller.diagnosisDataValue['Sbp_Left'] ?? '';
  82. leftLowValue = controller.diagnosisDataValue['Dbp_Left'] ?? '';
  83. rightHightValue = controller.diagnosisDataValue['Sbp_Right'] ?? '';
  84. rightLowValue = controller.diagnosisDataValue['Dbp_Right'] ?? '';
  85. logger.i(
  86. '_ExamBloodSugarState initData leftHightValue:$leftHightValue leftLowValue:$leftLowValue rightHightValue:$rightHightValue rightLowValue:$rightLowValue');
  87. setState(() {});
  88. return;
  89. }
  90. }
  91. void loadListeners() {
  92. worker!.liveUpdateEvent.addListener(_onLiveUpdate);
  93. worker!.errorEvent.addListener(_onError);
  94. worker!.resultUpdateEvent.addListener(_onSuccess);
  95. worker!.connectErrorEvent.addListener(_onConnectFail);
  96. worker!.connectedEvent.addListener(_onConnectSuccess);
  97. worker!.disconnectedEvent.addListener(_onDisconnected);
  98. }
  99. void releaseListeners() {
  100. if (worker != null) {
  101. worker!.connectErrorEvent.removeListener(_onConnectFail);
  102. worker!.connectedEvent.removeListener(_onConnectSuccess);
  103. worker!.liveUpdateEvent.removeListener(_onLiveUpdate);
  104. worker!.errorEvent.removeListener(_onError);
  105. worker!.resultUpdateEvent.removeListener(_onSuccess);
  106. worker!.disconnectedEvent.removeListener(_onDisconnected);
  107. }
  108. }
  109. Future<void> connect() async {
  110. try {
  111. if (worker != null) {
  112. await worker!.connect();
  113. }
  114. } catch (err) {
  115. print(err);
  116. }
  117. }
  118. Future<void> disconnect() async {
  119. try {
  120. if (worker != null) {
  121. await worker!.disconnect();
  122. }
  123. } catch (err) {
  124. print(err);
  125. }
  126. }
  127. /// 尝试重连
  128. Future<void> tryReconnect() async {
  129. if (worker != null) {
  130. await disconnect();
  131. await connect();
  132. }
  133. }
  134. @override
  135. void dispose() {
  136. nibp?.dispose();
  137. nibp = null;
  138. releaseListeners();
  139. disconnect();
  140. worker?.dispose();
  141. super.dispose();
  142. }
  143. void _onLiveUpdate(_, int e) {
  144. setState(() {
  145. liveValue = e;
  146. });
  147. }
  148. /// 检测报错
  149. void _onError(_, String errorMessage) async {
  150. logger.i('errorMessage:$errorMessage ${DateTime.now().toString()}');
  151. final message = errorMessage.replaceAll("1506|Error|", "");
  152. setState(() {
  153. isShowError = true;
  154. this.errorMessage = message;
  155. liveValue = 0;
  156. pressureDeviceStatus = PressureDeviceStatus.end;
  157. });
  158. }
  159. /// TODO 需求不清,检测的数据需要传给体检,但是检测又不区分左右侧血压
  160. void _onSuccess(_, NibpExamValue e) async {
  161. logger.i(
  162. '检测完成,高压:${e.systolicPressure},低压:${e.diastolicPressure},脉率:${e.pulse}');
  163. int sbp = e.systolicPressure;
  164. int dbp = e.diastolicPressure;
  165. liveValue = 0;
  166. final orgManager = Get.find<IOrganizationManager>();
  167. final paramSbp = await orgManager.getDynamicParamByKey("Sbp");
  168. if (paramSbp != null) {
  169. sbp = nibp!.handleExamValueSpecially(sbp, paramSbp);
  170. logger.i('NIBP Value handle specially - Sbp: $sbp.');
  171. }
  172. final paramDbp = await orgManager.getDynamicParamByKey("Dbp");
  173. if (paramDbp != null) {
  174. dbp = nibp!.handleExamValueSpecially(dbp, paramDbp);
  175. logger.i('NIBP Value handle specially - Dbp:$dbp.');
  176. }
  177. setState(() {
  178. pressureDeviceStatus = PressureDeviceStatus.end;
  179. if (controller.diagnosisDataValue['NIBP'] == null) {
  180. controller.diagnosisDataValue['NIBP'] = {};
  181. }
  182. // controller.diagnosisDataValue['NIBP'] = {};
  183. if (pressureStatus == PressureStatus.left) {
  184. controller.diagnosisDataValue['NIBP'].addAll({
  185. 'Sbp_Left': sbp.toString(),
  186. 'Dbp_Left': dbp.toString(),
  187. 'Pulse_Beat': e.pulse.toString(),
  188. });
  189. logger.i('NIBP Value is left: $sbp, $dbp, $e');
  190. } else if (pressureStatus == PressureStatus.right) {
  191. controller.diagnosisDataValue['NIBP'].addAll({
  192. 'Sbp_Right': sbp.toString(),
  193. 'Dbp_Right': dbp.toString(),
  194. 'Pulse_Beat': e.pulse.toString(),
  195. });
  196. logger.i('NIBP Value is right: $sbp, $dbp, $e');
  197. }
  198. // controller.diagnosisDataValue['NIBP'] = {
  199. // 'Sbp': sbp.toString(),
  200. // 'Dbp': dbp.toString(),
  201. // 'Pulse_Beat': e.pulse.toString(),
  202. // };
  203. controller.saveCachedRecord();
  204. });
  205. }
  206. void _onConnectFail(sender, e) {
  207. logger.i("连接设备失败:${worker!.mac}");
  208. print('连接设备失败:${worker!.mac},errorCount:$errorCount,${DateTime.now()}');
  209. if (errorCount < 3) {
  210. print('连接设备失败:${worker!.mac},$errorCount,${DateTime.now()}');
  211. logger.i('连接设备失败:${worker!.mac},$errorCount,${DateTime.now()}');
  212. errorCount++;
  213. tryReconnect();
  214. } else {
  215. print('连接设备失败:${worker!.mac},$errorCount,${DateTime.now()}');
  216. logger.i('连接设备失败:${worker!.mac},$errorCount,${DateTime.now()}');
  217. isConnectFail = true;
  218. }
  219. _connectStatus = WorkerStatus.connectionFailed;
  220. setState(() {});
  221. }
  222. void _onDisconnected(sender, e) {
  223. logger.i("设备连接中断:${worker!.mac}");
  224. print('设备连接中断:${worker!.mac}');
  225. tryReconnect();
  226. errorCount = 0;
  227. _connectStatus = WorkerStatus.disconnected;
  228. setState(() {});
  229. }
  230. void _onConnectSuccess(sender, e) {
  231. logger.i("设备连接成功:$e");
  232. print("设备连接成功:$e");
  233. setState(() {
  234. _connectStatus = WorkerStatus.connected;
  235. errorCount = 0;
  236. isConnectFail = false;
  237. isShowError = false;
  238. });
  239. }
  240. Widget _buildValue() {
  241. if (controller.diagnosisDataValue['NIBP'] == null) {
  242. return _buildLiveWidget();
  243. }
  244. return _buildLiveWidget();
  245. }
  246. Widget _buildButtonGroup() {
  247. return Container(
  248. margin: EdgeInsets.only(right: 16),
  249. child: AnimatedToggle(
  250. values: const ['左侧', '右侧'],
  251. onToggleCallback: (value) {
  252. setState(() {
  253. pressureStatus = value;
  254. });
  255. },
  256. statusValue: pressureStatus,
  257. buttonColor: Theme.of(context).primaryColor,
  258. backgroundColor: const Color(0xFFB5C1CC),
  259. textColor: const Color(0xFFFFFFFF),
  260. ),
  261. );
  262. }
  263. @override
  264. Widget build(BuildContext context) {
  265. final nibp = controller.diagnosisDataValue['NIBP'];
  266. String pulsebeat = "";
  267. if (nibp != null && nibp is Map) {
  268. // 左侧收缩压(高压)
  269. leftHightValue = nibp['Sbp_Left']?.toString() ?? '';
  270. // 左侧舒张压(低压)
  271. leftLowValue = nibp['Dbp_Left']?.toString() ?? '';
  272. // 右侧收缩压(高压)
  273. rightHightValue = nibp['Sbp_Right']?.toString() ?? '';
  274. // 右侧舒张压(低压)
  275. rightLowValue = nibp['Dbp_Right']?.toString() ?? '';
  276. if (nibp.containsKey("Pulse_Beat")) {
  277. pulsebeat = nibp['Pulse_Beat'];
  278. }
  279. setState(() {});
  280. }
  281. if (controller.diagnosisDataValue.containsKey("Pulse_Beat")) {
  282. pulsebeat = controller.diagnosisDataValue['Pulse_Beat'];
  283. }
  284. return Stack(
  285. children: [
  286. ExamCard(
  287. title: '',
  288. clickCard: _onClickBloodPressure,
  289. content: Column(
  290. crossAxisAlignment: CrossAxisAlignment.end,
  291. children: [
  292. SizedBox(
  293. height: 16,
  294. ),
  295. _buildButtonGroup(),
  296. Row(
  297. children: [
  298. const SizedBox(
  299. width: 25,
  300. ),
  301. const Text(
  302. '血压',
  303. style: TextStyle(fontSize: 25),
  304. ),
  305. const Expanded(child: SizedBox()),
  306. _buildValue(),
  307. ],
  308. ),
  309. Row(
  310. children: [
  311. const SizedBox(
  312. width: 25,
  313. ),
  314. const Text(
  315. '左侧血压',
  316. style: TextStyle(fontSize: 25),
  317. ),
  318. const Expanded(child: SizedBox()),
  319. if (leftHightValue.isNotEmpty || leftLowValue.isNotEmpty)
  320. _buildResultWidget(leftHightValue, leftLowValue),
  321. ],
  322. ),
  323. Row(
  324. children: [
  325. const SizedBox(
  326. width: 25,
  327. ),
  328. const Text(
  329. '右侧血压',
  330. style: TextStyle(fontSize: 25),
  331. ),
  332. const Expanded(child: SizedBox()),
  333. if (rightHightValue.isNotEmpty || rightLowValue.isNotEmpty)
  334. _buildResultWidget(rightHightValue, rightLowValue),
  335. ],
  336. ),
  337. Row(
  338. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  339. children: [
  340. const SizedBox(
  341. width: 25,
  342. ),
  343. const Text(
  344. '脉率',
  345. style: TextStyle(fontSize: 25),
  346. ),
  347. const Expanded(child: SizedBox()),
  348. Text(
  349. pulsebeat,
  350. style: const TextStyle(
  351. fontSize: 45,
  352. color: Colors.black,
  353. ),
  354. ),
  355. const Text(
  356. ' bpm',
  357. style: TextStyle(fontSize: 25),
  358. ),
  359. const SizedBox(
  360. width: 30,
  361. ),
  362. ],
  363. ),
  364. Container(
  365. height: 30,
  366. margin: const EdgeInsets.only(bottom: 10),
  367. child: isShowError
  368. ? Row(
  369. children: [
  370. const SizedBox(
  371. width: 25,
  372. ),
  373. Text(
  374. '检测失败:',
  375. style: TextStyle(
  376. fontSize: 20,
  377. ),
  378. ),
  379. Text(
  380. '${errorMessage}',
  381. style: TextStyle(fontSize: 20, color: Colors.grey),
  382. ),
  383. const Expanded(child: SizedBox()),
  384. ],
  385. )
  386. : SizedBox(),
  387. ),
  388. ],
  389. ),
  390. ),
  391. if (!isPureSoftwareMode) ...[
  392. if (isConnectFail) ...[
  393. _buildErrorButton(),
  394. ] else ...[
  395. Positioned(
  396. right: 10,
  397. top: 10,
  398. child: DeviceStatus(connectStatus: _connectStatus),
  399. ),
  400. ],
  401. ],
  402. ],
  403. );
  404. }
  405. /// 需要封装一下
  406. Widget _buildErrorButton() {
  407. return DeviceStatusPosition(
  408. deviceStatus: Row(
  409. children: [
  410. const Text(
  411. '请确认设备是否启动',
  412. style: TextStyle(fontSize: 24, color: Colors.red),
  413. ),
  414. IconButton(
  415. onPressed: () {
  416. liveValue = 0;
  417. tryReconnect();
  418. setState(() {
  419. _connectStatus = WorkerStatus.connecting;
  420. isConnectFail = false;
  421. });
  422. },
  423. icon: const Icon(Icons.refresh),
  424. iconSize: 32,
  425. ),
  426. ],
  427. ),
  428. );
  429. }
  430. Widget _buildLiveWidget() {
  431. return SizedBox(
  432. height: 70,
  433. child: Row(
  434. crossAxisAlignment: CrossAxisAlignment.center,
  435. children: [
  436. Container(
  437. alignment: Alignment.center,
  438. child: Text(
  439. liveValue.toString() == '0' ? '--' : liveValue.toString(),
  440. style: const TextStyle(
  441. fontSize: 60,
  442. color: Colors.black,
  443. ),
  444. ),
  445. ),
  446. const Text(
  447. ' mmHg',
  448. style: TextStyle(fontSize: 25),
  449. ),
  450. const SizedBox(
  451. width: 30,
  452. )
  453. ],
  454. ),
  455. );
  456. }
  457. Widget _buildResultWidget(String? sbp, String? dbp) {
  458. const textStyle = TextStyle(
  459. fontSize: 45,
  460. color: Colors.black,
  461. );
  462. var normalStyle = const TextStyle(
  463. fontSize: 25,
  464. color: Colors.black,
  465. );
  466. var uuitText = const Text(
  467. ' mmHg',
  468. style: TextStyle(
  469. fontSize: 25,
  470. color: Colors.black,
  471. ),
  472. );
  473. return Stack(
  474. children: [
  475. Column(
  476. mainAxisAlignment: MainAxisAlignment.center,
  477. mainAxisSize: MainAxisSize.min,
  478. crossAxisAlignment: CrossAxisAlignment.end,
  479. children: [
  480. Row(
  481. children: [
  482. Text(
  483. '收缩压',
  484. style: normalStyle,
  485. ),
  486. Container(
  487. alignment: Alignment.centerRight,
  488. width: 200,
  489. child: Text(
  490. sbp ?? '',
  491. style: textStyle,
  492. ),
  493. ),
  494. uuitText,
  495. const SizedBox(
  496. width: 30,
  497. ),
  498. ],
  499. ),
  500. Row(
  501. children: [
  502. Text(
  503. '舒张压',
  504. style: normalStyle,
  505. ),
  506. Container(
  507. alignment: Alignment.centerRight,
  508. width: 200,
  509. child: Text(
  510. dbp ?? '',
  511. style: textStyle,
  512. ),
  513. ),
  514. uuitText,
  515. const SizedBox(
  516. width: 30,
  517. ),
  518. ],
  519. ),
  520. ],
  521. ),
  522. ],
  523. );
  524. }
  525. Future<void> _onClickBloodPressure() async {
  526. String sbp = "";
  527. String dbp = "";
  528. String heart = "";
  529. Map<String, dynamic> diagnosisDataValue = controller.diagnosisDataValue;
  530. ///结构化的数据填充
  531. if (controller.diagnosisDataValue.containsKey("NIBP")) {
  532. var nibp = controller.diagnosisDataValue["NIBP"];
  533. if (nibp is Map) {
  534. if (pressureStatus == PressureStatus.left) {
  535. if (nibp.containsKey("Sbp_Left")) {
  536. sbp = nibp["Sbp_Left"];
  537. }
  538. if (nibp.containsKey("Dbp_Left")) {
  539. dbp = nibp["Dbp_Left"];
  540. }
  541. if (nibp.containsKey("Pulse_Beat")) {
  542. heart = nibp["Pulse_Beat"];
  543. }
  544. } else {
  545. if (nibp.containsKey("Sbp_Right")) {
  546. sbp = nibp["Sbp_Right"];
  547. }
  548. if (nibp.containsKey("Dbp_Right")) {
  549. dbp = nibp["Dbp_Right"];
  550. }
  551. if (nibp.containsKey("Pulse_Beat")) {
  552. heart = nibp["Pulse_Beat"];
  553. }
  554. }
  555. }
  556. }
  557. ///平铺方式的数据填充
  558. if (pressureStatus == PressureStatus.left) {
  559. if (diagnosisDataValue.containsKey("Sbp_Left")) {
  560. sbp = diagnosisDataValue["Sbp_Left"];
  561. }
  562. if (diagnosisDataValue.containsKey("Dbp_Left")) {
  563. dbp = diagnosisDataValue["Dbp_Left"];
  564. }
  565. } else {
  566. if (diagnosisDataValue.containsKey("Sbp_Right")) {
  567. sbp = diagnosisDataValue["Sbp_Left"];
  568. }
  569. if (diagnosisDataValue.containsKey("Dbp_Right")) {
  570. dbp = diagnosisDataValue["Dbp_Right"];
  571. }
  572. }
  573. if (diagnosisDataValue.containsKey("Pulse_Beat")) {
  574. heart = diagnosisDataValue["Pulse_Beat"];
  575. }
  576. String? result = await VDialogBloodPressure(
  577. title: '血压',
  578. initialValue: [
  579. sbp,
  580. dbp,
  581. ],
  582. heartRate: heart,
  583. isDisplayHeartRate: true,
  584. ).show();
  585. if (result != null) {
  586. try {
  587. String sbp = jsonDecode(result).first;
  588. String dbp = jsonDecode(result)[1];
  589. String heart = jsonDecode(result)[2];
  590. if (sbp.isNotEmpty && dbp.isNotEmpty) {
  591. if (pressureStatus == PressureStatus.left) {
  592. var nibp = controller.diagnosisDataValue["NIBP"];
  593. String? sdpRight = nibp?['Sbp_Right'];
  594. String? dbpRight = nibp?['Dbp_Right'];
  595. controller.diagnosisDataValue['NIBP'] = {
  596. 'Sbp_Left': sbp,
  597. 'Dbp_Left': dbp,
  598. 'Pulse_Beat': heart,
  599. };
  600. if (sdpRight != null && dbpRight != null) {
  601. controller.diagnosisDataValue['NIBP'].addAll({
  602. 'Sbp_Right': sdpRight,
  603. 'Dbp_Right': dbpRight,
  604. });
  605. }
  606. } else {
  607. var nibp = controller.diagnosisDataValue["NIBP"];
  608. String? sdpLeft = nibp?['Sbp_Left'];
  609. String? dbpLeft = nibp?['Dbp_Left'];
  610. controller.diagnosisDataValue['NIBP'] = {
  611. 'Sbp_Right': sbp,
  612. 'Dbp_Right': dbp,
  613. 'Pulse_Beat': heart,
  614. };
  615. if (sdpLeft != null && dbpLeft != null) {
  616. controller.diagnosisDataValue['NIBP'].addAll({
  617. 'Sbp_Left': sdpLeft,
  618. 'Dbp_Left': dbpLeft,
  619. });
  620. }
  621. }
  622. setState(() {});
  623. }
  624. } catch (e) {
  625. logger.e("BloodPressure _onClickBloodPressure ex:", e);
  626. }
  627. }
  628. }
  629. }