configurable_card.dart 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. import 'dart:convert';
  2. import 'package:fis_jsonrpc/rpc.dart';
  3. import 'package:flutter/material.dart';
  4. import 'package:get/get.dart';
  5. import 'package:vnoteapp/components/button.dart';
  6. import 'package:vnoteapp/components/dialog_input.dart';
  7. import 'package:vnoteapp/components/dialog_number.dart';
  8. import 'package:vnoteapp/components/dynamic_drawer.dart';
  9. import 'package:vnoteapp/managers/interfaces/cachedRecord.dart';
  10. import 'package:vnoteapp/managers/interfaces/template.dart';
  11. import 'package:vnoteapp/pages/check/models/form.dart';
  12. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_blood_pressure.dart';
  13. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_blood_sugar.dart';
  14. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_body_temperature.dart';
  15. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_body_weight.dart';
  16. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_boold_oxygen.dart';
  17. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_check_box.dart';
  18. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_health_guidance_check_box.dart';
  19. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_input.dart';
  20. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_number_input.dart';
  21. import 'dart:math' as math;
  22. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_radio.dart';
  23. import 'package:vnoteapp/pages/check/widgets/exam_configurable/exam_radio_score.dart';
  24. import 'package:vnoteapp/pages/check/widgets/device_controller.dart';
  25. const double _width = 170;
  26. const double _height = 38;
  27. String tw = '00.0';
  28. class ConfigurableCard extends StatefulWidget {
  29. final String cardKey;
  30. final Function(String, String, dynamic) callBack;
  31. final Widget? followUpWidget;
  32. final String? patientCode;
  33. final String? examData;
  34. const ConfigurableCard({
  35. super.key,
  36. required this.cardKey,
  37. required this.callBack,
  38. this.followUpWidget,
  39. this.patientCode,
  40. this.examData,
  41. });
  42. @override
  43. State<ConfigurableCard> createState() => _ConfigurableFormState();
  44. }
  45. class _ConfigurableFormState extends State<ConfigurableCard> {
  46. /// 当前最新的模板的键值对
  47. Map<String, dynamic> templateRelation = {};
  48. /// 当前模板数据
  49. List<FormObject> currentTemplate = [];
  50. /// 当前title的下标
  51. int currentTitleIndex = 0;
  52. Map<String, dynamic> formValue = {};
  53. var scaffoldKey = GlobalKey<ScaffoldState>();
  54. final _templateManager = Get.find<ITemplateManager>();
  55. final _cachedRecordManager = Get.find<ICachedRecordManager>();
  56. final arrowHeight = math.tan(120 / 180) * (_height / 2);
  57. List<String> deviceList = ['Temp', 'GLU', 'NIBP', 'SpO2', 'BMI'];
  58. Map<String, dynamic> deviceCached = {};
  59. @override
  60. void initState() {
  61. Get.put(DeviceController());
  62. super.initState();
  63. initTemplate();
  64. }
  65. @override
  66. void dispose() {
  67. super.dispose();
  68. }
  69. Future<void> initTemplate() async {
  70. await fetchTemplateIndex();
  71. await fetchTemplate(widget.cardKey);
  72. fetchTemplateData();
  73. // if (!['GXY', 'TNB'].contains(widget.cardKey)) {
  74. // fetchTemplateData();
  75. // }
  76. }
  77. /// 读取健康检测的缓存
  78. Future<String?> readCachedRecord(String currentDevice) async {
  79. if (widget.patientCode.isNull) {
  80. return null;
  81. }
  82. String? value = await _cachedRecordManager.readCachedRecord(
  83. currentDevice,
  84. widget.patientCode!,
  85. 'ZLZS',
  86. );
  87. return value;
  88. }
  89. /// 读取体检的缓存
  90. Future<String?> readCachedCheck() async {
  91. if (widget.patientCode.isNull) {
  92. return null;
  93. }
  94. String? value = await _cachedRecordManager.readCachedRecord(
  95. widget.cardKey,
  96. widget.patientCode!,
  97. 'EXAM',
  98. );
  99. return value;
  100. }
  101. Future<void> readCached() async {
  102. for (var element in deviceList) {
  103. String? value = await readCachedRecord(element);
  104. if (value?.isNotEmpty ?? false) {
  105. deviceCached.addAll(jsonDecode(value!));
  106. }
  107. }
  108. }
  109. Future<void> fetchTemplateIndex() async {
  110. try {
  111. /// 获取模板的键值对
  112. String? templates;
  113. templates = await _templateManager.readTemplate('templateRelation');
  114. templateRelation = jsonDecode(templates!);
  115. setState(() {});
  116. } catch (error) {
  117. print('发生错误: $error');
  118. }
  119. }
  120. Future<void> fetchTemplateData() async {
  121. // / 这逻辑需要优化
  122. if (widget.examData?.isNotEmpty ?? false) {
  123. formValue = jsonDecode(widget.examData!);
  124. return;
  125. }
  126. String? value = await readCachedCheck();
  127. await readCached();
  128. if (deviceCached.isNotEmpty) {
  129. formValue = deviceCached;
  130. setState(() {});
  131. return;
  132. }
  133. if (value?.isNotEmpty ?? false) {
  134. formValue = jsonDecode(value!);
  135. }
  136. formValue.forEach(
  137. (key, value) {
  138. if (value is List) {
  139. formValue[key] = List<String>.from(formValue[key]);
  140. }
  141. },
  142. );
  143. setState(() {});
  144. }
  145. Future<void> fetchTemplate(String key) async {
  146. try {
  147. if (templateRelation[key] == null) {
  148. currentTemplate = [];
  149. setState(() {});
  150. return;
  151. }
  152. var template =
  153. await _templateManager.readTemplate(templateRelation[key]!);
  154. String templateContent =
  155. TemplateDTO.fromJson(jsonDecode(template!)).templateContent!;
  156. List<Map<String, dynamic>> list =
  157. jsonDecode(templateContent).cast<Map<String, dynamic>>();
  158. for (var i in list) {
  159. if (i['children'] != null) {
  160. List<FormObject> currentChildren = [];
  161. for (var j in i['children']) {
  162. currentChildren.add(FormObject.fromJson(j));
  163. }
  164. i['children'] = currentChildren;
  165. }
  166. var item = FormObject.fromJson(i);
  167. currentTemplate.add(item);
  168. if (widget.cardKey == 'LNRZYYJKGLFWJL') {
  169. formValue[item.key!] = calculatePhysicalFitnessScore(item.key!);
  170. }
  171. }
  172. // var list = jsonDecode(templateContent).cast<Map<String, dynamic>>();
  173. // if (list == null) {
  174. // currentTemplate = [];
  175. // } else {
  176. // currentTemplate = updateChildren(list);
  177. // }
  178. // TextStorage t = TextStorage(
  179. // fileName: key,
  180. // directory: "template",
  181. // );
  182. // await t.save(jsonEncode(currentTemplate));
  183. setState(() {});
  184. } catch (error) {
  185. print('发生错误: $error');
  186. }
  187. }
  188. @override
  189. Widget build(BuildContext context) {
  190. return Scaffold(
  191. key: scaffoldKey,
  192. endDrawer: VDynamicDrawerWrapper(scaffoldKey: scaffoldKey),
  193. resizeToAvoidBottomInset: false,
  194. body: Column(
  195. children: [
  196. Row(
  197. crossAxisAlignment: CrossAxisAlignment.start,
  198. children: [
  199. const SizedBox(
  200. width: 16,
  201. ),
  202. Container(
  203. margin: const EdgeInsets.only(top: 8),
  204. width: 130,
  205. height: 54,
  206. child: VButton(
  207. onTap: () => {Get.back()},
  208. child: const Row(
  209. mainAxisAlignment: MainAxisAlignment.center,
  210. children: [
  211. Icon(Icons.arrow_back_ios_new, size: 24),
  212. SizedBox(
  213. width: 8,
  214. ),
  215. Text("返回", style: TextStyle(fontSize: 20)),
  216. ],
  217. ),
  218. ),
  219. ),
  220. Expanded(
  221. child: Container(
  222. padding:
  223. const EdgeInsets.symmetric(vertical: 16, horizontal: 10),
  224. child: _buildTitleList(),
  225. ),
  226. ),
  227. Container(
  228. margin: const EdgeInsets.only(top: 8, right: 16),
  229. width: 130,
  230. height: 54,
  231. child: VButton(
  232. onTap: () {
  233. widget.callBack(
  234. widget.cardKey,
  235. templateRelation[widget.cardKey]!,
  236. jsonEncode(formValue),
  237. );
  238. Get.back();
  239. },
  240. child: const Row(
  241. mainAxisAlignment: MainAxisAlignment.center,
  242. children: [
  243. Icon(Icons.save, size: 24),
  244. SizedBox(
  245. width: 8,
  246. ),
  247. Text("保存", style: TextStyle(fontSize: 20)),
  248. ],
  249. ),
  250. ),
  251. ),
  252. ],
  253. ),
  254. Expanded(
  255. child: Stack(
  256. children: [
  257. Row(
  258. mainAxisAlignment: MainAxisAlignment.start,
  259. crossAxisAlignment: CrossAxisAlignment.start,
  260. children: [
  261. _buildDiagram(),
  262. _buildContent(),
  263. ],
  264. ),
  265. if (currentTitleIndex != currentTemplate.length - 1)
  266. _buildPositionedButton(
  267. () async {
  268. currentTitleIndex++;
  269. setState(() {});
  270. },
  271. right: -30,
  272. ),
  273. ],
  274. ),
  275. )
  276. ],
  277. ),
  278. );
  279. }
  280. Widget buildSingleItem(Widget item, int span) {
  281. return FractionallySizedBox(
  282. widthFactor: span == 24 ? 1 : 0.5,
  283. child: item,
  284. );
  285. }
  286. Widget buildWidget(FormObject? currentFormObject) {
  287. Map<String, Widget Function(FormObject)> widgetMap = {
  288. 'checkbox': _buildCheckBox,
  289. 'numberInput': _buildNumberInput,
  290. 'input': _buildInput,
  291. 'radio': _buildRadio,
  292. 'radioScore': _buildRadioScore,
  293. 'bloodPressure': _buildBloodPressure,
  294. 'bodyTemperature': _buildBodyTemperature,
  295. 'weight': _buildBodyWeight,
  296. 'sugar': _buildBodySugar,
  297. 'bloodOxygen': _buildBloodOxygen,
  298. 'healthGuidanceCheckBox': _buildHealthGuidanceCheckBox
  299. };
  300. Widget Function(FormObject) builder =
  301. widgetMap[currentFormObject?.type] ?? _buildInput;
  302. return builder(currentFormObject!);
  303. }
  304. Widget flowCardList() {
  305. int itemCount = 0;
  306. bool currentTemplateOptionsIsNotEmpty = false;
  307. if (currentTemplate.isNotEmpty) {
  308. itemCount = currentTemplate[currentTitleIndex].children?.length ?? 0;
  309. currentTemplateOptionsIsNotEmpty =
  310. currentTemplate[currentTitleIndex].options?.isNotEmpty ?? false;
  311. }
  312. List<Widget> items = List.generate(itemCount, (index) {
  313. FormObject? currentFormObject =
  314. currentTemplate[currentTitleIndex].children?[index];
  315. int span = currentFormObject?.span ?? 12;
  316. //父结构的options不等于空或null
  317. if (true) {
  318. //子结构的options若是无值则取父类的options值
  319. if (currentTemplateOptionsIsNotEmpty) {
  320. currentFormObject!.options =
  321. currentTemplate[currentTitleIndex].options;
  322. }
  323. }
  324. return buildSingleItem(buildWidget(currentFormObject), span);
  325. });
  326. return Scrollbar(
  327. thumbVisibility: true,
  328. child: SingleChildScrollView(
  329. child: Container(
  330. alignment: Alignment.topCenter,
  331. padding: const EdgeInsets.all(15),
  332. child: Wrap(
  333. runSpacing: 20, // 纵向元素间距
  334. alignment: WrapAlignment.start,
  335. children: items,
  336. ),
  337. ),
  338. ),
  339. );
  340. }
  341. /// title标签
  342. Widget _buildTitleList() {
  343. return Wrap(
  344. runSpacing: 10, // 设置子小部件之间的间距
  345. spacing: -12,
  346. alignment: WrapAlignment.start,
  347. children: currentTemplate.asMap().entries.map(
  348. (e) {
  349. /// TODO 这边需要改下
  350. MaterialColor currentColors = Colors.grey;
  351. e.value.children?.forEach((element) {
  352. if (formValue.containsKey(element.key)) {
  353. if (!(widget.cardKey == 'LNRZYYJKGLFWJL' &&
  354. element.parentKey == 'Ping_Score')) {
  355. currentColors = Colors.green;
  356. } else if (element.key == 'qusition1') {
  357. {
  358. currentColors = Colors.green;
  359. }
  360. }
  361. }
  362. });
  363. return TitleClipRect(
  364. title: e.value.label ?? '',
  365. color: currentTitleIndex == e.key ? null : currentColors,
  366. arrowHeight: arrowHeight,
  367. clickTitle: () {
  368. // if (widget.cardKey == 'LNRZYYJKGLFWJL' &&
  369. // currentTitleIndex == 0 &&
  370. // formValue.length != 33) {
  371. // PromptBox.toast('题目未答全,不可进行指导');
  372. // } else
  373. {
  374. currentTitleIndex = e.key;
  375. setState(() {});
  376. }
  377. },
  378. );
  379. },
  380. ).toList(),
  381. );
  382. }
  383. /// 示意图
  384. Widget _buildDiagram() {
  385. return Expanded(
  386. flex: 1,
  387. child: Stack(
  388. children: [
  389. /// TODO BAKA-优化
  390. ['GXY', 'TNB', 'LNRZYYJKGLFWJL'].contains(widget.cardKey)
  391. ? widget.followUpWidget!
  392. : Container(
  393. alignment: Alignment.topCenter,
  394. margin: const EdgeInsets.all(16).copyWith(top: 0),
  395. child: Image.asset(
  396. 'assets/images/exam/normalMeasurementChart.png',
  397. height: double.infinity,
  398. fit: BoxFit.fitWidth, // 设置图像的适应方式
  399. ),
  400. ),
  401. if (currentTitleIndex != 0)
  402. _buildPositionedButton(
  403. () async {
  404. if (currentTitleIndex == 0) {
  405. Get.back();
  406. } else {
  407. currentTitleIndex--;
  408. setState(() {});
  409. }
  410. },
  411. left: -30,
  412. ),
  413. ],
  414. ),
  415. );
  416. }
  417. /// 按钮
  418. Widget _buildPositionedButton(Function onTap, {double? right, double? left}) {
  419. return Positioned(
  420. right: right,
  421. left: left,
  422. bottom: 0,
  423. top: 0,
  424. child: Container(
  425. width: 100,
  426. height: 100,
  427. alignment: Alignment.centerLeft,
  428. child: InkWell(
  429. onTap: () => onTap.call(),
  430. child: Container(
  431. width: 100,
  432. height: 100,
  433. padding: const EdgeInsets.all(20),
  434. alignment:
  435. right.isNull ? Alignment.centerRight : Alignment.centerLeft,
  436. decoration: BoxDecoration(
  437. borderRadius: BorderRadius.circular(50),
  438. color: Theme.of(context).primaryColor.withOpacity(
  439. .8,
  440. ),
  441. ),
  442. child: Image.asset(
  443. right.isNull
  444. ? "assets/images/exam/left-arrow.png"
  445. : "assets/images/exam/right-arrow.png",
  446. width: 40,
  447. height: 40,
  448. color: Colors.white,
  449. fit: BoxFit.contain,
  450. ),
  451. ),
  452. ),
  453. ),
  454. );
  455. }
  456. /// 主页面
  457. Widget _buildContent() {
  458. return Expanded(
  459. flex: 2,
  460. child: flowCardList(),
  461. );
  462. }
  463. /// 多选框组件
  464. Widget _buildCheckBox(FormObject currentFormObject) {
  465. List<Option> options = currentFormObject.options ?? [];
  466. List<dynamic> currentSelectedCheckBox =
  467. formValue[currentFormObject.key!] ?? [];
  468. void selectCheckBoxChange(Option e) {
  469. if (currentSelectedCheckBox.contains(e.value)) {
  470. currentSelectedCheckBox.remove(e.value);
  471. } else {
  472. currentSelectedCheckBox.add(e.value ?? '');
  473. }
  474. formValue[currentFormObject.key!] = currentSelectedCheckBox;
  475. setState(() {});
  476. }
  477. return ExamCheckBox(
  478. options: options,
  479. currentSelectedCheckBox: currentSelectedCheckBox,
  480. currentFormObject: currentFormObject,
  481. selectCheckBoxChange: selectCheckBoxChange,
  482. );
  483. }
  484. ///中医药健康指导多选框组件
  485. Widget _buildHealthGuidanceCheckBox(FormObject currentFormObject) {
  486. List<Option> options = currentFormObject.options ?? [];
  487. List<String> currentSelectedCheckBox =
  488. formValue[currentFormObject.key!] ?? [];
  489. var templateFromObj = currentTemplate.firstWhere(
  490. (element) => element.label!.contains(currentFormObject.label!));
  491. int score = formValue[templateFromObj.key!] ?? 0;
  492. String judgmentResult = getJudgmentResult(templateFromObj);
  493. void selectCheckBoxChange(Option e) {
  494. if (currentSelectedCheckBox.contains(e.value)) {
  495. currentSelectedCheckBox.remove(e.value);
  496. } else {
  497. currentSelectedCheckBox.add(e.value ?? '');
  498. }
  499. formValue[currentFormObject.key!] = currentSelectedCheckBox;
  500. setState(() {});
  501. }
  502. return ExamHealthGuidanceCheckBox(
  503. options: options,
  504. currentSelectedCheckBox: currentSelectedCheckBox,
  505. currentFormObject: currentFormObject,
  506. selectCheckBoxChange: selectCheckBoxChange,
  507. score: score,
  508. judgmentResult: judgmentResult,
  509. );
  510. }
  511. String getJudgmentResult(FormObject currentForm) {
  512. if (currentForm.label!.contains('平和质')) {
  513. var score = calculatePhysicalFitnessScore(currentForm.key!);
  514. if (score >= 17) {
  515. bool is8 = true;
  516. bool is10 = true;
  517. for (var e in currentTemplate) {
  518. if (formValue[e.key!] != null && e.key != 'Ping_Score') {
  519. score = formValue[e.key!];
  520. if (score > 8) {
  521. if (is8) {
  522. is8 = false;
  523. }
  524. if (score > 10) {
  525. is10 = false;
  526. }
  527. }
  528. if (!is10) {
  529. break;
  530. }
  531. }
  532. }
  533. if (is8) {
  534. return '是';
  535. } else if (is10) {
  536. return '基本是';
  537. }
  538. }
  539. return '否';
  540. } else {
  541. int score = formValue[currentForm.key!] ?? 0;
  542. if (score >= 11) {
  543. return '是';
  544. } else if (9 == score && score == 10) {
  545. return '倾向是';
  546. } else {
  547. return '否';
  548. }
  549. }
  550. }
  551. /// 数字输入框组件
  552. Widget _buildNumberInput(FormObject currentFormObject) {
  553. String currentInputValue = formValue[currentFormObject.key!] ?? '';
  554. if ((formValue['Height']?.isNotEmpty ?? false) &&
  555. (formValue['Weight']?.isNotEmpty ?? false)) {
  556. formValue['Bmi'] = (double.parse(formValue['Weight']) /
  557. ((double.parse(formValue['Height']) / 100) *
  558. (double.parse(formValue['Height']) / 100)))
  559. .toStringAsFixed(2);
  560. }
  561. Future<void> commonInput() async {
  562. String? result = await VDialogNumber(
  563. title: currentFormObject.label,
  564. initialValue: formValue[currentFormObject.key],
  565. ).show();
  566. if (result?.isNotEmpty ?? false) {
  567. formValue[currentFormObject.key!] = result;
  568. currentInputValue = formValue[currentFormObject.key!];
  569. setState(() {});
  570. }
  571. }
  572. void specialInput(String value) {
  573. formValue[currentFormObject.key!] = value;
  574. currentInputValue = formValue[currentFormObject.key!];
  575. setState(() {});
  576. }
  577. return ExamNumberInput(
  578. currentInputValue: currentInputValue,
  579. commonInput: commonInput,
  580. specialInput: specialInput,
  581. currentFormObject: currentFormObject,
  582. );
  583. }
  584. Widget _buildInput(FormObject currentFormObject) {
  585. String currentInputValue = formValue[currentFormObject.key!] ?? '';
  586. Future<void> commonInput() async {
  587. String? result = await VDialogInput(
  588. title: currentFormObject.label,
  589. initialValue: formValue[currentFormObject.key],
  590. ).show();
  591. if (result?.isNotEmpty ?? false) {
  592. formValue[currentFormObject.key!] = result;
  593. currentInputValue = formValue[currentFormObject.key!];
  594. setState(() {});
  595. }
  596. }
  597. return ExamInput(
  598. currentInputValue: currentInputValue,
  599. commonInput: commonInput,
  600. currentFormObject: currentFormObject,
  601. );
  602. }
  603. /// 血压组件
  604. Widget _buildBloodPressure(FormObject currentFormObject) {
  605. return const ExamBloodPressure();
  606. }
  607. /// 单选框组件
  608. Widget _buildRadio(FormObject currentFormObject) {
  609. List<Option> options = currentFormObject.options ?? [];
  610. String currentSelected = formValue[currentFormObject.key!] ?? "";
  611. void selectRaidoChange(Option e) {
  612. currentSelected = e.value ?? '';
  613. formValue[currentFormObject.key!] = currentSelected;
  614. if (widget.cardKey == 'LNRZYYJKGLFWJL') {
  615. formValue[currentFormObject.parentKey!] =
  616. calculatePhysicalFitnessScore(currentFormObject.parentKey!);
  617. }
  618. setState(() {});
  619. }
  620. return ExamRadio(
  621. options: options,
  622. currentFormObject: currentFormObject,
  623. selectRaidoChange: selectRaidoChange,
  624. currentSelected: currentSelected,
  625. );
  626. }
  627. int calculatePhysicalFitnessScore(String currentFormObjectParentKey) {
  628. int score = 0;
  629. var currentTemplateParent = currentTemplate
  630. .firstWhere((element) => element.key == currentFormObjectParentKey);
  631. if (currentFormObjectParentKey != 'Ping_Score') {
  632. for (var element in currentTemplateParent.children!) {
  633. if (formValue[element.key!] != null) {
  634. score += int.parse(formValue[element.key!]!);
  635. }
  636. }
  637. } else {
  638. int index = 0;
  639. for (var element in currentTemplateParent.children!) {
  640. if (index == 0) {
  641. if (formValue[element.key!] != null) {
  642. score += int.parse(formValue[element.key!]!);
  643. }
  644. } else {
  645. score += formValue[element.key!] != null
  646. ? (6 - int.parse(formValue[element.key!]!))
  647. : 0;
  648. }
  649. index++;
  650. }
  651. }
  652. return score;
  653. }
  654. Widget _buildRadioScore(FormObject currentFormObject) {
  655. print(currentFormObject.toJson());
  656. List<Option> options = currentFormObject.options ?? [];
  657. String currentSelected =
  658. formValue[currentFormObject.childrenKey!.first] ?? "";
  659. void selectRaidoChange(Option e) {
  660. currentSelected = e.value ?? '';
  661. formValue[currentFormObject.childrenKey!.first] = currentSelected;
  662. setState(() {});
  663. }
  664. return ExamRadioScore(
  665. options: options,
  666. currentFormObject: currentFormObject,
  667. selectRaidoChange: selectRaidoChange,
  668. currentSelected: currentSelected,
  669. );
  670. }
  671. /// 体温组件
  672. Widget _buildBodyTemperature(FormObject currentFormObject) {
  673. String currentInputValue = formValue[currentFormObject.key!] ?? '';
  674. void bodyTemperatureInput(String value) {
  675. formValue[currentFormObject.key!] = value;
  676. currentInputValue = formValue[currentFormObject.key!];
  677. setState(() {});
  678. }
  679. return ExamBodyTemperature(
  680. currentInputValue: currentInputValue,
  681. bodyTemperatureInput: bodyTemperatureInput,
  682. currentFormObject: currentFormObject,
  683. );
  684. }
  685. /// 血氧
  686. Widget _buildBloodOxygen(FormObject currentFormObject) {
  687. Map<String, dynamic> currentValue = formValue;
  688. void bloodOxygenInput(Map<String, dynamic> bloodOxygen) {
  689. formValue['Pulse_Frequency'] = bloodOxygen['Pulse_Frequency'];
  690. formValue['Spo2'] = bloodOxygen['Spo2'];
  691. currentValue = bloodOxygen;
  692. setState(() {});
  693. }
  694. return ExamBloodOxygen(
  695. currentValue: currentValue,
  696. bloodOxygenInput: bloodOxygenInput,
  697. );
  698. }
  699. /// 体重
  700. Widget _buildBodyWeight(FormObject currentFormObject) {
  701. String currentInputValue = formValue[currentFormObject.key!] ?? '';
  702. void bodyWeightInput(String value) {
  703. formValue[currentFormObject.key!] = value;
  704. currentInputValue = formValue[currentFormObject.key!];
  705. setState(() {});
  706. }
  707. return ExamBodyWeight(
  708. currentInputValue: currentInputValue,
  709. bodyWeightInput: bodyWeightInput,
  710. currentFormObject: currentFormObject,
  711. );
  712. }
  713. /// 血糖
  714. Widget _buildBodySugar(FormObject currentFormObject) {
  715. String currentInputValue = formValue[currentFormObject.key!] ?? '';
  716. void bloodSugarInput(String value) {
  717. formValue[currentFormObject.key!] = value;
  718. currentInputValue = formValue[currentFormObject.key!];
  719. setState(() {});
  720. }
  721. return ExamBloodSugar(
  722. currentInputValue: currentInputValue,
  723. bloodSugarInput: bloodSugarInput,
  724. currentFormObject: currentFormObject,
  725. );
  726. }
  727. }
  728. class TitleClipRect extends StatelessWidget {
  729. const TitleClipRect({
  730. super.key,
  731. this.color = Colors.grey,
  732. required this.title,
  733. required this.arrowHeight,
  734. required this.clickTitle,
  735. });
  736. final Color? color;
  737. final String title;
  738. final double arrowHeight;
  739. final Function clickTitle;
  740. @override
  741. Widget build(BuildContext context) {
  742. return InkWell(
  743. onTap: () {
  744. clickTitle.call();
  745. },
  746. customBorder: HoleShapeBorder(arrowHeight),
  747. child: Card(
  748. margin: const EdgeInsets.all(0),
  749. shape: HoleShapeBorder(arrowHeight),
  750. color: color,
  751. elevation: color == null ? 5 : 0,
  752. shadowColor: Theme.of(context).primaryColor.withOpacity(0.8),
  753. child: ClipPath(
  754. clipper: _TitleClipPath(arrowHeight),
  755. child: Container(
  756. width: _width,
  757. height: _height,
  758. padding: const EdgeInsets.symmetric(
  759. horizontal: 15,
  760. ),
  761. decoration: BoxDecoration(
  762. boxShadow: [
  763. BoxShadow(
  764. color: Theme.of(context).primaryColor.withOpacity(1),
  765. ),
  766. ],
  767. color: color,
  768. ),
  769. alignment: Alignment.center,
  770. child: FittedBox(
  771. child: Text(
  772. title,
  773. style: const TextStyle(color: Colors.white, fontSize: 20),
  774. ),
  775. ),
  776. ),
  777. ),
  778. ),
  779. );
  780. }
  781. }
  782. class _TitleClipPath extends CustomClipper<Path> {
  783. final double arrowHeight;
  784. _TitleClipPath(this.arrowHeight);
  785. @override
  786. Path getClip(Size size) {
  787. final height = size.height;
  788. final arrowBase = height / 2;
  789. // final arrowPLine = math.tan(120 / 180) * arrowBase;
  790. final path = Path();
  791. path.moveTo(0, 0); // 左上角
  792. path.lineTo(size.width - arrowHeight, 0); // 右上角
  793. path.lineTo(size.width, arrowBase); // 右端点
  794. path.lineTo(size.width - arrowHeight, height); // 右下角
  795. path.lineTo(0, height); // 左下角
  796. path.lineTo(arrowHeight, arrowBase); // 左端点
  797. path.lineTo(0, 0); // 左上角
  798. return path;
  799. }
  800. @override
  801. bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
  802. return false;
  803. }
  804. }
  805. class HoleShapeBorder extends ShapeBorder {
  806. final Offset offset;
  807. final double size;
  808. final double arrowHeight;
  809. const HoleShapeBorder(this.arrowHeight,
  810. {this.offset = const Offset(0, 0), this.size = 0});
  811. @override
  812. EdgeInsetsGeometry get dimensions => throw UnimplementedError();
  813. @override
  814. void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
  815. @override
  816. Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
  817. var path = Path();
  818. final height = rect.size.height;
  819. final arrowBase = height / 2;
  820. path.moveTo(0, 0); // 左上角
  821. path.lineTo(rect.size.width - arrowHeight, 0); // 右上角
  822. path.lineTo(rect.size.width, arrowBase); // 右端点
  823. path.lineTo(rect.size.width - arrowHeight, height); // 右下角
  824. path.lineTo(0, height); // 左下角
  825. path.lineTo(arrowHeight, arrowBase); // 左端点
  826. path.lineTo(0, 0); // 左上角
  827. return path;
  828. }
  829. @override
  830. ShapeBorder scale(double t) {
  831. // TODO: implement scale
  832. throw UnimplementedError();
  833. }
  834. @override
  835. Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
  836. // TODO: implement getInnerPath
  837. throw UnimplementedError();
  838. }
  839. }
  840. // ClipPath(
  841. // clipper: TriangleClipper(),
  842. // child: Container(
  843. // width: 50,
  844. // height: 50,
  845. // padding:
  846. // const EdgeInsets.all(
  847. // 2),
  848. // alignment:
  849. // Alignment.topRight,
  850. // decoration:
  851. // const BoxDecoration(
  852. // color: Colors.blue,
  853. // borderRadius:
  854. // BorderRadius.only(
  855. // topRight:
  856. // Radius.circular(
  857. // 8,
  858. // ),
  859. // ),
  860. // ),
  861. // child: const Icon(
  862. // Icons.check,
  863. // color: Colors.white,
  864. // ),
  865. // ))
  866. // class TriangleClipper extends CustomClipper<Path> {
  867. // @override
  868. // Path getClip(Size size) {
  869. // final path = Path()
  870. // ..moveTo(size.width, 0)
  871. // ..lineTo(0, 0)
  872. // ..lineTo(size.width, size.height)
  873. // ..close();
  874. // return path;
  875. // }
  876. // @override
  877. // bool shouldReclip(CustomClipper<Path> oldClipper) => false;
  878. // }