import 'dart:convert'; import 'dart:io'; import 'package:fis_jsonrpc/rpc.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:uuid/uuid.dart'; import 'package:vitalapp/architecture/utils/prompt_box.dart'; import 'package:vitalapp/architecture/utils/upload.dart'; import 'package:vitalapp/components/alert_dialog.dart'; import 'package:vitalapp/database/db.dart'; import 'package:vitalapp/database/entities/defines.dart'; import 'package:vitalapp/database/entities/diagnosis.dart'; import 'package:vitalapp/global.dart'; import 'package:vitalapp/managers/interfaces/exam.dart'; import 'package:vitalapp/managers/interfaces/models/diagnosis_aggregation_record_model.dart'; import 'package:vitalapp/managers/interfaces/patient.dart'; import 'package:vitalapp/managers/interfaces/record_data_cache.dart'; import 'package:vitalapp/routes/routes.dart'; import 'package:vitalapp/rpc.dart'; import 'package:vnote_device_plugin/consts/types.dart'; import 'package:vitalapp/architecture/defines.dart'; import 'package:vitalapp/architecture/storage/text_storage.dart'; import 'package:vitalapp/managers/interfaces/cachedRecord.dart'; import 'package:vitalapp/managers/interfaces/device.dart'; import 'package:vitalapp/managers/interfaces/diagnosis.dart'; import 'package:vitalapp/managers/interfaces/models/device.dart'; import 'package:vitalapp/pages/medical/models/item.dart'; import 'package:vitalapp/pages/medical/state.dart'; import 'package:vitalapp/store/store.dart'; import 'package:vnote_device_plugin/events/event_type.dart'; import 'package:fis_common/logger/logger.dart'; import 'package:vitalapp/architecture/storage/storage.dart'; class MedicalController extends FControllerBase { String appDataId = ""; String patientCode = ''; Map diagnosisDataValue = {}; ExamDTO? currentExam; final _patientManager = Get.find(); final _examManager = Get.find(); final state = MedicalState(); final recordDataCacheManager = Get.find(); final FEventHandler onSelectExam = FEventHandler(); // /// 体检页面回填的数据 // final FEventHandler> onGetExamData = // FEventHandler>(); /// 返回给体检页面的数据 final FEventHandler> setExamData = FEventHandler>(); static final typeConvertMap = { DeviceTypes.TEMP: "Temp", DeviceTypes.WEIGHT: "BMI", DeviceTypes.SPO2: "SpO2", DeviceTypes.NIBP: "NIBP", DeviceTypes.SUGAR: "GLU", DeviceTypes.URINE: "Urine", DeviceTypes.IC_READER: "ICReader", DeviceTypes.HEART: "HEART", DeviceTypes.TWELVEHEART: "Twelveheart", }; final changePatient = FEventHandler(); final _diagnosisManager = Get.find(); final _cachedRecordManager = Get.find(); final _deviceManager = Get.find(); final _medicalMenus = [ MedicalItem(key: DeviceTypes.TEMP, diagnosticItem: '体温'), MedicalItem(key: DeviceTypes.SUGAR, diagnosticItem: '血糖'), MedicalItem(key: DeviceTypes.NIBP, diagnosticItem: '血压'), MedicalItem(key: DeviceTypes.SPO2, diagnosticItem: '血氧'), MedicalItem(key: DeviceTypes.WEIGHT, diagnosticItem: 'BMI'), MedicalItem(key: DeviceTypes.WAIST, diagnosticItem: '腰臀比'), MedicalItem(key: DeviceTypes.URINE, diagnosticItem: '尿常规'), MedicalItem(key: DeviceTypes.HEART, diagnosticItem: '心电'), MedicalItem(key: DeviceTypes.TWELVEHEART, diagnosticItem: '十二导心电'), ]; // @override // void onInit() { // super.onInit(); // } @override void onReady() async { super.onReady(); await initData(); await getAccessTypes(); // onGetExamData.addListener(getExamData); state.currentTab = DeviceTypes.TEMP; //等数据加载完成之后在切换到体温页面 // busy = false; // logger.i('MedicalController init end'); } Future initData() async { patientCode = Store.user.currentSelectPatientInfo?.code ?? ''; if (patientCode.isEmpty) { logger.w("MedicalController init fail, because `patientCode` not set."); return; } if (Routes.parameters["diagnosisEditData"] != null) { await _loadDataFromRoute(); return; } logger.i( 'MedicalController initData patientName:${Store.user.currentSelectPatientInfo?.patientName} patientCode:${Store.user.currentSelectPatientInfo?.code}'); var cachedAppDataId = await readCachedAppDataId(); if (cachedAppDataId != null) { appDataId = cachedAppDataId; } else { await saveCachedAppDataId(); } await initReadCached(); } Future _loadDataFromRoute() async { final model = Routes.parameters["diagnosisEditData"] as DiagnosisAggregationRecordModel; appDataId = model.appDataId!; final dataList = model.diagnosisAggregationData ?? []; diagnosisDataValue = {}; for (var item in dataList) { if (item.key != null && item.diagnosisData != null) { diagnosisDataValue[item.key!] = jsonDecode(item.diagnosisData!); } } Routes.parameters["diagnosisEditData"] = null; } Future getAccessTypes() async { List medicalItemList = []; List accessTypes = await _deviceManager.getAccessTypes(); for (var element in _medicalMenus) { if (accessTypes.contains(element.key)) { medicalItemList.add(element); } } state.medicalMenuList = medicalItemList; } Future getDevice(String type) async { List devices = await _deviceManager.getDeviceList(); return devices.firstWhereOrNull((element) => element.type == type); } Future initReadCached() async { if (patientCode.isNotEmpty) { logger .i('MedicalController initReadCached fail,patientCod e:$patientCode'); TextStorage cachedRecord = TextStorage( fileName: 'JKJC', directory: "patient/$patientCode", ); String? value = await cachedRecord.read(); if (value == null) { diagnosisDataValue = {}; Store.resident.handleSaveMedicalData(jsonEncode(diagnosisDataValue)); return; } Store.resident.handleSaveMedicalData(value); if (kIsWeb) { diagnosisDataValue = {}; } else { diagnosisDataValue = jsonDecode(value); } } } Future saveCachedAppDataId() async { appDataId = const Uuid().v4().replaceAll('-', ''); TextStorage cachedRecord = TextStorage( fileName: 'appDataId', directory: "patient/$patientCode", ); // Get.back(); return cachedRecord.save(appDataId); } Future readCachedAppDataId() async { TextStorage cachedRecord = TextStorage( fileName: 'appDataId', directory: "patient/$patientCode", ); return cachedRecord.read(); } Future saveCachedRecord() async { if (patientCode.isEmpty) { logger.i( 'MedicalController saveCachedRecord fail,patientCode:$patientCode'); return false; } Store.resident.handleSaveMedicalData(jsonEncode(diagnosisDataValue)); TextStorage cachedRecord = TextStorage( fileName: 'JKJC', directory: "patient/$patientCode", ); return cachedRecord.save(jsonEncode(diagnosisDataValue)); } Future deleteDirectory() async { TextStorage cachedRecord = TextStorage( fileName: 'JKJC', directory: "patient/$patientCode", ); //TODO(Loki):这里是否仅删除一个JKJC的文件就行了,不需要删除整个文件夹 return cachedRecord.delete(); } Future submitDiagnosis( Map submitDiagnosisDataValue, ) async { try { List diagnosisItems = await recordDataCacheManager .verifyDiagnosisDataList(submitDiagnosisDataValue); state.currentTab = '-1'; await recordDataCacheManager.saveRecordData( appDataId, patientCode, submitDiagnosisDataValue, ); if (kIsOnline) { busy = true; diagnosisItems = await recordDataCacheManager .convertDiagnosisDataToList(submitDiagnosisDataValue); bool result; result = await _diagnosisManager.syncPatientAndDiagnosisData( SyncPatientAndDiagnosisDataRequest( patientCode: patientCode, patientName: Store.user.currentSelectPatientInfo?.patientName ?? '', patientAddress: Store.user.currentSelectPatientInfo?.patientAddress ?? '', patientGender: Store.user.currentSelectPatientInfo?.patientGender ?? GenderEnum.Unknown, permanentResidenceAddress: Store.user.currentSelectPatientInfo?.permanentResidenceAddress, phone: Store.user.currentSelectPatientInfo?.phone, cardNo: Store.user.currentSelectPatientInfo?.cardNo, nationality: Store.user.currentSelectPatientInfo?.nationality, birthday: Store.user.currentSelectPatientInfo?.birthday, crowdLabels: Store.user.currentSelectPatientInfo?.crowdLabels, cardType: Store.user.currentSelectPatientInfo?.cardType ?? CardTypeEnum.Identity, contractedDoctor: Store.user.currentSelectPatientInfo?.contractedDoctor, appDataId: appDataId, diagnosisTime: DateTime.now(), token: Store.user.token, diagnosisItems: diagnosisItems, ), ); busy = false; if (result) { //如果在线,则提交历史数据 var submitSuccess = await _submitHistory(); if (submitSuccess) { logger.i('当前数据提交成功,且历史记录:$patientCode 提交成功'); } recordDataCacheManager.recordSyncStateChange(appDataId); PromptBox.toast('提交成功'); await initRecordDataState(); Get.back(); ///提交之后,测试结果清空 diagnosisDataValue.clear(); } else { PromptBox.toast('提交失败'); logger.i('提交失败:$patientCode'); } state.currentTab = state.medicalMenuList[0].key; } else { Future.delayed(const Duration(milliseconds: 10), () { state.currentTab = state.medicalMenuList[0].key; }); PromptBox.toast("已缓存至本地"); logger.i('已缓存至本地:$patientCode'); await initRecordDataState(); ///提交之后,测试结果清空 diagnosisDataValue.clear(); state.currentTab = state.medicalMenuList[0].key; state.refreshCurrentTab(); Get.back(); } } catch (err) { busy = false; state.currentTab = state.medicalMenuList[0].key; state.refreshCurrentTab(); logger.e('submitDiagnosis error: ${err.toString()}'); } } Future initRecordDataState() async { await saveCachedAppDataId(); await deleteDirectory(); await initReadCached(); } Future createDiagnosis({ required bool isHealthCheck, }) async { busy = true; try { if (Store.user.teamName.isEmpty) { PromptBox.toast('未设置团队无法提交检测数据'); return; } if (patientCode.isEmpty || patientCode != Store.user.currentSelectPatientInfo?.code) { initData(); } Map submitDiagnosisDataValue = Map.from(diagnosisDataValue); //确定提交值不会发生变更 List diagnosisItems = await recordDataCacheManager .verifyDiagnosisDataList(submitDiagnosisDataValue); logger.i('submitDiagnosis diagnosisItems.leng:${diagnosisItems.length}'); if (diagnosisItems.isEmpty) { if (patientCode.isNotEmpty && kIsOnline) { bool submitHistory = await _submitHistory(); if (submitHistory) { PromptBox.toast('提交成功'); return; } } PromptBox.toast('不能提交空数据'); return; } if (state.medicalMenuList.length > submitDiagnosisDataValue.length) { logger.i( 'state.medicalMenuList.length:${state.medicalMenuList.length},copiedDiagnosisDataValue.length:${diagnosisDataValue.length}'); Get.dialog( VAlertDialog( title: '提示', content: Container( margin: const EdgeInsets.only(bottom: 20), child: const Text( '当前检测项目未完成,请确定是否提交本次检测', style: TextStyle(fontSize: 20), textAlign: TextAlign.center, ), ), showCancel: true, onConfirm: () { Get.back(); if (isHealthCheck) { try { setExamData.emit( this, submitDiagnosisDataValue, ); } catch (e) { print("MedicalController setExamData ex:$e"); logger.e("MedicalController setExamData ex:$e"); } } submitDiagnosis(submitDiagnosisDataValue); }, onCanceled: () {}, ), ); return; } else { if (isHealthCheck) { try { setExamData.emit( this, submitDiagnosisDataValue, ); } catch (e) { print("MedicalController setExamData ex:$e"); logger.e("MedicalController setExamData ex:$e"); } } ///如果已完成所有检查,则直接提交 submitDiagnosis(submitDiagnosisDataValue); } } catch (e) { logger.e('MedicalController createDiagnosis ex:', e); } finally { busy = false; } } @override void dispose() { setExamData.dispose(); // onGetExamData.removeListener(getExamData); print('MedicalController dispose'); super.dispose(); } RxBool _isNormalcy = RxBool(true); bool get isNormalcy => _isNormalcy.value; set isNormalcy(bool value) => _isNormalcy.value = value; /// 【TODO 接口需要变更】 心电 Future createHeart( String physicalExamNumber, String? keyValue, String recordCode, ) async { try { Map input = diagnosisDataValue; if (diagnosisDataValue.isEmpty) { PromptBox.toast('不能提交空数据'); return; } await Get.dialog( VAlertDialog( title: '提示', content: Container( margin: const EdgeInsets.only(bottom: 20), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text( '当前心电检测结果是否正常:', style: TextStyle(fontSize: 20), textAlign: TextAlign.center, ), SizedBox( width: 10, ), Obx( () => Row( children: [ Radio( onChanged: (v) { isNormalcy = v ?? false; }, value: true, groupValue: isNormalcy, ), Text( "正常", style: TextStyle(fontSize: 20), ), Radio( onChanged: (v) { isNormalcy = v ?? false; }, value: false, groupValue: isNormalcy, ), Text( "异常", style: TextStyle(fontSize: 20), ), ], ), ) ], )), showCancel: false, onConfirm: () { diagnosisDataValue["TwelveHeart"]["isHeartNormalcy"] = isNormalcy; Get.back(); }, ), ); for (var entry in input.entries) { var key = entry.key; var value = entry.value; if (value != null) { Store.app.setBusy("提交中"); if (['Heart', 'TwelveHeart'].contains(key) && value is Map) { value = await uploadData(value); } Store.app.busy = false; } print('$key: $value'); } Map output = {}; input.forEach((key, value) { value.forEach((innerKey, innerValue) { output[innerKey] = innerValue; }); }); bool? result = false; if (kIsWeb && currentExam != null) { result = await _examManager.editExam(UpdateExamRequest( key: keyValue ?? "HEIECG", examData: jsonEncode(output), code: currentExam!.code)); } else { result = await _examManager.createExamDatas( recordCode, jsonEncode(output), ); } if (result == true) { busy = false; PromptBox.toast('提交成功'); Get.back(); } } catch (e) { busy = false; logger.e('MedicalController createCheckup ex:', e); } } bool isUploaded(String url) { return url.startsWith('https://') || url.startsWith('http://'); } // TODO Baka 体检暂时写法 需要封装 Future uploadData(Map data) async { if (data['ECG_POINT'] != null && !isUploaded(data['ECG_POINT'])) { if (kIsWeb) { Uint8List ecgPointbyte = await rpc.storage.writeStringToByte(data['ECG_POINT']); String? ecgPointUrl = await rpc.storage.uploadByte(ecgPointbyte); data['ECG_POINT'] = ecgPointUrl ?? ''; } else { File ecgPointFile = await rpc.storage.writeStringToFile(data['ECG_POINT']); String? ecgPointUrl = await rpc.storage.uploadFile(ecgPointFile); data['ECG_POINT'] = ecgPointUrl ?? ''; } // ... 上传点集 } if (data['ECG'] != null && !isUploaded(data['ECG'])) { // ... 上传图片 /// 图片地址 final imageFile = UploadUtils.convertBase64ToXFile(data['ECG']); String? imageUrl = await rpc.storage.upload(imageFile!); data['ECG'] = imageUrl ?? ''; } if (data['ECG_POINT12'] != null && !isUploaded(data['ECG_POINT12'])) { if (kIsWeb) { Uint8List ecgPointBytes = await rpc.storage.writeStringToByte(data['ECG_POINT12']); String? ecgPointUrl = await rpc.storage.uploadByte(ecgPointBytes); data['ECG_POINT12'] = ecgPointUrl ?? ''; } else { File ecgPointFile = await rpc.storage.writeStringToFile(data['ECG_POINT12']); String? ecgPointUrl = await rpc.storage.uploadFile(ecgPointFile); data['ECG_POINT12'] = ecgPointUrl ?? ''; } // ... 上传点集 } if (data['ECG12'] != null && !isUploaded(data['ECG12'])) { // ... 上传图片 if (kIsWeb) { var file = UploadUtils.convertBase64ToFile(data['ECG12']); String? url = await rpc.storage.webUpload(file!); data['ECG12'] = url ?? ''; } else { final imageFile = UploadUtils.convertBase64ToXFile(data['ECG12']); String? imageUrl = await rpc.storage.upload(imageFile!, fileType: "jpg"); data['ECG12'] = imageUrl ?? ''; } } if (data['ECG12_30s'] != null && !isUploaded(data['ECG12_30s'])) { // ... 上传图片 if (kIsWeb) { var file = UploadUtils.convertBase64ToFile(data['ECG12_30s']); String? url = await rpc.storage.webUpload(file!); data['ECG12_30s'] = url ?? ''; } else { final imageFile = UploadUtils.convertBase64ToXFile(data['ECG12_30s']); String? imageUrl = await rpc.storage.upload(imageFile!, fileType: "jpg"); data['ECG12_30s'] = imageUrl ?? ''; } } return data; } /// 体检 检查提交 Future createCheckup( String? physicalExamNumber, String? keyValue, ) async { try { Map input = diagnosisDataValue; Map output = {}; if (diagnosisDataValue.isEmpty) { PromptBox.toast('不能提交空数据'); return; } setBusy("提交中"); input.forEach((key, value) { if (value is String) { output[key] = value; } else if (value is Map) { value.forEach((innerKey, innerValue) { output[innerKey] = innerValue; }); } }); var result = await _examManager.createExam(CreateExamRequest( key: keyValue ?? "HEIBasic", examData: jsonEncode(output), physicalExamNumber: physicalExamNumber, )); if (result == true) { busy = false; PromptBox.toast('提交成功'); Get.back(); } onSelectExam.emit(this, true); saveCachedRecord(); } catch (e) { busy = false; logger.e('MedicalController createCheckup ex:', e); } } ///提交历史记录 Future _submitHistory() async { var existDatas = await recordDataCacheManager.getNoSubmitRecords(patientCode); var historyDatas = existDatas.where((element) => element.code != appDataId); if (historyDatas.isEmpty) { return false; } for (DiagnosisEntity data in historyDatas) { try { Map jsonData = jsonDecode(data.dataJson); var diagnosisItems = await recordDataCacheManager.convertDiagnosisDataToList(jsonData); SubmitDiagnosisRequest submitDiagnosisRequest = SubmitDiagnosisRequest( appDataId: data.code, patientCode: data.patientCode, diagnosisItems: diagnosisItems, diagnosisTime: data.updateTime ?? data.createTime, ); bool submitResult; if (kIsOnline) { PatientDTO? patientInfo = await _patientManager.getDetail( data.patientCode, ); if (patientInfo == null) { ///如果Server没有,则从本地读取 var patientEntity = await db.repositories.patient .singleByCodeWithUserCode( data.patientCode, Store.user.userCode!); if (patientEntity != null) { patientInfo = PatientDTO.fromJson(jsonDecode(patientEntity.dataJson)); } } submitResult = await _diagnosisManager.syncPatientAndDiagnosisData( SyncPatientAndDiagnosisDataRequest( token: Store.user.token, patientCode: data.patientCode, patientName: patientInfo?.patientName ?? '', patientAddress: patientInfo?.patientAddress ?? '', patientGender: patientInfo?.patientGender ?? GenderEnum.Unknown, permanentResidenceAddress: patientInfo?.permanentResidenceAddress, phone: patientInfo?.phone, cardNo: patientInfo?.cardNo, nationality: patientInfo?.nationality, birthday: patientInfo?.birthday, crowdLabels: patientInfo?.crowdLabels, cardType: patientInfo?.cardType ?? CardTypeEnum.Identity, contractedDoctor: patientInfo?.contractedDoctor, appDataId: data.code, diagnosisTime: data.updateTime ?? data.createTime, diagnosisItems: diagnosisItems, ), ); } else { submitResult = await _diagnosisManager .submitDiagnosisAsync(submitDiagnosisRequest); } OfflineDataSyncState state; if (submitResult) { state = OfflineDataSyncState.success; logger.i('提交历史记录:$patientCode 成功'); } else { state = OfflineDataSyncState.fail; logger.i('提交历史记录:$patientCode 失败'); } recordDataCacheManager.recordSyncStateChange(data.code, state: state); } catch (e) { logger.e('MedicalController _submitHistory ex:', e); } } return true; } }