tcm_card.dart 38 KB

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