tcm_card.dart 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102
  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:vitalapp/architecture/storage/text_storage.dart';
  6. import 'package:vitalapp/architecture/utils/prompt_box.dart';
  7. import 'package:vitalapp/components/alert_dialog.dart';
  8. import 'package:vitalapp/components/appbar.dart';
  9. import 'package:vitalapp/components/button.dart';
  10. import 'package:vitalapp/components/dialog_input.dart';
  11. import 'package:vitalapp/components/dynamic_drawer.dart';
  12. import 'package:vitalapp/consts/styles.dart';
  13. import 'package:vitalapp/global.dart';
  14. import 'package:vitalapp/managers/interfaces/cachedRecord.dart';
  15. import 'package:vitalapp/managers/interfaces/template.dart';
  16. import 'package:vitalapp/pages/check/models/form.dart';
  17. import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_health_guidance_check_box.dart';
  18. import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_input.dart';
  19. import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_radio.dart';
  20. import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_radio_score.dart';
  21. import 'package:vitalapp/store/store.dart';
  22. class TCMConstitutionModule extends StatefulWidget {
  23. final String cardKey;
  24. final Future<bool> Function(String, String, dynamic) callBack;
  25. final String? patientCode;
  26. final String? examData;
  27. final bool? isEdit;
  28. const TCMConstitutionModule({
  29. super.key,
  30. required this.cardKey,
  31. required this.callBack,
  32. this.patientCode,
  33. this.examData,
  34. this.isEdit = false,
  35. });
  36. @override
  37. State<TCMConstitutionModule> createState() => _ConfigurableFormState();
  38. }
  39. class _ConfigurableFormState extends State<TCMConstitutionModule> {
  40. /// 当前最新的模板的键值对
  41. Map<String, dynamic> templateRelation = {};
  42. bool isFirstEnter = true;
  43. /// 当前模板数据
  44. List<FormObject> currentTemplate = [];
  45. /// 当前title的下标
  46. int currentTitleIndex = 0;
  47. Map<String, dynamic> formValue = {};
  48. var scaffoldKey = GlobalKey<ScaffoldState>();
  49. List<TCMData> resourceInfo = [];
  50. final _templateManager = Get.find<ITemplateManager>();
  51. final _cachedRecordManager = Get.find<ICachedRecordManager>();
  52. Map currentTable = {};
  53. late MapEntry<String, dynamic> currentResult;
  54. late List<MapEntry<String, dynamic>> compatibleResult =
  55. <MapEntry<String, dynamic>>[];
  56. late List<String> unansweredQuestions = <String>[];
  57. List<String> storeTypeList = [
  58. 'Qi_Score',
  59. 'Yang_Score',
  60. 'Yin_Score',
  61. 'Tan_Score',
  62. 'Shi_Score',
  63. 'Xue_Score',
  64. 'Qiyu_Score',
  65. 'Te_Score',
  66. 'Ping_Score'
  67. ];
  68. Map<String, dynamic> storeTypeMap = {
  69. "Qi_Score": '气虚质',
  70. "Yang_Score": '阳虚质',
  71. "Yin_Score": '阴虚质',
  72. "Tan_Score": '痰湿质',
  73. "Shi_Score": '湿热质',
  74. "Xue_Score": '血瘀质',
  75. "Qiyu_Score": '气郁质',
  76. "Te_Score": '特禀质',
  77. "Ping_Score": '平和质',
  78. };
  79. Map<String, String> typeMap = {
  80. 'Peaceful_Quality': "Ping_Score",
  81. "Yang_Deficiency_Substance": "Yang_Score",
  82. "Idiosyncratic_Quality": "Te_Score",
  83. "Qi_Stagnation_Constitution": "Qiyu_Score",
  84. "Blood_Stasis_Substance": "Xue_Score",
  85. "Damp-heat_constitution": "Shi_Score",
  86. "Phlegm-dampness_constitution": "Tan_Score",
  87. "Yin_Deficiency_Substance": "Yin_Score",
  88. "Qi_Deficiency_Constitution": "Qi_Score",
  89. };
  90. String resultConstitution = "";
  91. late int? selectedIndex = -1; // 用于跟踪选中的项目索引
  92. @override
  93. void initState() {
  94. selectedIndex = -1;
  95. super.initState();
  96. initReadCached();
  97. getResourceInfo();
  98. unansweredQuestions.clear();
  99. // compatibleResult?.clear();
  100. WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
  101. if (mounted) {
  102. initTemplate();
  103. }
  104. });
  105. }
  106. Future<void> initReadCached() async {
  107. TextStorage cachedRecord = TextStorage(
  108. fileName: 'ZYTZ',
  109. directory: "patient/${widget.patientCode}",
  110. );
  111. String? value = await cachedRecord.read();
  112. if (value == null) {
  113. formValue = {};
  114. Store.resident.handleSaveMedicalData(jsonEncode(formValue));
  115. return;
  116. }
  117. Store.resident.handleSaveMedicalData(value);
  118. formValue = jsonDecode(value);
  119. }
  120. Future<bool?> saveCachedRecord() async {
  121. // deleteDirectory();
  122. Store.resident.handleSaveMedicalData(jsonEncode(formValue));
  123. TextStorage cachedRecord = TextStorage(
  124. fileName: 'ZYTZ',
  125. directory: "patient/${widget.patientCode}",
  126. );
  127. return cachedRecord.save(jsonEncode(formValue));
  128. }
  129. Future<bool?> deleteDirectory() async {
  130. TextStorage cachedRecord = TextStorage(
  131. fileName: 'ZYTZ',
  132. directory: "patient/${widget.patientCode}",
  133. );
  134. return cachedRecord.deleteDirectory();
  135. }
  136. Future<void> getResourceInfo() async {
  137. TextStorage resourceInfoCacheRecord = TextStorage(
  138. fileName: "healthGuidance",
  139. directory: "HealthGuidance/temlpate",
  140. );
  141. String? value = await resourceInfoCacheRecord.read();
  142. if (value != null) {
  143. var json = jsonDecode(value);
  144. resourceInfo =
  145. List<TCMData>.from(json['TCMDesc'].map((x) => TCMData.fromJson(x)));
  146. }
  147. }
  148. @override
  149. Widget build(BuildContext context) {
  150. return Scaffold(
  151. key: scaffoldKey,
  152. appBar: VAppBar(
  153. titleText: "中医体质",
  154. ),
  155. resizeToAvoidBottomInset: false,
  156. body: (!widget.isEdit!) && isFirstEnter
  157. ? Container(
  158. decoration: const BoxDecoration(
  159. image: DecorationImage(
  160. image:
  161. AssetImage("assets/images/healthGuidanceBackground.png"),
  162. fit: BoxFit.cover,
  163. ),
  164. ),
  165. child: Column(
  166. children: [
  167. const SizedBox(
  168. height: 120,
  169. ),
  170. Center(
  171. child: Container(
  172. decoration: BoxDecoration(
  173. border: Border.all(color: Colors.black, width: 1)),
  174. padding: const EdgeInsets.all(10), // 添加内边距
  175. child: Container(
  176. decoration: BoxDecoration(
  177. border: Border.all(color: Colors.black, width: 1)),
  178. padding: const EdgeInsets.all(10), // 添加内边距
  179. child: const SizedBox(
  180. width: 900,
  181. child: Text(
  182. '《中医体质分类与判定标准》于2009年4月9日,由中华中医药学会正式发布,该标准旨在为体质辨识及中医体质相关疾病的防治、养生保健、健康管理提供依据,使体质分类科学化、规范化,是我国第一部指导和规范中医体质研究及应用的文件。适用于从事中医体质研究的中医临床医生、科研人员及相关管理人员,并可作为临床实践、判定规范及质量评定的重要参考依据。其中将中国人的体质分为九种,是体质分类的一种规范。具体有平和质、阳虚质、阴虚质、气虚质、痰湿质、湿热质、气郁质、血瘀质、特禀质。',
  183. style: TextStyle(fontSize: 24),
  184. ),
  185. ),
  186. ),
  187. ),
  188. ),
  189. const SizedBox(
  190. height: 120,
  191. ),
  192. VButton(
  193. label: "开始检测",
  194. onTap: () {
  195. setState(() {
  196. isFirstEnter = false;
  197. });
  198. },
  199. ),
  200. ],
  201. ),
  202. )
  203. : Column(
  204. mainAxisAlignment: MainAxisAlignment.start,
  205. crossAxisAlignment: CrossAxisAlignment.start,
  206. children: [
  207. const SizedBox(
  208. height: 20,
  209. ),
  210. if (currentTitleIndex == 1)
  211. Column(
  212. mainAxisAlignment: MainAxisAlignment.start,
  213. crossAxisAlignment: CrossAxisAlignment.start,
  214. children: [
  215. Row(
  216. children: [
  217. const SizedBox(
  218. width: 80,
  219. ),
  220. Text(
  221. '当前体质判定为:${storeTypeMap[currentResult.key]} (${jsonDecode(currentResult.value)["Result"]} ${jsonDecode(currentResult.value)["Value"]}分)',
  222. style: const TextStyle(
  223. fontSize: 28,
  224. color: Colors.black,
  225. ),
  226. )
  227. ],
  228. ),
  229. if (compatibleResult.isNotEmpty)
  230. Row(
  231. children: const [
  232. SizedBox(
  233. width: 80,
  234. ),
  235. Text(
  236. '兼夹体质有:',
  237. style: TextStyle(
  238. fontSize: 25,
  239. color: Colors.black,
  240. ),
  241. ),
  242. ],
  243. ),
  244. if (compatibleResult.isNotEmpty)
  245. ListView.builder(
  246. shrinkWrap: true,
  247. itemCount: (compatibleResult.length / 3).ceil(),
  248. itemBuilder: (context, index) {
  249. return Row(
  250. children: [
  251. const SizedBox(width: 120),
  252. for (int i = index * 5;
  253. i < (index + 1) * 5;
  254. i++)
  255. if (i < compatibleResult.length)
  256. Column(
  257. crossAxisAlignment:
  258. CrossAxisAlignment.start,
  259. children: [
  260. Text(
  261. '${storeTypeMap[compatibleResult[i].key]} (${jsonDecode(compatibleResult[i].value)["Result"]} ${jsonDecode(compatibleResult[i].value)["Value"]}分) ',
  262. style: const TextStyle(
  263. fontSize: 20,
  264. color: Colors.black,
  265. ),
  266. softWrap: true,
  267. ),
  268. ],
  269. ),
  270. if ((index + 1) * 3 < compatibleResult.length)
  271. Container(height: 20), // 控制换行间距
  272. ],
  273. );
  274. },
  275. ),
  276. ],
  277. ),
  278. Expanded(
  279. child: Row(
  280. mainAxisAlignment: MainAxisAlignment.start,
  281. crossAxisAlignment: CrossAxisAlignment.start,
  282. children: [
  283. // SizedBox(width: widget.isEdit! ? 90 : 50),
  284. _buildContent(),
  285. // SizedBox(width: widget.isEdit! ? 90 : 50),
  286. ],
  287. ),
  288. ),
  289. Row(
  290. mainAxisAlignment: MainAxisAlignment.center,
  291. crossAxisAlignment: CrossAxisAlignment.center,
  292. children: [
  293. if (currentTitleIndex == 0)
  294. VButton(
  295. onTap: _buildCompute,
  296. child: Row(
  297. mainAxisAlignment: MainAxisAlignment.center,
  298. children: [
  299. Text(
  300. "计算体质辨识结果",
  301. style: const TextStyle(
  302. fontSize: 20,
  303. ),
  304. ),
  305. ],
  306. ),
  307. ),
  308. const SizedBox(
  309. width: 10,
  310. ),
  311. if (currentTitleIndex == 1)
  312. VButton(
  313. child: Row(
  314. mainAxisAlignment: MainAxisAlignment.center,
  315. children: [
  316. Text(widget.isEdit! ? "修改" : "返回",
  317. style: TextStyle(fontSize: 20)),
  318. ],
  319. ),
  320. onTap: () {
  321. setState(() {
  322. // if (!widget.isEdit!) formValue.clear();
  323. currentTitleIndex = 0;
  324. });
  325. },
  326. ),
  327. const SizedBox(
  328. width: 10,
  329. ),
  330. if (currentTitleIndex == 1)
  331. VButton(
  332. child: Row(
  333. mainAxisAlignment: MainAxisAlignment.center,
  334. children: [
  335. Text("提交", style: const TextStyle(fontSize: 20)),
  336. ],
  337. ),
  338. onTap: () async {
  339. final result = await widget.callBack(
  340. widget.cardKey,
  341. templateRelation[widget.cardKey]!,
  342. jsonEncode(formValue),
  343. );
  344. if (result) {
  345. setState(() {
  346. if (!widget.isEdit!) formValue.clear();
  347. currentTitleIndex = 0;
  348. deleteDirectory();
  349. });
  350. }
  351. Get.back();
  352. },
  353. ),
  354. ],
  355. ),
  356. const SizedBox(
  357. height: 10,
  358. ),
  359. ],
  360. ),
  361. );
  362. }
  363. String getHealthGuidance(String key) {
  364. String? value =
  365. resourceInfo.firstWhere((element) => element.key == key).value;
  366. return value;
  367. }
  368. /// 主页面
  369. Widget _buildContent() {
  370. return Expanded(
  371. child: flowCardList(),
  372. );
  373. }
  374. @override
  375. void dispose() {
  376. super.dispose();
  377. }
  378. Future<void> initTemplate() async {
  379. Store.app.busy = true;
  380. await fetchTemplateIndex();
  381. await fetchTemplate(widget.cardKey);
  382. await fetchTemplateData();
  383. Store.app.busy = false;
  384. }
  385. /// 读取体检的缓存
  386. Future<String?> readCachedCheck() async {
  387. if (widget.patientCode == null) {
  388. return null;
  389. }
  390. String? value = await _cachedRecordManager.readCachedRecord(
  391. widget.cardKey,
  392. widget.patientCode!,
  393. 'FollowUp',
  394. );
  395. return value;
  396. }
  397. Future<void> fetchTemplateIndex() async {
  398. try {
  399. /// 获取模板的键值对
  400. String? templates;
  401. templates =
  402. await _templateManager.readTemplateRelation('templateRelation');
  403. templateRelation = jsonDecode(templates!);
  404. setState(() {});
  405. } catch (error) {
  406. print('发生错误: $error');
  407. }
  408. }
  409. Future<void> fetchTemplateData() async {
  410. if (widget.examData?.isNotEmpty ?? false) {
  411. formValue = jsonDecode(widget.examData!);
  412. var jsonKey = formValue["PhysicalConclusion"];
  413. currentResult = MapEntry(jsonKey, formValue[jsonKey]);
  414. compatibleResult = calculateAllResylts();
  415. if (widget.isEdit!) {
  416. currentTitleIndex = 1;
  417. }
  418. setState(() {});
  419. return;
  420. }
  421. formValue.forEach(
  422. (key, value) {
  423. if (value is List<String>) {
  424. formValue[key] = List<String>.from(formValue[key]);
  425. } else if (value is List<Map>) {
  426. formValue[key] = List<Map>.from(formValue[key]);
  427. }
  428. },
  429. );
  430. // currentResult = formValue[formValue["PhysicalConclusion"].value];
  431. // if (widget.isEdit!) {
  432. // currentTitleIndex = 1;
  433. // }
  434. setState(() {});
  435. }
  436. Future<void> fetchTemplate(String key) async {
  437. try {
  438. if (templateRelation[key] == null) {
  439. currentTemplate = [];
  440. setState(() {});
  441. return;
  442. }
  443. var template =
  444. await _templateManager.readTemplate(templateRelation[key]!);
  445. String templateContent =
  446. TemplateDTO.fromJson(jsonDecode(template!)).templateContent!;
  447. List<Map<String, dynamic>> list =
  448. jsonDecode(templateContent).cast<Map<String, dynamic>>();
  449. for (var i in list) {
  450. if (i['children'] != null) {
  451. List<FormObject> currentChildren = [];
  452. for (var j in i['children']) {
  453. currentChildren.add(FormObject.fromJson(j));
  454. }
  455. i['children'] = currentChildren;
  456. }
  457. var item = FormObject.fromJson(i);
  458. currentTemplate.add(item);
  459. }
  460. // if (widget.cardKey == 'LNRZYYJKGLFWJL') {
  461. // for (var element in storeTypeList) {
  462. // formValue[element] =
  463. // calculatePhysicalFitnessScore(element, element == 'Ping_Score');
  464. // }
  465. // }
  466. setState(() {});
  467. } catch (error) {
  468. print('发生错误: $error');
  469. }
  470. }
  471. Widget buildSingleItem(Widget item, int span) {
  472. return Column(
  473. children: [
  474. FractionallySizedBox(
  475. widthFactor: span == 24 ? 1 : 0.5,
  476. child: item,
  477. ),
  478. ],
  479. );
  480. }
  481. Widget buildWidget(FormObject? currentFormObject) {
  482. Map<String, Widget Function(FormObject)> widgetMap = {
  483. 'input': _buildInput,
  484. 'radio': _buildRadio,
  485. 'radioScore': _buildRadioScore,
  486. 'healthGuidanceCheckBox': _buildHealthGuidanceCheckBox,
  487. };
  488. Widget Function(FormObject) builder =
  489. widgetMap[currentFormObject?.type] ?? _buildInput;
  490. return builder(currentFormObject!);
  491. }
  492. Widget flowCardList() {
  493. int itemCount = 0;
  494. bool currentTemplateOptionsIsNotEmpty = false;
  495. if (currentTemplate.isNotEmpty) {
  496. itemCount = currentTemplate[currentTitleIndex].children?.length ?? 0;
  497. currentTemplateOptionsIsNotEmpty =
  498. currentTemplate[currentTitleIndex].options?.isNotEmpty ?? false;
  499. }
  500. List<Widget> items = List.generate(itemCount, (index) {
  501. FormObject? currentFormObject =
  502. currentTemplate[currentTitleIndex].children?[index];
  503. int span = currentFormObject?.span ?? 12;
  504. //父结构的options不等于空或null
  505. //子结构的options若是无值则取父类的options值
  506. if (currentTemplateOptionsIsNotEmpty) {
  507. if (currentFormObject!.options!.isEmpty)
  508. currentFormObject.options =
  509. currentTemplate[currentTitleIndex].options;
  510. }
  511. return buildSingleItem(buildWidget(currentFormObject), span);
  512. });
  513. final ScrollController scrollController = ScrollController();
  514. return Scrollbar(
  515. controller: scrollController,
  516. // thumbVisibility: false,
  517. // isAlwaysShown: false,
  518. child: ListView(
  519. controller: scrollController,
  520. padding: EdgeInsets.symmetric(horizontal: 50),
  521. children: [
  522. Container(
  523. alignment: Alignment.topCenter,
  524. child: Wrap(
  525. runSpacing: currentTitleIndex == 1 ? 5 : 10, // 纵向元素间距
  526. alignment: WrapAlignment.start,
  527. children: items,
  528. ),
  529. ),
  530. if (currentTitleIndex == 1)
  531. Text(
  532. getHealthGuidance(currentResult.key),
  533. style: TextStyle(fontSize: 18),
  534. ),
  535. ],
  536. ),
  537. );
  538. }
  539. /// title标签
  540. _buildCompute() {
  541. var questionsAnsweredNumber = formValue.entries
  542. .where((entry) => entry.key.contains("qusition"))
  543. .length; //获取已完成答题数量
  544. unansweredQuestions.clear();
  545. if (widget.cardKey == 'LNRZYYJKGLFWJL' &&
  546. currentTitleIndex == 0 &&
  547. questionsAnsweredNumber >= 33) {
  548. List<MapEntry<String, dynamic>> result = caculationConclusion();
  549. String? guidanceResult = hasGuidanceBeenProvided();
  550. String message = '';
  551. String title = '';
  552. bool isExistConflict = false;
  553. if (result.length == 1) {
  554. currentResult = result.first;
  555. formValue["PhysicalConclusion"] = currentResult.key;
  556. //直接进入中医药保健指导
  557. setState(() {
  558. compatibleResult = calculateAllResylts();
  559. currentTitleIndex = 1;
  560. });
  561. } else {
  562. if (result.length > 1) {
  563. //弹出选择框
  564. isExistConflict = result
  565. .where((entry) =>
  566. entry.key == "Yang_Score" || entry.key == "Yin_Score")
  567. .length ==
  568. 2;
  569. if (isExistConflict) {
  570. message = '判定结果既是阴虚又是阳虚的矛盾判定结果,需重新填写问题:10、11、12、13、21、26、29、31';
  571. title = '体质冲突';
  572. var list = [10, 11, 12, 13, 21, 26, 29, 31];
  573. for (var element in list) {
  574. unansweredQuestions.add('qusition$element');
  575. }
  576. } else {
  577. message = '根据答题情况,计算出多个体质,请选择其中一个';
  578. title = '体质选择';
  579. }
  580. }
  581. if (result.isEmpty) {
  582. //无法断体质
  583. message = '请重新完成体质问询或2周后重新采集填写';
  584. title = '无法判断体质';
  585. }
  586. Get.dialog(
  587. VAlertDialog(
  588. title: title,
  589. width: 700,
  590. showCancel: false,
  591. content: Container(
  592. padding: const EdgeInsets.symmetric(horizontal: 24),
  593. height: result.length > 1 && !isExistConflict
  594. ? result.length / 3 * 150
  595. : 150,
  596. alignment: Alignment.center,
  597. child: Column(
  598. children: [
  599. Text(
  600. message,
  601. style: const TextStyle(fontSize: 24),
  602. ),
  603. SizedBox(
  604. height: 10,
  605. ),
  606. if (result.length > 1 && !isExistConflict)
  607. Expanded(
  608. child: SelectResult(
  609. result: result,
  610. guidanceResult: guidanceResult,
  611. selectCheckBoxChange: (value) {
  612. selectedIndex = value;
  613. currentResult = result[value];
  614. formValue["PhysicalConclusion"] = currentResult.key;
  615. },
  616. ),
  617. ),
  618. ],
  619. ),
  620. ),
  621. onConfirm: () {
  622. if (selectedIndex != -1) {
  623. compatibleResult = calculateAllResylts();
  624. currentTitleIndex = 1;
  625. }
  626. Get.back();
  627. setState(() {});
  628. },
  629. ),
  630. barrierDismissible: false,
  631. barrierColor: Colors.black.withOpacity(.4),
  632. );
  633. }
  634. } else {
  635. for (var i = 1; i < 34; i++) {
  636. var isExitsQuestion = formValue.entries
  637. .where((element) => element.key == 'qusition$i')
  638. .isNotEmpty;
  639. if (!isExitsQuestion) {
  640. unansweredQuestions.add('qusition$i');
  641. }
  642. }
  643. setState(() {});
  644. PromptBox.toast('题目未答完整,无法计算体质');
  645. }
  646. }
  647. //验证体质
  648. List<MapEntry<String, dynamic>> caculationConclusion() {
  649. var formValues = formValue.entries
  650. .where((entry) =>
  651. storeTypeList.contains(entry.key) && entry.key != "Ping_Score")
  652. .toList();
  653. var pingResult = jsonDecode(formValue["Ping_Score"])["Result"];
  654. // int score = int.parse(formValue["Ping_Score"]["Value"] ?? 0);
  655. // var pingResult = getJudgmentResult('Ping_Score', score);
  656. if (["是", "基本是", "倾向是"].contains(pingResult)) {
  657. return formValue.entries
  658. .where((element) => element.key == 'Ping_Score')
  659. .toList();
  660. } else {
  661. dynamic maxValue = formValues
  662. .map((entry) => jsonDecode(entry.value)["Value"])
  663. .reduce((a, b) =>
  664. int.parse(a.toString()) > int.parse(b.toString()) ? a : b);
  665. if (int.parse(maxValue.toString()) > 8) {
  666. List<MapEntry<String, dynamic>> keyValuePairs = formValues
  667. .where((entry) => jsonDecode(entry.value)["Value"] == maxValue)
  668. .toList();
  669. return keyValuePairs;
  670. } else {
  671. return List.empty();
  672. }
  673. }
  674. }
  675. String? hasGuidanceBeenProvided() {
  676. var formValues = formValue.entries
  677. .where((entry) => entry.key == 'Peaceful_Quality')
  678. .toList();
  679. if (formValues.isNotEmpty) {
  680. return typeMap["Peaceful_Quality"];
  681. }
  682. formValues = formValue.entries
  683. .where((entry) => entry.key == "Yang_Deficiency_Substance")
  684. .toList();
  685. if (formValues.isNotEmpty) {
  686. return typeMap["Yang_Deficiency_Substance"];
  687. }
  688. formValues = formValue.entries
  689. .where((entry) => entry.key == "Idiosyncratic_Quality")
  690. .toList();
  691. if (formValues.isNotEmpty) {
  692. return typeMap["Idiosyncratic_Quality"];
  693. }
  694. formValues = formValue.entries
  695. .where((entry) => entry.key == "Qi_Stagnation_Constitution")
  696. .toList();
  697. if (formValues.isNotEmpty) {
  698. return typeMap["Qi_Stagnation_Constitution"];
  699. }
  700. formValues = formValue.entries
  701. .where((entry) => entry.key == "Blood_Stasis_Substance")
  702. .toList();
  703. if (formValues.isNotEmpty) {
  704. return typeMap["Blood_Stasis_Substance"];
  705. }
  706. formValues = formValue.entries
  707. .where((entry) => entry.key == "Damp-heat_constitution")
  708. .toList();
  709. if (formValues.isNotEmpty) {
  710. return typeMap["Damp-heat_constitution"];
  711. }
  712. formValues = formValue.entries
  713. .where((entry) => entry.key == "Phlegm-dampness_constitution")
  714. .toList();
  715. if (formValues.isNotEmpty) {
  716. return typeMap["Phlegm-dampness_constitution"];
  717. }
  718. formValues = formValue.entries
  719. .where((entry) => entry.key == "Yin_Deficiency_Substance")
  720. .toList();
  721. if (formValues.isNotEmpty) {
  722. return typeMap["Yin_Deficiency_Substance"];
  723. }
  724. formValues = formValue.entries
  725. .where((entry) => entry.key == "Qi_Deficiency_Constitution")
  726. .toList();
  727. if (formValues.isNotEmpty) {
  728. return typeMap["Qi_Deficiency_Constitution"];
  729. }
  730. return "";
  731. }
  732. Widget _buildInput(FormObject currentFormObject) {
  733. String currentInputValue = formValue[currentFormObject.key!] ?? '';
  734. Future<void> commonInput() async {
  735. String? result = await VDialogInput(
  736. title: currentFormObject.label,
  737. initialValue: formValue[currentFormObject.key],
  738. ).show();
  739. if (result?.isNotEmpty ?? false) {
  740. formValue[currentFormObject.key!] = result;
  741. currentInputValue = formValue[currentFormObject.key!];
  742. setState(() {});
  743. }
  744. }
  745. return ExamInput(
  746. currentInputValue: currentInputValue,
  747. commonInput: commonInput,
  748. currentFormObject: currentFormObject,
  749. );
  750. }
  751. ///计算满足的所有体质类型
  752. List<MapEntry<String, dynamic>> calculateAllResylts() {
  753. List<MapEntry<String, dynamic>> compatibleResults =
  754. <MapEntry<String, dynamic>>[];
  755. for (var key in storeTypeList) {
  756. var result = jsonDecode(formValue[key])["Result"];
  757. if (["是", "基本是", "倾向是"].contains(result)) {
  758. compatibleResults.add(MapEntry(key, formValue[key]));
  759. }
  760. }
  761. compatibleResults = compatibleResults
  762. .where((element) => element.key != currentResult.key)
  763. .toList();
  764. return compatibleResults;
  765. }
  766. ///中医药健康指导多选框组件
  767. Widget _buildHealthGuidanceCheckBox(FormObject currentFormObject) {
  768. List<Option> options = currentFormObject.options ?? [];
  769. List<dynamic> currentSelectedCheckBox = formValue["HealthGuidance"] ?? [];
  770. var currentScoreKey = currentFormObject.groupKeys != null
  771. ? currentFormObject.groupKeys![0]
  772. : '';
  773. if (currentResult.key != currentScoreKey) {
  774. return Container();
  775. }
  776. int score = jsonDecode(formValue[currentScoreKey])["Value"];
  777. String judgmentResult = jsonDecode(formValue[currentScoreKey])["Result"];
  778. void selectCheckBoxChange(Option e) {
  779. if (currentSelectedCheckBox.contains(e.value)) {
  780. currentSelectedCheckBox.remove(e.value);
  781. } else {
  782. currentSelectedCheckBox.add(e.value ?? '');
  783. }
  784. formValue["HealthGuidance"] = currentSelectedCheckBox;
  785. saveCachedRecord();
  786. setState(() {});
  787. }
  788. return ExamHealthGuidanceWidget(
  789. options: options,
  790. currentSelectedCheckBox: currentSelectedCheckBox,
  791. currentFormObject: currentFormObject,
  792. selectCheckBoxChange: selectCheckBoxChange,
  793. score: score,
  794. judgmentResult: judgmentResult,
  795. );
  796. }
  797. ///识别体质
  798. String getJudgmentResult(String storeKey, int score) {
  799. if (storeKey == 'Ping_Score') {
  800. if (score >= 17) {
  801. bool is8 = true;
  802. bool is10 = true;
  803. for (var e in storeTypeList) {
  804. if (formValue[e] != null && e != 'Ping_Score') {
  805. var otherScore = formValue[e];
  806. if (otherScore > 8) {
  807. if (is8) {
  808. is8 = false;
  809. }
  810. if (otherScore > 10) {
  811. is10 = false;
  812. }
  813. }
  814. if (!is10) {
  815. break;
  816. }
  817. }
  818. }
  819. if (is8) {
  820. return '是';
  821. } else if (is10) {
  822. return '基本是';
  823. }
  824. }
  825. return '否';
  826. } else {
  827. if (score >= 11) {
  828. return '是';
  829. } else if (9 == score || score == 10) {
  830. return '倾向是';
  831. } else {
  832. return '否';
  833. }
  834. }
  835. }
  836. /// 单选框组件
  837. Widget _buildRadio(FormObject currentFormObject) {
  838. List<Option> options = currentFormObject.options ?? [];
  839. String currentSelected = formValue[currentFormObject.key!] ?? "";
  840. void selectRaidoChange(Option e) {
  841. currentSelected = e.value ?? '';
  842. setState(() {
  843. optionUpdate(currentFormObject, currentSelected);
  844. });
  845. }
  846. var decoration = unansweredQuestions.contains(currentFormObject.key!)
  847. ? BoxDecoration(
  848. border: Border.all(color: Colors.red, width: 2),
  849. borderRadius: GlobalStyles.borderRadius,
  850. )
  851. : null;
  852. return Container(
  853. decoration: decoration,
  854. child: ExamRadio(
  855. options: options,
  856. currentFormObject: currentFormObject,
  857. selectRaidoChange: selectRaidoChange,
  858. currentSelected: currentSelected,
  859. ),
  860. );
  861. }
  862. Future<void> optionUpdate(
  863. FormObject formObject, String currentSelected) async {
  864. formValue[formObject.key!] = currentSelected;
  865. if (widget.cardKey == 'LNRZYYJKGLFWJL') {
  866. var pingScore = [
  867. 'qusition1',
  868. 'qusition2',
  869. 'qusition4',
  870. 'qusition5',
  871. 'qusition13'
  872. ];
  873. var isPingScore = pingScore.contains(formObject.key);
  874. int store =
  875. calculatePhysicalFitnessScore(formObject.groupKeys?[0], false);
  876. formValue[formObject.groupKeys?[0]] = jsonEncode({
  877. "Value": store,
  878. "Result": getJudgmentResult(formObject.groupKeys?[0], store),
  879. });
  880. if (isPingScore) {
  881. int store = calculatePhysicalFitnessScore('Ping_Score', true);
  882. formValue['Ping_Score'] = jsonEncode({
  883. "Value": store,
  884. "Result": getJudgmentResult('Ping_Score', store),
  885. });
  886. }
  887. }
  888. saveCachedRecord();
  889. }
  890. ///计算得分 currentScoreKey 当前分数的key isPingScore 是否是平和质
  891. int calculatePhysicalFitnessScore(String currentScoreKey, bool isPingScore) {
  892. int score = 0;
  893. var currentTemplateStoreList = currentTemplate[0].children?.where(
  894. (element) =>
  895. element.groupKeys != null &&
  896. element.groupKeys!.contains(currentScoreKey));
  897. if (!isPingScore) {
  898. for (var element in currentTemplateStoreList!) {
  899. if (formValue[element.key!] != null) {
  900. score += int.parse(formValue[element.key!]!);
  901. }
  902. }
  903. } else {
  904. int index = 0;
  905. for (var element in currentTemplateStoreList!) {
  906. if (index == 0) {
  907. if (formValue[element.key!] != null) {
  908. score += int.parse(formValue[element.key!]!);
  909. }
  910. } else {
  911. score += formValue[element.key!] != null
  912. ? (6 - int.parse(formValue[element.key!]!))
  913. : 0;
  914. }
  915. index++;
  916. }
  917. }
  918. return score;
  919. }
  920. Widget _buildRadioScore(FormObject currentFormObject) {
  921. print(currentFormObject.toJson());
  922. List<Option> options = currentFormObject.options ?? [];
  923. String currentSelected =
  924. formValue[currentFormObject.childrenKey!.first] ?? "";
  925. String currentScore = formValue[currentFormObject.childrenKey!.last] ?? "";
  926. void selectRaidoChange(Option e) {
  927. currentSelected = e.value ?? '';
  928. formValue[currentFormObject.childrenKey!.first] = currentSelected;
  929. setState(() {});
  930. }
  931. void changeScore(String? score) {
  932. currentScore = score ?? '';
  933. formValue[currentFormObject.childrenKey!.last] = currentScore;
  934. setState(() {});
  935. }
  936. return ExamRadioScore(
  937. options: options,
  938. currentFormObject: currentFormObject,
  939. selectRaidoChange: selectRaidoChange,
  940. currentSelected: currentSelected,
  941. changeScore: changeScore,
  942. currentScore: currentScore,
  943. );
  944. }
  945. }
  946. ///多个体质时选项
  947. class SelectResult extends StatefulWidget {
  948. final List<MapEntry<String, dynamic>> result;
  949. final Function selectCheckBoxChange;
  950. final String? guidanceResult;
  951. const SelectResult(
  952. {super.key,
  953. required this.result,
  954. required this.selectCheckBoxChange,
  955. this.guidanceResult});
  956. @override
  957. State<SelectResult> createState() => _SelectResultState();
  958. }
  959. class _SelectResultState extends State<SelectResult> {
  960. Map<String, dynamic> storeTypeMap = {
  961. "Qi_Score": '气虚质',
  962. "Yang_Score": '阳虚质',
  963. "Yin_Score": '阴虚质',
  964. "Tan_Score": '痰虚质',
  965. "Shi_Score": '湿热质',
  966. "Xue_Score": '血瘀质',
  967. "Qiyu_Score": '气郁质',
  968. "Te_Score": '特禀质',
  969. "Ping_Score": '平和质',
  970. };
  971. late int? selectedIndex = -1; // 用于跟踪选中的项目索引
  972. @override
  973. void initState() {
  974. super.initState();
  975. selectedIndex = -1;
  976. }
  977. @override
  978. Widget build(BuildContext context) {
  979. return GridView.builder(
  980. gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
  981. crossAxisCount: 3,
  982. crossAxisSpacing: 10,
  983. mainAxisSpacing: 10,
  984. childAspectRatio: 3,
  985. ),
  986. itemCount: widget.result.length,
  987. itemBuilder: (context, index) {
  988. if (widget.result[index].key == widget.guidanceResult) {
  989. widget.selectCheckBoxChange(index);
  990. selectedIndex = index;
  991. }
  992. return Row(
  993. children: [
  994. Radio(
  995. value: index,
  996. groupValue: selectedIndex,
  997. onChanged: (value) {
  998. widget.selectCheckBoxChange(value);
  999. setState(() {
  1000. selectedIndex = value;
  1001. });
  1002. }),
  1003. Text(
  1004. "${storeTypeMap[widget.result[index].key]} (${jsonDecode(widget.result[index].value)["Result"]} ${jsonDecode(widget.result[index].value)["Value"]}分)",
  1005. style: TextStyle(fontSize: 22),
  1006. ),
  1007. SizedBox(
  1008. width: 5,
  1009. ),
  1010. ],
  1011. );
  1012. });
  1013. }
  1014. @override
  1015. void dispose() {
  1016. super.dispose();
  1017. }
  1018. }
  1019. class TCMData {
  1020. String key;
  1021. String value;
  1022. TCMData({required this.key, required this.value});
  1023. factory TCMData.fromJson(Map<String, dynamic> json) {
  1024. return TCMData(
  1025. key: json['Key'],
  1026. value: json['Value'],
  1027. );
  1028. }
  1029. }