controller.dart 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. import 'dart:convert';
  2. import 'dart:io';
  3. import 'package:fis_jsonrpc/rpc.dart';
  4. import 'package:flutter/foundation.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:get/get.dart';
  7. import 'package:uuid/uuid.dart';
  8. import 'package:vitalapp/architecture/utils/prompt_box.dart';
  9. import 'package:vitalapp/architecture/utils/upload.dart';
  10. import 'package:vitalapp/components/alert_dialog.dart';
  11. import 'package:vitalapp/database/db.dart';
  12. import 'package:vitalapp/database/entities/defines.dart';
  13. import 'package:vitalapp/database/entities/diagnosis.dart';
  14. import 'package:vitalapp/global.dart';
  15. import 'package:vitalapp/managers/interfaces/exam.dart';
  16. import 'package:vitalapp/managers/interfaces/models/diagnosis_aggregation_record_model.dart';
  17. import 'package:vitalapp/managers/interfaces/patient.dart';
  18. import 'package:vitalapp/managers/interfaces/record_data_cache.dart';
  19. import 'package:vitalapp/routes/routes.dart';
  20. import 'package:vitalapp/rpc.dart';
  21. import 'package:vnote_device_plugin/consts/types.dart';
  22. import 'package:vitalapp/architecture/defines.dart';
  23. import 'package:vitalapp/architecture/storage/text_storage.dart';
  24. import 'package:vitalapp/managers/interfaces/cachedRecord.dart';
  25. import 'package:vitalapp/managers/interfaces/device.dart';
  26. import 'package:vitalapp/managers/interfaces/diagnosis.dart';
  27. import 'package:vitalapp/managers/interfaces/models/device.dart';
  28. import 'package:vitalapp/pages/medical/models/item.dart';
  29. import 'package:vitalapp/pages/medical/state.dart';
  30. import 'package:vitalapp/store/store.dart';
  31. import 'package:vnote_device_plugin/events/event_type.dart';
  32. import 'package:fis_common/logger/logger.dart';
  33. import 'package:vitalapp/architecture/storage/storage.dart';
  34. class MedicalController extends FControllerBase {
  35. String patientCode = '';
  36. Map<String, dynamic> diagnosisDataValue = {};
  37. final _patientManager = Get.find<IPatientManager>();
  38. final _examManager = Get.find<IExamManager>();
  39. String appDataId = "";
  40. final state = MedicalState();
  41. final recordDataCacheManager = Get.find<IRecordDataCacheManager>();
  42. static final typeConvertMap = <String, String>{
  43. DeviceTypes.TEMP: "Temp",
  44. DeviceTypes.WEIGHT: "BMI",
  45. DeviceTypes.SPO2: "SpO2",
  46. DeviceTypes.NIBP: "NIBP",
  47. DeviceTypes.SUGAR: "GLU",
  48. DeviceTypes.URINE: "Urine",
  49. DeviceTypes.IC_READER: "ICReader",
  50. DeviceTypes.HEART: "HEART",
  51. DeviceTypes.TWELVEHEART: "Twelveheart",
  52. };
  53. final changePatient = FEventHandler<String>();
  54. final _diagnosisManager = Get.find<IDiagnosisManager>();
  55. final _cachedRecordManager = Get.find<ICachedRecordManager>();
  56. final _deviceManager = Get.find<IDeviceManager>();
  57. final _medicalMenus = [
  58. MedicalItem(key: DeviceTypes.TEMP, diagnosticItem: '体温'),
  59. MedicalItem(key: DeviceTypes.SUGAR, diagnosticItem: '血糖'),
  60. MedicalItem(key: DeviceTypes.NIBP, diagnosticItem: '血压'),
  61. MedicalItem(key: DeviceTypes.SPO2, diagnosticItem: '血氧'),
  62. MedicalItem(key: DeviceTypes.WEIGHT, diagnosticItem: 'BMI'),
  63. MedicalItem(key: DeviceTypes.WAIST, diagnosticItem: '腰臀比'),
  64. MedicalItem(key: DeviceTypes.URINE, diagnosticItem: '尿常规'),
  65. MedicalItem(key: DeviceTypes.HEART, diagnosticItem: '心电'),
  66. MedicalItem(key: DeviceTypes.TWELVEHEART, diagnosticItem: '十二导心电'),
  67. ];
  68. // @override
  69. // void onInit() {
  70. // super.onInit();
  71. // }
  72. @override
  73. void onReady() async {
  74. super.onReady();
  75. await initData();
  76. await getAccessTypes();
  77. state.currentTab = DeviceTypes.TEMP; //等数据加载完成之后在切换到体温页面
  78. // busy = false;
  79. // logger.i('MedicalController init end');
  80. }
  81. Future<void> initData() async {
  82. patientCode = Store.user.currentSelectPatientInfo?.code ?? '';
  83. if (patientCode.isEmpty) {
  84. logger.w("MedicalController init fail, because `patientCode` not set.");
  85. return;
  86. }
  87. if (Routes.parameters["diagnosisEditData"] != null) {
  88. await _loadDataFromRoute();
  89. return;
  90. }
  91. logger.i(
  92. 'MedicalController initData patientName:${Store.user.currentSelectPatientInfo?.patientName} patientCode:${Store.user.currentSelectPatientInfo?.code}');
  93. var cachedAppDataId = await readCachedAppDataId();
  94. if (cachedAppDataId != null) {
  95. appDataId = cachedAppDataId;
  96. } else {
  97. await saveCachedAppDataId();
  98. }
  99. await initReadCached();
  100. }
  101. Future<void> _loadDataFromRoute() async {
  102. final model = Routes.parameters["diagnosisEditData"]
  103. as DiagnosisAggregationRecordModel;
  104. appDataId = model.appDataId!;
  105. final dataList = model.diagnosisAggregationData ?? [];
  106. diagnosisDataValue = {};
  107. for (var item in dataList) {
  108. if (item.key != null && item.diagnosisData != null) {
  109. diagnosisDataValue[item.key!] = jsonDecode(item.diagnosisData!);
  110. }
  111. }
  112. Routes.parameters["diagnosisEditData"] = null;
  113. }
  114. Future<void> getAccessTypes() async {
  115. List<MedicalItem> medicalItemList = [];
  116. List<String> accessTypes = await _deviceManager.getAccessTypes();
  117. for (var element in _medicalMenus) {
  118. if (accessTypes.contains(element.key)) {
  119. medicalItemList.add(element);
  120. }
  121. }
  122. state.medicalMenuList = medicalItemList;
  123. }
  124. Future<DeviceModel?> getDevice(String type) async {
  125. List<DeviceModel> devices = await _deviceManager.getDeviceList();
  126. return devices.firstWhereOrNull((element) => element.type == type);
  127. }
  128. Future<void> initReadCached() async {
  129. if (patientCode.isNotEmpty) {
  130. logger
  131. .i('MedicalController initReadCached fail,patientCod e:$patientCode');
  132. TextStorage cachedRecord = TextStorage(
  133. fileName: 'JKJC',
  134. directory: "patient/$patientCode",
  135. );
  136. String? value = await cachedRecord.read();
  137. if (value == null) {
  138. diagnosisDataValue = {};
  139. Store.resident.handleSaveMedicalData(jsonEncode(diagnosisDataValue));
  140. return;
  141. }
  142. Store.resident.handleSaveMedicalData(value);
  143. if (kIsWeb) {
  144. diagnosisDataValue = {};
  145. } else {
  146. diagnosisDataValue = jsonDecode(value);
  147. }
  148. }
  149. }
  150. Future<bool?> saveCachedAppDataId() async {
  151. appDataId = const Uuid().v4().replaceAll('-', '');
  152. TextStorage cachedRecord = TextStorage(
  153. fileName: 'appDataId',
  154. directory: "patient/$patientCode",
  155. );
  156. Get.back();
  157. return cachedRecord.save(appDataId);
  158. }
  159. Future<String?> readCachedAppDataId() async {
  160. TextStorage cachedRecord = TextStorage(
  161. fileName: 'appDataId',
  162. directory: "patient/$patientCode",
  163. );
  164. return cachedRecord.read();
  165. }
  166. Future<bool?> saveCachedRecord() async {
  167. if (patientCode.isEmpty) {
  168. logger.i(
  169. 'MedicalController saveCachedRecord fail,patientCode:$patientCode');
  170. return false;
  171. }
  172. Store.resident.handleSaveMedicalData(jsonEncode(diagnosisDataValue));
  173. TextStorage cachedRecord = TextStorage(
  174. fileName: 'JKJC',
  175. directory: "patient/$patientCode",
  176. );
  177. return cachedRecord.save(jsonEncode(diagnosisDataValue));
  178. }
  179. Future<bool?> deleteDirectory() async {
  180. TextStorage cachedRecord = TextStorage(
  181. fileName: 'JKJC',
  182. directory: "patient/$patientCode",
  183. );
  184. return cachedRecord.deleteDirectory();
  185. }
  186. Future<void> submitDiagnosis(
  187. Map<String, dynamic> submitDiagnosisDataValue,
  188. ) async {
  189. try {
  190. List<DiagnosisItem> diagnosisItems = await recordDataCacheManager
  191. .verifyDiagnosisDataList(submitDiagnosisDataValue);
  192. state.currentTab = '-1';
  193. await recordDataCacheManager.saveRecordData(
  194. appDataId,
  195. patientCode,
  196. submitDiagnosisDataValue,
  197. );
  198. if (kIsOnline) {
  199. busy = true;
  200. diagnosisItems = await recordDataCacheManager
  201. .convertDiagnosisDataToList(submitDiagnosisDataValue);
  202. bool result;
  203. result = await _diagnosisManager.syncPatientAndDiagnosisData(
  204. SyncPatientAndDiagnosisDataRequest(
  205. patientCode: patientCode,
  206. patientName: Store.user.currentSelectPatientInfo?.patientName ?? '',
  207. patientAddress:
  208. Store.user.currentSelectPatientInfo?.patientAddress ?? '',
  209. patientGender: Store.user.currentSelectPatientInfo?.patientGender ??
  210. GenderEnum.Unknown,
  211. permanentResidenceAddress:
  212. Store.user.currentSelectPatientInfo?.permanentResidenceAddress,
  213. phone: Store.user.currentSelectPatientInfo?.phone,
  214. cardNo: Store.user.currentSelectPatientInfo?.cardNo,
  215. nationality: Store.user.currentSelectPatientInfo?.nationality,
  216. birthday: Store.user.currentSelectPatientInfo?.birthday,
  217. crowdLabels: Store.user.currentSelectPatientInfo?.crowdLabels,
  218. cardType: Store.user.currentSelectPatientInfo?.cardType ??
  219. CardTypeEnum.Identity,
  220. contractedDoctor:
  221. Store.user.currentSelectPatientInfo?.contractedDoctor,
  222. appDataId: appDataId,
  223. diagnosisTime: DateTime.now(),
  224. token: Store.user.token,
  225. diagnosisItems: diagnosisItems,
  226. ),
  227. );
  228. // print(SyncPatientAndDiagnosisDataRequest(
  229. // patientCode: patientCode,
  230. // patientName: Store.user.currentSelectPatientInfo?.patientName ?? '',
  231. // patientAddress:
  232. // Store.user.currentSelectPatientInfo?.patientAddress ?? '',
  233. // patientGender: Store.user.currentSelectPatientInfo?.patientGender ??
  234. // GenderEnum.Unknown,
  235. // permanentResidenceAddress:
  236. // Store.user.currentSelectPatientInfo?.permanentResidenceAddress,
  237. // phone: Store.user.currentSelectPatientInfo?.phone,
  238. // cardNo: Store.user.currentSelectPatientInfo?.cardNo,
  239. // nationality: Store.user.currentSelectPatientInfo?.nationality,
  240. // birthday: Store.user.currentSelectPatientInfo?.birthday,
  241. // crowdLabels: Store.user.currentSelectPatientInfo?.crowdLabels,
  242. // cardType: Store.user.currentSelectPatientInfo?.cardType ??
  243. // CardTypeEnum.Identity,
  244. // contractedDoctor:
  245. // Store.user.currentSelectPatientInfo?.contractedDoctor,
  246. // appDataId: appDataId,
  247. // diagnosisTime: DateTime.now(),
  248. // token: Store.user.token,
  249. // diagnosisItems: diagnosisItems,
  250. // ).toJson());
  251. busy = false;
  252. if (result) {
  253. //如果在线,则提交历史数据
  254. var submitSuccess = await _submitHistory();
  255. if (submitSuccess) {
  256. logger.i('当前数据提交成功,且历史记录:$patientCode 提交成功');
  257. }
  258. recordDataCacheManager.recordSyncStateChange(appDataId);
  259. PromptBox.toast('提交成功');
  260. await initRecordDataState();
  261. ///提交之后,测试结果清空
  262. diagnosisDataValue.clear();
  263. } else {
  264. PromptBox.toast('提交失败');
  265. logger.i('提交失败:$patientCode');
  266. }
  267. state.currentTab = state.medicalMenuList[0].key;
  268. } else {
  269. Future.delayed(const Duration(milliseconds: 10), () {
  270. state.currentTab = state.medicalMenuList[0].key;
  271. });
  272. PromptBox.toast("已缓存至本地");
  273. logger.i('已缓存至本地:$patientCode');
  274. await initRecordDataState();
  275. ///提交之后,测试结果清空
  276. diagnosisDataValue.clear();
  277. state.currentTab = state.medicalMenuList[0].key;
  278. state.refreshCurrentTab();
  279. Get.back();
  280. }
  281. } catch (err) {
  282. busy = false;
  283. state.currentTab = state.medicalMenuList[0].key;
  284. state.refreshCurrentTab();
  285. logger.e('submitDiagnosis error: ${err.toString()}');
  286. }
  287. }
  288. Future<void> initRecordDataState() async {
  289. await saveCachedAppDataId();
  290. await deleteDirectory();
  291. await initReadCached();
  292. }
  293. Future<void> createDiagnosis() async {
  294. busy = true;
  295. try {
  296. if (Store.user.teamName.isEmpty) {
  297. PromptBox.toast('未设置团队无法提交检测数据');
  298. return;
  299. }
  300. if (patientCode.isEmpty ||
  301. patientCode != Store.user.currentSelectPatientInfo?.code) {
  302. initData();
  303. }
  304. Map<String, dynamic> submitDiagnosisDataValue =
  305. Map.from(diagnosisDataValue); //确定提交值不会发生变更
  306. List<DiagnosisItem> diagnosisItems = await recordDataCacheManager
  307. .verifyDiagnosisDataList(submitDiagnosisDataValue);
  308. logger.i('submitDiagnosis diagnosisItems.leng:${diagnosisItems.length}');
  309. if (diagnosisItems.isEmpty) {
  310. if (patientCode.isNotEmpty && kIsOnline) {
  311. bool submitHistory = await _submitHistory();
  312. if (submitHistory) {
  313. PromptBox.toast('提交成功');
  314. return;
  315. }
  316. }
  317. PromptBox.toast('不能提交空数据');
  318. return;
  319. }
  320. if (state.medicalMenuList.length > submitDiagnosisDataValue.length) {
  321. logger.i(
  322. 'state.medicalMenuList.length:${state.medicalMenuList.length},copiedDiagnosisDataValue.length:${diagnosisDataValue.length}');
  323. Get.dialog(
  324. VAlertDialog(
  325. title: '提示',
  326. content: Container(
  327. margin: const EdgeInsets.only(bottom: 20),
  328. child: const Text(
  329. '当前检测项目未完成,请确定是否提交本次检测',
  330. style: TextStyle(fontSize: 20),
  331. textAlign: TextAlign.center,
  332. ),
  333. ),
  334. showCancel: true,
  335. onConfirm: () {
  336. Get.back();
  337. submitDiagnosis(submitDiagnosisDataValue);
  338. },
  339. onCanceled: () {},
  340. ),
  341. );
  342. return;
  343. } else {
  344. ///如果已完成所有检查,则直接提交
  345. submitDiagnosis(submitDiagnosisDataValue);
  346. }
  347. } catch (e) {
  348. logger.e('MedicalController createDiagnosis ex:', e);
  349. } finally {
  350. busy = false;
  351. }
  352. }
  353. @override
  354. void dispose() {
  355. print('MedicalController dispose');
  356. super.dispose();
  357. }
  358. /// 【TODO 接口需要变更】 心电
  359. Future<void> createHeart(
  360. String physicalExamNumber,
  361. String? keyValue,
  362. ) async {
  363. Map<String, dynamic> input = diagnosisDataValue;
  364. for (var entry in input.entries) {
  365. var key = entry.key;
  366. var value = entry.value;
  367. if (value != null) {
  368. Store.app.setBusy("提交中");
  369. if (['Heart', 'TwelveHeart'].contains(key) && value is Map) {
  370. value = await uploadData(value);
  371. }
  372. Store.app.busy = false;
  373. // diagnosisItems.add(
  374. // DiagnosisItem(
  375. // key: key,
  376. // diagnosisData: jsonEncode(value),
  377. // ),
  378. // );
  379. }
  380. print('$key: $value');
  381. }
  382. Map<String, dynamic> output = {};
  383. input.forEach((key, value) {
  384. value.forEach((innerKey, innerValue) {
  385. output[innerKey] = innerValue;
  386. });
  387. });
  388. var result = await _examManager.createExam(CreateExamRequest(
  389. key: keyValue ?? "HEIBasic",
  390. examData: jsonEncode(output),
  391. physicalExamNumber: physicalExamNumber,
  392. ));
  393. if (result == true) {
  394. Get.back();
  395. }
  396. }
  397. bool isUploaded(String url) {
  398. return url.startsWith('https://') || url.startsWith('http://');
  399. }
  400. Future<Map> uploadData(Map data) async {
  401. if (data['ECG_POINT'] != null && !isUploaded(data['ECG_POINT'])) {
  402. File ecgPointFile =
  403. await rpc.storage.writeStringToFile(data['ECG_POINT']);
  404. String? ecgPointUrl = await rpc.storage.uploadFile(ecgPointFile);
  405. data['ECG_POINT'] = ecgPointUrl ?? '';
  406. // ... 上传点集
  407. }
  408. if (data['ECG'] != null && !isUploaded(data['ECG'])) {
  409. // ... 上传图片
  410. /// 图片地址
  411. final imageFile = UploadUtils.convertBase64ToXFile(data['ECG']);
  412. String? imageUrl = await rpc.storage.upload(imageFile!);
  413. data['ECG'] = imageUrl ?? '';
  414. }
  415. if (data['ECG_POINT12'] != null && !isUploaded(data['ECG_POINT12'])) {
  416. File ecgPointFile =
  417. await rpc.storage.writeStringToFile(data['ECG_POINT12']);
  418. String? ecgPointUrl = await rpc.storage.uploadFile(ecgPointFile);
  419. data['ECG_POINT12'] = ecgPointUrl ?? '';
  420. // ... 上传点集
  421. }
  422. if (data['ECG12'] != null && !isUploaded(data['ECG12'])) {
  423. // ... 上传图片
  424. final imageFile = UploadUtils.convertBase64ToXFile(data['ECG12']);
  425. String? imageUrl = await rpc.storage.upload(imageFile!, fileType: "jpg");
  426. data['ECG12'] = imageUrl ?? '';
  427. }
  428. return data;
  429. }
  430. /// 体检 检查提交
  431. Future<void> createCheckup(
  432. String? physicalExamNumber,
  433. String? keyValue,
  434. ) async {
  435. Map<String, dynamic> input = diagnosisDataValue;
  436. Map<String, dynamic> output = {};
  437. input.forEach((key, value) {
  438. value.forEach((innerKey, innerValue) {
  439. output[innerKey] = innerValue;
  440. });
  441. });
  442. var result = await _examManager.createExam(CreateExamRequest(
  443. key: keyValue ?? "HEIBasic",
  444. examData: jsonEncode(output),
  445. physicalExamNumber: physicalExamNumber,
  446. ));
  447. if (result == true) {
  448. Get.back();
  449. }
  450. print(result);
  451. }
  452. ///提交历史记录
  453. Future<bool> _submitHistory() async {
  454. var existDatas =
  455. await recordDataCacheManager.getNoSubmitRecords(patientCode);
  456. var historyDatas = existDatas.where((element) => element.code != appDataId);
  457. if (historyDatas.isEmpty) {
  458. return false;
  459. }
  460. for (DiagnosisEntity data in historyDatas) {
  461. try {
  462. Map<String, dynamic> jsonData = jsonDecode(data.dataJson);
  463. var diagnosisItems =
  464. await recordDataCacheManager.convertDiagnosisDataToList(jsonData);
  465. SubmitDiagnosisRequest submitDiagnosisRequest = SubmitDiagnosisRequest(
  466. appDataId: data.code,
  467. patientCode: data.patientCode,
  468. diagnosisItems: diagnosisItems,
  469. diagnosisTime: data.updateTime ?? data.createTime,
  470. );
  471. bool submitResult;
  472. if (kIsOnline) {
  473. PatientDTO? patientInfo = await _patientManager.getDetail(
  474. data.patientCode,
  475. );
  476. if (patientInfo == null) {
  477. ///如果Server没有,则从本地读取
  478. var patientEntity =
  479. await db.repositories.patient.singleByCode(data.patientCode);
  480. if (patientEntity != null) {
  481. patientInfo =
  482. PatientDTO.fromJson(jsonDecode(patientEntity.dataJson));
  483. }
  484. }
  485. submitResult = await _diagnosisManager.syncPatientAndDiagnosisData(
  486. SyncPatientAndDiagnosisDataRequest(
  487. token: Store.user.token,
  488. patientCode: data.patientCode,
  489. patientName: patientInfo?.patientName ?? '',
  490. patientAddress: patientInfo?.patientAddress ?? '',
  491. patientGender: patientInfo?.patientGender ?? GenderEnum.Unknown,
  492. permanentResidenceAddress: patientInfo?.permanentResidenceAddress,
  493. phone: patientInfo?.phone,
  494. cardNo: patientInfo?.cardNo,
  495. nationality: patientInfo?.nationality,
  496. birthday: patientInfo?.birthday,
  497. crowdLabels: patientInfo?.crowdLabels,
  498. cardType: patientInfo?.cardType ?? CardTypeEnum.Identity,
  499. contractedDoctor: patientInfo?.contractedDoctor,
  500. appDataId: data.code,
  501. diagnosisTime: data.updateTime ?? data.createTime,
  502. diagnosisItems: diagnosisItems,
  503. ),
  504. );
  505. } else {
  506. submitResult = await _diagnosisManager
  507. .submitDiagnosisAsync(submitDiagnosisRequest);
  508. }
  509. OfflineDataSyncState state;
  510. if (submitResult) {
  511. state = OfflineDataSyncState.success;
  512. logger.i('提交历史记录:$patientCode 成功');
  513. } else {
  514. state = OfflineDataSyncState.fail;
  515. logger.i('提交历史记录:$patientCode 失败');
  516. }
  517. recordDataCacheManager.recordSyncStateChange(data.code, state: state);
  518. } catch (e) {
  519. logger.e('MedicalController _submitHistory ex:', e);
  520. }
  521. }
  522. return true;
  523. }
  524. }