exam_blood_pressure.dart 13 KB

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