follow_blood_pressure.dart 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  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:vitalapp/components/alert_dialog.dart';
  6. import 'package:vitalapp/pages/check/models/form.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:vnote_device_plugin/models/exams/nibp.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 FollowBloodPressure extends StatefulWidget {
  151. FollowBloodPressure({
  152. super.key,
  153. required this.currentValue,
  154. required this.bloodPressure,
  155. required this.currentFormObject,
  156. });
  157. Map currentValue;
  158. Function(Map) bloodPressure;
  159. final FormObject currentFormObject;
  160. @override
  161. State<FollowBloodPressure> createState() => _FollowBloodPressureState();
  162. }
  163. class _FollowBloodPressureState extends State<FollowBloodPressure> {
  164. PressureStatus pressureStatus = PressureStatus.left;
  165. NibpExamValue? value;
  166. NibpExamValue? _nibpExamValue;
  167. @override
  168. void initState() {
  169. if (widget.currentValue['Blood'] != null) {
  170. String currentValue = widget.currentValue['Blood'];
  171. _nibpExamValue = NibpExamValue(
  172. systolicPressure: int.parse(jsonDecode(currentValue).first.toString()),
  173. diastolicPressure: int.parse(jsonDecode(currentValue).last.toString()),
  174. pulse: 0,
  175. );
  176. }
  177. super.initState();
  178. }
  179. void didUpdateWidget(FollowBloodPressure oldWidget) {
  180. if (oldWidget.currentValue != widget.currentValue) {
  181. if (widget.currentValue['Blood'] != null) {
  182. String currentValue = widget.currentValue['Blood'];
  183. _nibpExamValue = NibpExamValue(
  184. systolicPressure:
  185. int.parse(jsonDecode(currentValue).first.toString()),
  186. diastolicPressure:
  187. int.parse(jsonDecode(currentValue).last.toString()),
  188. pulse: 0,
  189. );
  190. setState(() {});
  191. }
  192. }
  193. super.didUpdateWidget(oldWidget);
  194. }
  195. @override
  196. void dispose() {
  197. super.dispose();
  198. }
  199. @override
  200. Widget build(BuildContext context) {
  201. return Stack(
  202. children: [
  203. ExamCard(
  204. content: Column(
  205. mainAxisAlignment: MainAxisAlignment.start,
  206. children: [
  207. InkWell(
  208. child: SideBar(
  209. title: '血压',
  210. value: _buildResult(
  211. _nibpExamValue,
  212. ' mmHg',
  213. ),
  214. unit: 'mmHg',
  215. required: widget.currentFormObject.required,
  216. ),
  217. onTap: () async {
  218. String? result = await VDialogBloodPressure(
  219. title: '血压',
  220. initialValue: [
  221. _nibpExamValue?.systolicPressure.toString() ?? '',
  222. _nibpExamValue?.diastolicPressure.toString() ?? '',
  223. ],
  224. ).show();
  225. if (result != null) {
  226. _nibpExamValue = NibpExamValue(
  227. diastolicPressure: int.parse(jsonDecode(result).last),
  228. systolicPressure: int.parse(jsonDecode(result).first),
  229. pulse: 0,
  230. );
  231. widget.bloodPressure({
  232. "Blood": result,
  233. });
  234. }
  235. setState(() {});
  236. },
  237. ),
  238. ],
  239. ),
  240. ),
  241. ],
  242. );
  243. }
  244. Widget _buildResult(NibpExamValue? nibpExamValue, String? unit) {
  245. const textStyle = TextStyle(fontSize: 26, color: Colors.black);
  246. return Row(
  247. children: [
  248. Column(
  249. mainAxisAlignment: MainAxisAlignment.center,
  250. mainAxisSize: MainAxisSize.min,
  251. crossAxisAlignment: CrossAxisAlignment.center,
  252. children: [
  253. Align(
  254. alignment: Alignment.centerLeft,
  255. child: RichText(
  256. text: TextSpan(
  257. text: nibpExamValue?.systolicPressure.toString() ?? '',
  258. style: textStyle,
  259. children: [
  260. TextSpan(
  261. text: unit ?? '',
  262. style: textStyle.copyWith(fontSize: 20),
  263. ),
  264. ]),
  265. ),
  266. ),
  267. Align(
  268. alignment: Alignment.centerRight,
  269. child: RichText(
  270. text: TextSpan(
  271. text: nibpExamValue?.diastolicPressure.toString() ?? '',
  272. style: textStyle,
  273. children: [
  274. TextSpan(
  275. text: unit ?? '',
  276. style: textStyle.copyWith(fontSize: 20),
  277. ),
  278. ],
  279. ),
  280. ),
  281. ),
  282. ],
  283. ),
  284. ],
  285. );
  286. }
  287. }
  288. class SideBar extends StatelessWidget {
  289. final String title;
  290. final Widget value;
  291. final String unit;
  292. final bool? required;
  293. const SideBar({
  294. required this.title,
  295. required this.value,
  296. required this.unit,
  297. this.required,
  298. });
  299. @override
  300. Widget build(BuildContext context) {
  301. return Row(
  302. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  303. crossAxisAlignment: CrossAxisAlignment.start,
  304. children: [
  305. Container(
  306. // padding: const EdgeInsets.symmetric(horizontal: 30),
  307. child: RichText(
  308. text: TextSpan(
  309. text: "",
  310. children: [
  311. if (required ?? false)
  312. TextSpan(
  313. text: "* ",
  314. style: const TextStyle(color: Colors.red, fontSize: 35),
  315. ),
  316. TextSpan(
  317. text: title,
  318. style: TextStyle(
  319. fontSize: 26,
  320. color: Colors.black,
  321. fontFamily: "NotoSansSC",
  322. fontFamilyFallback: const ["NotoSansSC"],
  323. ),
  324. ),
  325. ],
  326. ),
  327. ),
  328. ),
  329. Container(
  330. alignment: Alignment.bottomRight,
  331. padding: const EdgeInsets.only(
  332. right: 30,
  333. ),
  334. child: Row(
  335. mainAxisAlignment: MainAxisAlignment.end,
  336. crossAxisAlignment: CrossAxisAlignment.end,
  337. children: [
  338. value,
  339. ],
  340. ),
  341. ),
  342. ],
  343. );
  344. }
  345. }