import 'dart:convert'; import 'dart:math'; import 'package:fis_common/index.dart'; import 'package:fis_jsonrpc/rpc.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:vitalapp/architecture/storage/text_storage.dart'; import 'package:vitalapp/architecture/utils/prompt_box.dart'; import 'package:vitalapp/components/alert_dialog.dart'; import 'package:vitalapp/components/appbar.dart'; import 'package:vitalapp/components/button.dart'; import 'package:vitalapp/components/dialog_input.dart'; import 'package:vitalapp/components/dialog_select.dart'; import 'package:vitalapp/components/dynamic_drawer.dart'; import 'package:vitalapp/consts/styles.dart'; import 'package:vitalapp/global.dart'; import 'package:vitalapp/managers/interfaces/cachedRecord.dart'; import 'package:vitalapp/managers/interfaces/template.dart'; import 'package:vitalapp/pages/check/models/form.dart'; import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_health_guidance_check_box.dart'; import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_input.dart'; import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_radio.dart'; import 'package:vitalapp/pages/check/widgets/exam_configurable/exam_radio_score.dart'; import 'package:vitalapp/store/store.dart'; class TCMConstitutionModule extends StatefulWidget { final String cardKey; final Future Function(String, String, dynamic) callBack; final String? patientCode; final String? examData; final bool? isEdit; const TCMConstitutionModule({ super.key, required this.cardKey, required this.callBack, this.patientCode, this.examData, this.isEdit = false, }); @override State createState() => _ConfigurableFormState(); } class _ConfigurableFormState extends State { /// 当前最新的模板的键值对 Map templateRelation = {}; bool isFirstEnter = true; String templateCode = ''; /// 当前模板数据 List currentTemplate = []; /// 当前title的下标 int currentTitleIndex = 0; Map formValue = {}; var scaffoldKey = GlobalKey(); List resourceInfo = []; final _templateManager = Get.find(); final _cachedRecordManager = Get.find(); Map currentTable = {}; late MapEntry currentResult; late List> compatibleResult = >[]; late List unansweredQuestions = []; List storeTypeList = [ 'Qi_Score', 'Yang_Score', 'Yin_Score', 'Tan_Score', 'Shi_Score', 'Xue_Score', 'Qiyu_Score', 'Te_Score', 'Ping_Score' ]; Map storeTypeMap = { "Qi_Score": '气虚质', "Yang_Score": '阳虚质', "Yin_Score": '阴虚质', "Tan_Score": '痰湿质', "Shi_Score": '湿热质', "Xue_Score": '血瘀质', "Qiyu_Score": '气郁质', "Te_Score": '特禀质', "Ping_Score": '平和质', }; Map storeTypeMapDefaultTitle = { "Qi_Score": [2, 3, 4, 14], "Yang_Score": [11, 12, 13, 29], "Yin_Score": [10, 21, 26, 31], "Tan_Score": [9, 16, 28, 32], "Shi_Score": [23, 25, 27, 30], "Xue_Score": [19, 22, 24, 33], "Qiyu_Score": [5, 6, 7, 8], "Te_Score": [15, 17, 18, 20], "Ping_Score": [1, 2, 4, 5, 13], }; Map typeMap = { 'Peaceful_Quality': "Ping_Score", "Yang_Deficiency_Substance": "Yang_Score", "Idiosyncratic_Quality": "Te_Score", "Qi_Stagnation_Constitution": "Qiyu_Score", "Blood_Stasis_Substance": "Xue_Score", "Damp-heat_constitution": "Shi_Score", "Phlegm-dampness_constitution": "Tan_Score", "Yin_Deficiency_Substance": "Yin_Score", "Qi_Deficiency_Constitution": "Qi_Score", }; String resultConstitution = ""; late int? selectedIndex = -1; // 用于跟踪选中的项目索引 @override void initState() { selectedIndex = -1; super.initState(); initReadCached(); getResourceInfo(); unansweredQuestions.clear(); // compatibleResult?.clear(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { if (mounted) { initTemplate(); } }); } Future initReadCached() async { TextStorage cachedRecord = TextStorage( fileName: 'ZYTZ', directory: "patient/${widget.patientCode}", ); String? value = await cachedRecord.read(); if (value == null) { formValue = {}; Store.resident.handleSaveMedicalData(jsonEncode(formValue)); return; } Store.resident.handleSaveMedicalData(value); formValue = jsonDecode(value); } Future saveCachedRecord() async { // deleteDirectory(); Store.resident.handleSaveMedicalData(jsonEncode(formValue)); TextStorage cachedRecord = TextStorage( fileName: 'ZYTZ', directory: "patient/${widget.patientCode}", ); return cachedRecord.save(jsonEncode(formValue)); } Future deleteDirectory() async { TextStorage cachedRecord = TextStorage( fileName: 'ZYTZ', directory: "patient/${widget.patientCode}", ); return cachedRecord.deleteDirectory(); } Future getResourceInfo() async { if (FPlatform.isWindows) { return; } TextStorage resourceInfoCacheRecord = TextStorage( fileName: "healthGuidance", directory: "HealthGuidance/temlpate", ); String? value = await resourceInfoCacheRecord.read(); if (value != null) { var json = jsonDecode(value); resourceInfo = List.from(json['TCMDesc'].map((x) => TCMData.fromJson(x))); } } @override Widget build(BuildContext context) { return Scaffold( key: scaffoldKey, appBar: VAppBar( titleText: "中医体质", actions: (currentTitleIndex == 0 && !isFirstEnter) ? [ TextButton( onPressed: () async { final result = await VDialogSelect( title: "体质类型", source: storeTypeList, valueGetter: (data) => data, labelGetter: (data) => storeTypeMap[data], initialValue: formValue["PhysicalConclusion"], ).show(); if (result != null) { Random random = Random(); formValue.clear(); formValue["PhysicalConclusion"] = result; if (result == "Ping_Score") { for (var i = 1; i < 34; i++) { if (i == 1) { int randomNumber = 3 + random.nextInt(3); formValue["qusition$i"] = randomNumber.toString(); } else { int randomNumber = 1 + random.nextInt(2); formValue["qusition$i"] = randomNumber.toString(); } } } else { for (var i = 1; i < 34; i++) { var questions = storeTypeMapDefaultTitle[result] as List; if (questions.contains(i)) { int randomNumber = 3 + random.nextInt(3); formValue["qusition$i"] = randomNumber.toString(); } else { int randomNumber = 1 + random.nextInt(2); formValue["qusition$i"] = randomNumber.toString(); } } } for (var currentFormObject in currentTemplate[currentTitleIndex].children!) { optionUpdate(currentFormObject, formValue[currentFormObject.key]); } var currentFormObject = currentTemplate[currentTitleIndex].children!.first; optionUpdate( currentFormObject, formValue[currentFormObject.key]); } setState(() {}); }, child: Text( "选择体质", style: TextStyle( color: Colors.white, fontSize: 24, ), ), ), ] : null, ), resizeToAvoidBottomInset: false, body: (!widget.isEdit!) && isFirstEnter ? Container( decoration: const BoxDecoration( image: DecorationImage( image: AssetImage("assets/images/healthGuidanceBackground.png"), fit: BoxFit.cover, ), ), child: Column( children: [ const SizedBox( height: 120, ), Center( child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.black, width: 1)), padding: const EdgeInsets.all(10), // 添加内边距 child: Container( decoration: BoxDecoration( border: Border.all(color: Colors.black, width: 1)), padding: const EdgeInsets.all(10), // 添加内边距 child: const SizedBox( width: 900, child: Text( '《中医体质分类与判定标准》于2009年4月9日,由中华中医药学会正式发布,该标准旨在为体质辨识及中医体质相关疾病的防治、养生保健、健康管理提供依据,使体质分类科学化、规范化,是我国第一部指导和规范中医体质研究及应用的文件。适用于从事中医体质研究的中医临床医生、科研人员及相关管理人员,并可作为临床实践、判定规范及质量评定的重要参考依据。其中将中国人的体质分为九种,是体质分类的一种规范。具体有平和质、阳虚质、阴虚质、气虚质、痰湿质、湿热质、气郁质、血瘀质、特禀质。', style: TextStyle(fontSize: 24), ), ), ), ), ), const SizedBox( height: 120, ), VButton( label: "开始检测", onTap: () { setState(() { isFirstEnter = false; }); }, ), ], ), ) : Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox( height: 20, ), if (currentTitleIndex == 1) Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const SizedBox( width: 80, ), Text( '当前体质判定为:${storeTypeMap[currentResult.key]} (${jsonDecode(currentResult.value)["Result"]} ${jsonDecode(currentResult.value)["Value"]}分)', style: const TextStyle( fontSize: 28, color: Colors.black, ), ) ], ), if (compatibleResult.isNotEmpty) Row( children: const [ SizedBox( width: 80, ), Text( '兼夹体质有:', style: TextStyle( fontSize: 25, color: Colors.black, ), ), ], ), if (compatibleResult.isNotEmpty) ListView.builder( shrinkWrap: true, itemCount: (compatibleResult.length / 3).ceil(), itemBuilder: (context, index) { return Row( children: [ const SizedBox(width: 120), for (int i = index * 5; i < (index + 1) * 5; i++) if (i < compatibleResult.length) Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${storeTypeMap[compatibleResult[i].key]} (${jsonDecode(compatibleResult[i].value)["Result"]} ${jsonDecode(compatibleResult[i].value)["Value"]}分) ', style: const TextStyle( fontSize: 20, color: Colors.black, ), softWrap: true, ), ], ), if ((index + 1) * 3 < compatibleResult.length) Container(height: 20), // 控制换行间距 ], ); }, ), ], ), Expanded( child: Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ // SizedBox(width: widget.isEdit! ? 90 : 50), _buildContent(), // SizedBox(width: widget.isEdit! ? 90 : 50), ], ), ), Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ if (currentTitleIndex == 0) VButton( onTap: _buildCompute, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "计算体质辨识结果", style: const TextStyle( fontSize: 20, ), ), ], ), ), const SizedBox( width: 10, ), if (currentTitleIndex == 1) VButton( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(widget.isEdit! ? "修改" : "返回", style: TextStyle(fontSize: 20)), ], ), onTap: () { setState(() { // if (!widget.isEdit!) formValue.clear(); currentTitleIndex = 0; }); }, ), const SizedBox( width: 10, ), if (currentTitleIndex == 1) VButton( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text("提交", style: const TextStyle(fontSize: 20)), ], ), onTap: () async { final result = await widget.callBack( widget.cardKey, templateCode, jsonEncode(formValue), ); if (result) { setState(() { if (!widget.isEdit!) formValue.clear(); currentTitleIndex = 0; if (!FPlatform.isWindows) { deleteDirectory(); } }); } Get.back(); }, ), ], ), const SizedBox( height: 10, ), ], ), ); } String getHealthGuidance(String key) { String? value = resourceInfo.firstWhereOrNull((element) => element.key == key)?.value; return value ?? ''; } /// 主页面 Widget _buildContent() { return Expanded( child: flowCardList(), ); } @override void dispose() { super.dispose(); } Future initTemplate() async { Store.app.busy = true; await fetchTemplateIndex(); await fetchTemplate(widget.cardKey); await fetchTemplateData(); Store.app.busy = false; } /// 读取体检的缓存 Future readCachedCheck() async { if (widget.patientCode == null) { return null; } String? value = await _cachedRecordManager.readCachedRecord( widget.cardKey, widget.patientCode!, 'FollowUp', ); return value; } Future fetchTemplateIndex() async { try { /// 获取模板的键值对 String? templates; templates = await _templateManager.readTemplateRelation('templateRelation'); templateRelation = jsonDecode(templates!); setState(() {}); } catch (error) { print('发生错误: $error'); } } Future fetchTemplateData() async { if (widget.examData?.isNotEmpty ?? false) { formValue = jsonDecode(widget.examData!); var jsonKey = formValue["PhysicalConclusion"]; currentResult = MapEntry(jsonKey, formValue[jsonKey]); compatibleResult = calculateAllResylts(); if (widget.isEdit!) { currentTitleIndex = 1; } setState(() {}); return; } formValue.forEach( (key, value) { if (value is List) { formValue[key] = List.from(formValue[key]); } else if (value is List) { formValue[key] = List.from(formValue[key]); } }, ); // currentResult = formValue[formValue["PhysicalConclusion"].value]; // if (widget.isEdit!) { // currentTitleIndex = 1; // } setState(() {}); } Future fetchTemplate(String key) async { try { if (templateRelation[key] == null) { currentTemplate = []; setState(() {}); if (!FPlatform.isWindows) { return; } } List> list = []; String? template; if (templateRelation.containsKey(key)) { templateCode = templateRelation[key]!; template = await _templateManager.readTemplate(templateCode); } if (template == null) { var json = await loadJsonFromAssets('assets/templates/${key}.json'); list = jsonDecode(json)["Content"].cast>(); } else { var templateContent = TemplateDTO.fromJson(jsonDecode(template)).templateContent!; list = jsonDecode(templateContent).cast>(); } for (var i in list) { if (i['children'] != null) { List currentChildren = []; for (var j in i['children']) { currentChildren.add(FormObject.fromJson(j)); } i['children'] = currentChildren; } var item = FormObject.fromJson(i); currentTemplate.add(item); } // if (widget.cardKey == 'LNRZYYJKGLFWJL') { // for (var element in storeTypeList) { // formValue[element] = // calculatePhysicalFitnessScore(element, element == 'Ping_Score'); // } // } setState(() {}); } catch (error) { print('发生错误: $error'); } } Future loadJsonFromAssets(String filePath) async { String jsonString = await rootBundle.loadString(filePath); return jsonString; } Widget buildSingleItem(Widget item, int span) { return Column( children: [ FractionallySizedBox( widthFactor: span == 24 ? 1 : 0.5, child: item, ), ], ); } Widget buildWidget(FormObject? currentFormObject) { Map widgetMap = { 'input': _buildInput, 'radio': _buildRadio, 'radioScore': _buildRadioScore, 'healthGuidanceCheckBox': _buildHealthGuidanceCheckBox, }; Widget Function(FormObject) builder = widgetMap[currentFormObject?.type] ?? _buildInput; return builder(currentFormObject!); } Widget flowCardList() { int itemCount = 0; bool currentTemplateOptionsIsNotEmpty = false; if (currentTemplate.isNotEmpty) { itemCount = currentTemplate[currentTitleIndex].children?.length ?? 0; currentTemplateOptionsIsNotEmpty = currentTemplate[currentTitleIndex].options?.isNotEmpty ?? false; } List items = List.generate(itemCount, (index) { FormObject? currentFormObject = currentTemplate[currentTitleIndex].children?[index]; int span = currentFormObject?.span ?? 12; //父结构的options不等于空或null //子结构的options若是无值则取父类的options值 if (currentTemplateOptionsIsNotEmpty) { if (currentFormObject!.options!.isEmpty) currentFormObject.options = currentTemplate[currentTitleIndex].options; } return buildSingleItem(buildWidget(currentFormObject), span); }); final ScrollController scrollController = ScrollController(); return Scrollbar( controller: scrollController, // thumbVisibility: false, // isAlwaysShown: false, child: ListView( controller: scrollController, padding: EdgeInsets.symmetric(horizontal: 50), children: [ Container( alignment: Alignment.topCenter, child: Wrap( runSpacing: currentTitleIndex == 1 ? 5 : 10, // 纵向元素间距 alignment: WrapAlignment.start, children: items, ), ), if (currentTitleIndex == 1) Text( getHealthGuidance(currentResult.key), style: TextStyle(fontSize: 18), ), ], ), ); } /// title标签 _buildCompute() { var questionsAnsweredNumber = formValue.entries .where((entry) => entry.key.contains("qusition")) .length; //获取已完成答题数量 unansweredQuestions.clear(); if (widget.cardKey == 'LNRZYYJKGLFWJL' && currentTitleIndex == 0 && questionsAnsweredNumber >= 33) { List> result = caculationConclusion(); String? guidanceResult = hasGuidanceBeenProvided(); String message = ''; String title = ''; bool isExistConflict = false; if (result.length == 1) { currentResult = result.first; formValue["PhysicalConclusion"] = currentResult.key; //直接进入中医药保健指导 setState(() { compatibleResult = calculateAllResylts(); currentTitleIndex = 1; }); } else { if (result.length > 1) { //弹出选择框 isExistConflict = result .where((entry) => entry.key == "Yang_Score" || entry.key == "Yin_Score") .length == 2; if (isExistConflict) { message = '判定结果既是阴虚又是阳虚的矛盾判定结果,需重新填写问题:10、11、12、13、21、26、29、31'; title = '体质冲突'; var list = [10, 11, 12, 13, 21, 26, 29, 31]; for (var element in list) { unansweredQuestions.add('qusition$element'); } } else { message = '根据答题情况,计算出多个体质,请选择其中一个'; title = '体质选择'; } } if (result.isEmpty) { //无法断体质 message = '请重新完成体质问询或2周后重新采集填写'; title = '无法判断体质'; } Get.dialog( VAlertDialog( title: title, width: 700, showCancel: false, content: Container( padding: const EdgeInsets.symmetric(horizontal: 24), height: result.length > 1 && !isExistConflict ? result.length / 3 * 150 : 150, alignment: Alignment.center, child: Column( children: [ Text( message, style: const TextStyle(fontSize: 24), ), SizedBox( height: 10, ), if (result.length > 1 && !isExistConflict) Expanded( child: SelectResult( result: result, guidanceResult: guidanceResult, selectCheckBoxChange: (value) { selectedIndex = value; currentResult = result[value]; formValue["PhysicalConclusion"] = currentResult.key; }, ), ), ], ), ), onConfirm: () { if (selectedIndex != -1) { compatibleResult = calculateAllResylts(); currentTitleIndex = 1; } Get.back(); setState(() {}); }, ), barrierDismissible: false, barrierColor: Colors.black.withOpacity(.4), ); } } else { for (var i = 1; i < 34; i++) { var isExitsQuestion = formValue.entries .where((element) => element.key == 'qusition$i') .isNotEmpty; if (!isExitsQuestion) { unansweredQuestions.add('qusition$i'); } } setState(() {}); PromptBox.toast('题目未答完整,无法计算体质'); } } //验证体质 List> caculationConclusion() { var formValues = formValue.entries .where((entry) => storeTypeList.contains(entry.key) && entry.key != "Ping_Score") .toList(); var pingResult = jsonDecode(formValue["Ping_Score"])["Result"]; // int score = int.parse(formValue["Ping_Score"]["Value"] ?? 0); // var pingResult = getJudgmentResult('Ping_Score', score); if (["是", "基本是", "倾向是"].contains(pingResult)) { return formValue.entries .where((element) => element.key == 'Ping_Score') .toList(); } else { dynamic maxValue = formValues .map((entry) => jsonDecode(entry.value)["Value"]) .reduce((a, b) => int.parse(a.toString()) > int.parse(b.toString()) ? a : b); if (int.parse(maxValue.toString()) > 8) { List> keyValuePairs = formValues .where((entry) => jsonDecode(entry.value)["Value"] == maxValue) .toList(); return keyValuePairs; } else { return List.empty(); } } } String? hasGuidanceBeenProvided() { var formValues = formValue.entries .where((entry) => entry.key == 'PhysicalConclusion') .toList(); if (formValues.isNotEmpty) { return formValues.first.value; } return ""; } Widget _buildInput(FormObject currentFormObject) { String currentInputValue = formValue[currentFormObject.key!] ?? ''; Future commonInput() async { String? result = await VDialogInput( title: currentFormObject.label, initialValue: formValue[currentFormObject.key], ).show(); if (result?.isNotEmpty ?? false) { formValue[currentFormObject.key!] = result; currentInputValue = formValue[currentFormObject.key!]; setState(() {}); } } return ExamInput( currentInputValue: currentInputValue, commonInput: commonInput, currentFormObject: currentFormObject, ); } ///计算满足的所有体质类型 List> calculateAllResylts() { List> compatibleResults = >[]; for (var key in storeTypeList) { var result = jsonDecode(formValue[key])["Result"]; if (["是", "基本是", "倾向是"].contains(result)) { compatibleResults.add(MapEntry(key, formValue[key])); } } compatibleResults = compatibleResults .where((element) => element.key != currentResult.key) .toList(); return compatibleResults; } ///中医药健康指导多选框组件 Widget _buildHealthGuidanceCheckBox(FormObject currentFormObject) { List