exam_blood_pressure.dart 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. import 'dart:convert';
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. import 'package:get/get.dart';
  5. import 'package:vnote_device_plugin/models/exams/nibp.dart';
  6. import 'package:vitalapp/components/alert_dialog.dart';
  7. import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_card.dart';
  8. import 'package:vitalapp/pages/medical/models/item.dart';
  9. import 'package:vitalapp/pages/medical/widgets/switch_button.dart';
  10. /// 小弹窗输入
  11. class VDialogBloodPressure extends StatelessWidget {
  12. /// 标题
  13. final String? title;
  14. /// 描述
  15. final String? description;
  16. /// 输入占位符
  17. final String? placeholder;
  18. /// 初始值
  19. final List<String>? initialValue;
  20. ///是否显示心率
  21. final bool isDisplayHeartRate;
  22. final String? heartRate;
  23. const VDialogBloodPressure({
  24. super.key,
  25. this.title,
  26. this.description,
  27. this.placeholder,
  28. this.initialValue,
  29. this.heartRate,
  30. this.isDisplayHeartRate = false,
  31. });
  32. Future<String?> show<String>() => VAlertDialog.showDialog<String>(this);
  33. @override
  34. Widget build(BuildContext context) {
  35. final controller1 = TextEditingController(text: initialValue?.first);
  36. final controller2 = TextEditingController(text: initialValue?.last);
  37. final controller3 = TextEditingController(text: heartRate);
  38. return VAlertDialog(
  39. title: title,
  40. width: 440,
  41. contentPadding: const EdgeInsets.symmetric(vertical: 12, horizontal: 24),
  42. content: _buildContent(context, controller1, controller2, controller3),
  43. showCancel: true,
  44. onConfirm: () {
  45. var content = [controller1.text, controller2.text];
  46. if (isDisplayHeartRate) {
  47. content.add(controller3.text);
  48. }
  49. Get.back(result: json.encode(content));
  50. },
  51. );
  52. }
  53. Widget _buildContent(
  54. BuildContext context,
  55. TextEditingController controller1,
  56. TextEditingController controller2,
  57. TextEditingController controller3,
  58. ) {
  59. final children = <Widget>[];
  60. if (description != null) {
  61. children.add(
  62. Padding(
  63. padding: const EdgeInsets.symmetric(horizontal: 4),
  64. child: Text(
  65. description!,
  66. style: const TextStyle(color: Colors.black87, fontSize: 18),
  67. ),
  68. ),
  69. );
  70. children.add(const SizedBox(height: 8));
  71. } else {
  72. children.add(const SizedBox(height: 12));
  73. }
  74. children.add(_buildInputWidget(context, controller1, controller2));
  75. if (isDisplayHeartRate) {
  76. children.add(const SizedBox(height: 12));
  77. children.add(_buildInputHeartRate(context, controller3));
  78. }
  79. return SingleChildScrollView(
  80. child: Column(
  81. mainAxisSize: MainAxisSize.min,
  82. crossAxisAlignment: CrossAxisAlignment.start,
  83. children: children,
  84. ),
  85. );
  86. }
  87. Widget _buildInputWidget(
  88. BuildContext context,
  89. TextEditingController controller1,
  90. TextEditingController controller2,
  91. ) {
  92. const fontSize = 20.0;
  93. return Row(
  94. children: [
  95. _buildItem(context, controller1, '高压'),
  96. const Text(
  97. '/',
  98. style: TextStyle(fontSize: fontSize),
  99. ),
  100. _buildItem(context, controller2, '低压'),
  101. ],
  102. );
  103. }
  104. Widget _buildItem(
  105. BuildContext context,
  106. TextEditingController controller,
  107. String hintText,
  108. ) {
  109. const fontSize = 20.0;
  110. const height = 56.0;
  111. return Expanded(
  112. child: SizedBox(
  113. height: height,
  114. child: TextField(
  115. controller: controller,
  116. readOnly: false,
  117. autofocus: true,
  118. keyboardType: const TextInputType.numberWithOptions(
  119. decimal: true), // 允许输入数字和小数点
  120. inputFormatters: [
  121. FilteringTextInputFormatter.allow(
  122. RegExp(r'^\d+\.?\d{0,2}'),
  123. ), // 只允许输入数字和小数点,俩位小数
  124. ],
  125. style: const TextStyle(fontSize: fontSize),
  126. decoration: InputDecoration(
  127. border: const UnderlineInputBorder(
  128. borderRadius: BorderRadius.zero,
  129. borderSide: BorderSide(),
  130. ),
  131. enabledBorder: const UnderlineInputBorder(
  132. borderRadius: BorderRadius.zero,
  133. borderSide: BorderSide(
  134. color: Colors.black54,
  135. ),
  136. ),
  137. focusedBorder: UnderlineInputBorder(
  138. borderRadius: BorderRadius.zero,
  139. borderSide: BorderSide(
  140. color: Theme.of(context).primaryColor.withOpacity(.4),
  141. ),
  142. ),
  143. filled: true,
  144. fillColor: Colors.white,
  145. contentPadding: const EdgeInsets.symmetric(
  146. vertical: (height - fontSize * 1.2) / 2,
  147. horizontal: 8,
  148. ),
  149. hintStyle: const TextStyle(fontSize: fontSize),
  150. labelStyle: const TextStyle(fontSize: fontSize),
  151. hintText: hintText,
  152. isCollapsed: true,
  153. ),
  154. onSubmitted: (value) {
  155. print(value);
  156. Get.back(result: value);
  157. },
  158. ),
  159. ),
  160. );
  161. }
  162. Widget _buildInputHeartRate(
  163. BuildContext context,
  164. TextEditingController controller,
  165. ) {
  166. return Row(
  167. children: [_buildItem(context, controller, '脉率')],
  168. );
  169. }
  170. }
  171. /// TODO 优化血压组件,存储需要重新设计
  172. // ignore: must_be_immutable
  173. class ExamBloodPressure extends StatefulWidget {
  174. ExamBloodPressure({
  175. super.key,
  176. required this.currentValue,
  177. required this.bloodPressure,
  178. });
  179. Map currentValue;
  180. Function(Map) bloodPressure;
  181. @override
  182. State<ExamBloodPressure> createState() => _ExamBloodPressureState();
  183. }
  184. class _ExamBloodPressureState extends State<ExamBloodPressure> {
  185. PressureStatus pressureStatus = PressureStatus.left;
  186. NibpExamValue? value;
  187. NibpExamValue? leftNibpExamValue;
  188. NibpExamValue? rightNibpExamValue;
  189. String? right;
  190. String? left;
  191. @override
  192. void initState() {
  193. showNibp();
  194. super.initState();
  195. }
  196. @override
  197. void dispose() {
  198. super.dispose();
  199. }
  200. @override
  201. void didUpdateWidget(ExamBloodPressure oldWidget) {
  202. showNibp();
  203. setState(() {});
  204. super.didUpdateWidget(oldWidget);
  205. }
  206. void showNibp() {
  207. if (widget.currentValue['left'] != null &&
  208. widget.currentValue['left'] != "") {
  209. try {
  210. leftNibpExamValue = NibpExamValue(
  211. systolicPressure: int.parse(
  212. jsonDecode(widget.currentValue['left']).first.toString(),
  213. ),
  214. diastolicPressure: int.parse(
  215. jsonDecode(widget.currentValue['left']).last.toString(),
  216. ),
  217. pulse: 0,
  218. );
  219. left = widget.currentValue['left'];
  220. print(widget.currentValue['left']);
  221. } catch (e) {
  222. leftNibpExamValue = null;
  223. }
  224. } else {
  225. leftNibpExamValue = null;
  226. }
  227. if (widget.currentValue['right'] != null &&
  228. widget.currentValue['right'] != "") {
  229. try {
  230. rightNibpExamValue = NibpExamValue(
  231. systolicPressure: int.parse(
  232. jsonDecode(widget.currentValue['right']).first.toString(),
  233. ),
  234. diastolicPressure: int.parse(
  235. jsonDecode(widget.currentValue['right']).last.toString(),
  236. ),
  237. pulse: 0,
  238. );
  239. right = widget.currentValue['right'];
  240. } catch (e) {
  241. rightNibpExamValue = null;
  242. }
  243. } else {
  244. rightNibpExamValue = null;
  245. }
  246. }
  247. @override
  248. Widget build(BuildContext context) {
  249. return Stack(
  250. children: [
  251. ExamCard(
  252. // clickCard: () {},
  253. content: Column(
  254. mainAxisAlignment: MainAxisAlignment.start,
  255. children: [
  256. InkWell(
  257. child: _SideBar(
  258. title: '左侧血压',
  259. value: _buildResult(leftNibpExamValue),
  260. unit: 'mmHg',
  261. ),
  262. onTap: () async {
  263. String? result = await VDialogBloodPressure(
  264. title: '左侧血压',
  265. initialValue: [
  266. leftNibpExamValue?.systolicPressure.toString() ?? '',
  267. leftNibpExamValue?.diastolicPressure.toString() ?? '',
  268. ],
  269. ).show();
  270. if (result != null) {
  271. left = result;
  272. widget.bloodPressure({
  273. 'left': left,
  274. 'right': right,
  275. });
  276. leftNibpExamValue = NibpExamValue(
  277. diastolicPressure: int.parse(jsonDecode(result).last),
  278. systolicPressure: int.parse(jsonDecode(result).first),
  279. pulse: 0,
  280. );
  281. }
  282. setState(() {});
  283. },
  284. ),
  285. const Divider(indent: 3),
  286. InkWell(
  287. child: _SideBar(
  288. title: '右侧血压',
  289. value: _buildResult(rightNibpExamValue),
  290. unit: 'mmHg',
  291. ),
  292. onTap: () async {
  293. String? result = await VDialogBloodPressure(
  294. title: '右侧血压',
  295. initialValue: [
  296. rightNibpExamValue?.systolicPressure.toString() ?? '',
  297. rightNibpExamValue?.diastolicPressure.toString() ?? '',
  298. ],
  299. ).show();
  300. if (result != null) {
  301. right = result;
  302. widget.bloodPressure({
  303. 'left': left,
  304. 'right': right,
  305. });
  306. rightNibpExamValue = NibpExamValue(
  307. diastolicPressure: int.parse(jsonDecode(result).last),
  308. systolicPressure: int.parse(jsonDecode(result).first),
  309. pulse: 0,
  310. );
  311. }
  312. setState(() {});
  313. },
  314. ),
  315. ],
  316. ),
  317. ),
  318. ],
  319. );
  320. }
  321. Widget _buildButtonGroup() {
  322. return AnimatedToggle(
  323. values: const ['左侧', '右侧'],
  324. onToggleCallback: (value) {
  325. setState(() {
  326. pressureStatus = value;
  327. });
  328. },
  329. statusValue: pressureStatus,
  330. buttonColor: Theme.of(context).primaryColor,
  331. backgroundColor: const Color(0xFFB5C1CC),
  332. textColor: const Color(0xFFFFFFFF),
  333. );
  334. }
  335. Widget _buildResult(NibpExamValue? nibpExamValue) {
  336. const textStyle = TextStyle(fontSize: 26, color: Colors.black);
  337. return Row(
  338. children: [
  339. Column(
  340. mainAxisAlignment: MainAxisAlignment.center,
  341. mainAxisSize: MainAxisSize.min,
  342. crossAxisAlignment: CrossAxisAlignment.end,
  343. children: [
  344. Align(
  345. alignment: Alignment.centerLeft,
  346. child: RichText(
  347. textAlign: TextAlign.end,
  348. text: TextSpan(
  349. style: TextStyle(
  350. fontFamily: "NotoSansSC",
  351. fontFamilyFallback: const ["NotoSansSC"],
  352. ),
  353. children: [
  354. TextSpan(
  355. text: nibpExamValue?.systolicPressure.toString() ?? '',
  356. style: textStyle,
  357. ),
  358. TextSpan(
  359. text: ' mmHg',
  360. style: textStyle,
  361. ),
  362. ],
  363. ),
  364. ),
  365. ),
  366. Align(
  367. alignment: Alignment.centerRight,
  368. child: RichText(
  369. textAlign: TextAlign.end,
  370. text: TextSpan(
  371. style: TextStyle(
  372. fontFamily: "NotoSansSC",
  373. fontFamilyFallback: const ["NotoSansSC"],
  374. ),
  375. children: [
  376. TextSpan(
  377. text: nibpExamValue?.diastolicPressure.toString() ?? '',
  378. style: textStyle,
  379. ),
  380. TextSpan(
  381. text: ' mmHg',
  382. style: textStyle,
  383. ),
  384. ],
  385. ),
  386. ),
  387. ),
  388. ],
  389. ),
  390. ],
  391. );
  392. }
  393. Widget _buildResultWidget() {
  394. const textStyle = TextStyle(fontSize: 48, color: Colors.black);
  395. return Row(
  396. children: [
  397. Column(
  398. mainAxisAlignment: MainAxisAlignment.center,
  399. mainAxisSize: MainAxisSize.min,
  400. crossAxisAlignment: CrossAxisAlignment.center,
  401. children: [
  402. Align(
  403. alignment: Alignment.centerLeft,
  404. child: Text(
  405. value!.systolicPressure.toString(),
  406. style: textStyle,
  407. ),
  408. ),
  409. Align(
  410. alignment: Alignment.centerRight,
  411. child: Text(
  412. value!.diastolicPressure.toString(),
  413. style: textStyle,
  414. ),
  415. ),
  416. ],
  417. ),
  418. _buildButtonGroup(),
  419. ],
  420. );
  421. }
  422. }
  423. class _SideBar extends StatelessWidget {
  424. final String title;
  425. final Widget value;
  426. final String unit;
  427. const _SideBar({
  428. required this.title,
  429. required this.value,
  430. required this.unit,
  431. });
  432. @override
  433. Widget build(BuildContext context) {
  434. return Row(
  435. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  436. crossAxisAlignment: CrossAxisAlignment.start,
  437. children: [
  438. Container(
  439. // padding: const EdgeInsets.symmetric(horizontal: 30),
  440. child: Text(
  441. title,
  442. style: const TextStyle(
  443. fontSize: 26,
  444. ),
  445. ),
  446. ),
  447. Container(
  448. alignment: Alignment.bottomRight,
  449. padding: const EdgeInsets.only(
  450. right: 30,
  451. ),
  452. child: Row(
  453. mainAxisAlignment: MainAxisAlignment.end,
  454. crossAxisAlignment: CrossAxisAlignment.end,
  455. children: [
  456. value,
  457. ],
  458. ),
  459. ),
  460. ],
  461. );
  462. }
  463. }
  464. // /// 血压组件
  465. // class ExamBloodPressure extends StatelessWidget {
  466. // const ExamBloodPressure({
  467. // super.key,
  468. // required this.currentFormObject,
  469. // required this.currentBloodPressureValue,
  470. // required this.commonBloodPressure,
  471. // });
  472. // final FormObject currentFormObject;
  473. // final List<String> currentBloodPressureValue;
  474. // final Function commonBloodPressure;
  475. // String get getBloodPressure {
  476. // if (currentBloodPressureValue.first.isNotEmpty ||
  477. // currentBloodPressureValue.last.isNotEmpty) {
  478. // return '${currentBloodPressureValue.first} / ${currentBloodPressureValue.last}';
  479. // } else {
  480. // return '';
  481. // }
  482. // }
  483. // @override
  484. // Widget build(BuildContext context) {
  485. // return ExamCard(
  486. // title: currentFormObject.label ?? '',
  487. // clickCard: () => commonBloodPressure.call(),
  488. // content: Container(
  489. // alignment: Alignment.bottomRight,
  490. // padding: const EdgeInsets.only(bottom: 20, right: 30, left: 40),
  491. // constraints: const BoxConstraints(minHeight: 50),
  492. // child: FittedBox(
  493. // child: Row(
  494. // mainAxisAlignment: MainAxisAlignment.end,
  495. // crossAxisAlignment: CrossAxisAlignment.end,
  496. // children: [
  497. // RichText(
  498. // text: TextSpan(
  499. // text: getBloodPressure,
  500. // style: const TextStyle(
  501. // fontSize: 60,
  502. // color: Colors.black,
  503. // ),
  504. // children: [
  505. // TextSpan(
  506. // text: currentFormObject.append ?? '',
  507. // style: const TextStyle(fontSize: 25),
  508. // )
  509. // ],
  510. // ),
  511. // ),
  512. // ],
  513. // ),
  514. // ),
  515. // ),
  516. // );
  517. // }
  518. // }