controller.dart 20 KB

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