report_manager.dart 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. import 'dart:convert';
  2. import 'package:fis_common/index.dart';
  3. import 'package:fis_common/logger/logger.dart';
  4. import 'package:fis_jsonrpc/rpc.dart';
  5. import 'package:fis_jsonrpc/rpc.dart' as r;
  6. import 'package:fis_jsonrpc/rpc.dart';
  7. import 'package:fis_lib_report/report/report_template_document.dart';
  8. import 'package:fis_lib_report/report_info/report_info.dart';
  9. import 'package:fis_ui/index.dart';
  10. import 'package:flutter/material.dart';
  11. import 'package:flutter/services.dart';
  12. import 'package:flutter_sms/flutter_sms.dart';
  13. import 'package:fluwx/fluwx.dart';
  14. import 'package:get/get.dart';
  15. import 'package:intl/intl.dart';
  16. import 'package:url_launcher/url_launcher.dart';
  17. import 'package:vitalapp/architecture/utils/prompt_box.dart';
  18. import 'package:vitalapp/pages/measure_home/controller.dart';
  19. import 'package:vitalapp/pages/measure_home/view.dart';
  20. import 'package:vitalapp/pages/report_edit/controller.dart';
  21. import 'package:vitalapp/pages/report_edit/view.dart';
  22. import 'package:vitalapp/pages/report_edit/widgets/report_share_dialog.dart';
  23. import 'package:vitalapp/pages/report_preview/controller.dart';
  24. import 'package:vitalapp/pages/report_preview/view.dart';
  25. import 'package:vitalapp/pages/widgets/row_separator.dart';
  26. import 'package:vitalapp/rpc.dart';
  27. import 'interfaces/base_manager.dart';
  28. import 'interfaces/models/report_element_info.dart';
  29. import 'interfaces/models/report_tag_key.dart';
  30. import 'interfaces/report.dart';
  31. import 'package:fis_common/event/event_type.dart';
  32. import 'package:fis_i18n/i18n.dart';
  33. class ReportManager extends BaseManager implements IReportManager {
  34. final onEditReport = FEventHandler<String>();
  35. ///提交报告事件
  36. final onSubmitReport = FEventHandler<bool>();
  37. ///手机端提交报告
  38. final onMobileSubmitReport = FEventHandler<bool>();
  39. ///直接打印文档
  40. @override
  41. Future<void> directlyPrintPDF(String reportCode, String fileName) async {
  42. try {
  43. await _initPreviewReport(reportCode);
  44. try {} catch (e) {
  45. logger.e("print report error:", e);
  46. }
  47. } catch (e) {
  48. logger.e('InspectionReportController _initPreviewReport ex:', e);
  49. }
  50. }
  51. /// 直接打印图片
  52. @override
  53. Future<void> directlyPrintPDFJpges(
  54. List<String>? images, String fileName) async {
  55. try {} catch (e) {
  56. logger.e("print report error:", e);
  57. }
  58. }
  59. ///打印文件(通过下载地址)
  60. @override
  61. Future<void> directlyPrintPDFFile(String fileUrl, String fileName) async {
  62. try {} catch (e) {
  63. logger.e("print report error:", e);
  64. }
  65. }
  66. @override
  67. List<FReportElementInfo> convertReportData(String jsonData) {
  68. List<FReportElementInfo> result = [];
  69. final infos = jsonDecode(jsonData) as List<dynamic>;
  70. for (var element in infos) {
  71. result.add(
  72. FReportElementInfo(
  73. key: element['Key'],
  74. tagId: element['TagId'],
  75. name: element['Name'],
  76. value: element['Value'],
  77. ),
  78. );
  79. }
  80. return result;
  81. }
  82. ///语音识别
  83. Future<String> commitASRInfoAsync(String token, String extension) async {
  84. try {
  85. var audioConvertResult = await rpc.aSR.commitASRInfoAsync(
  86. CommitASRInfoRequest(
  87. token: token,
  88. url: token,
  89. fileType: extension,
  90. ),
  91. );
  92. return audioConvertResult.content ?? '';
  93. } catch (e) {
  94. logger.e('ReportEditController commitASRInfoAsync ex:', e);
  95. }
  96. return '';
  97. }
  98. ///直接下载图像PDF
  99. @override
  100. Future<void> directlyDownloadPDFJpges(
  101. List<String>? images, String fileName) async {
  102. try {} catch (e) {
  103. logger.e("export report error:", e);
  104. }
  105. }
  106. ///直接下载 PDF 文件,通过 URL
  107. @override
  108. Future<void> directlyDownloadPDFFile(String fileUrl, String fileName) async {
  109. try {} catch (e) {
  110. logger.e("export report error:", e);
  111. }
  112. }
  113. ///直接下载文档报告
  114. @override
  115. Future<void> directlyDownloadPDF(String reportCode, String fileName) async {
  116. try {
  117. await _initPreviewReport(reportCode);
  118. try {} catch (e) {
  119. logger.e("export report error:", e);
  120. }
  121. } catch (e) {
  122. logger.e('InspectionReportController _initPreviewReport ex:', e);
  123. }
  124. }
  125. ///获取默认模板
  126. @override
  127. Future<String> getReportTemplate() async {
  128. return '';
  129. }
  130. /// 打开报告分享窗口
  131. ///
  132. /// [reportUrlToShare] 待分享的报告code
  133. @override
  134. Future<bool> openReportShareDialog(String? reportToShare) async {
  135. if (reportToShare == null || reportToShare.isEmpty) {
  136. return false;
  137. }
  138. var reportUrlToShare =
  139. await rpc.report.findReportShareUrlAsync(FindReportShareUrlRequest(
  140. languageCode: 'zh-CN',
  141. reportCode: reportToShare,
  142. token: token,
  143. ));
  144. if (reportUrlToShare.isEmpty) {
  145. PromptBox.toast("报告分享地址为空");
  146. return false;
  147. }
  148. Get.dialog(
  149. ReportShareDialog(
  150. reportUrlToShare,
  151. reportToShare,
  152. ),
  153. );
  154. return true;
  155. }
  156. ///提交报告标签
  157. Future<bool> submitReportTags(
  158. String reportCode, List<String> selectedTags) async {
  159. try {
  160. final result =
  161. await rpc.report.modifyReportLabelsAsync(ModifyReportLabelsRequest(
  162. token: token,
  163. reportCode: reportCode,
  164. reportLabels: selectedTags,
  165. ));
  166. return result;
  167. } catch (e) {
  168. logger.e('AddReportTagController submitReportTags ex:', e);
  169. }
  170. return false;
  171. }
  172. ///初始化标签所需数据
  173. ///
  174. /// [reportType] 默认值远程诊断
  175. Future<Map<ReportTagKey, List<String>>> getReportTagDatas(
  176. String recordCode, [
  177. ReportTypeEnum? reportType,
  178. ]) async {
  179. Map<ReportTagKey, List<String>> allReportTags = {};
  180. try {
  181. final labels = await rpc.report.findReportLabelsAsync(
  182. FindReportLabelsRequest(
  183. token: token,
  184. recordCode: recordCode,
  185. reportType: reportType ?? ReportTypeEnum.RemoteDiagnosis,
  186. ),
  187. );
  188. labels.forEach((label) {
  189. final tagKey =
  190. ReportTagKey(label.reportLabelCode ?? '', label.labelName ?? '');
  191. if (!allReportTags.containsKey(tagKey)) {
  192. allReportTags[tagKey] = label.labelItems ?? [];
  193. } else {
  194. logger.e(
  195. 'AddReportTagController _initDatas contains duplicate labels :' +
  196. tagKey.code);
  197. }
  198. });
  199. } catch (e) {
  200. logger.e('ReportManager getReportTagDatas ex:', e);
  201. }
  202. return allReportTags;
  203. }
  204. @override
  205. Future<List<r.ReportDTO>> findReportsAsync(String recordCode) async {
  206. try {
  207. var reportInfo = await rpc.ultrasoundReport.vitalFindReportsAsync(
  208. FindReportsRequest(
  209. token: token,
  210. recordCode: recordCode,
  211. ),
  212. );
  213. return reportInfo;
  214. } catch (e) {
  215. logger.e('ReportManager findReportsAsync ex:$e');
  216. return [];
  217. }
  218. }
  219. ///获取报告提交内容信息Json
  220. String getMobileReportDatasJson({
  221. String? patientName,
  222. String? patientSex,
  223. String? age,
  224. List<String>? imageInfos,
  225. String? des,
  226. String? summary,
  227. String? creatorName,
  228. String? fullName,
  229. String? customDoctor,
  230. String? customOrganzation,
  231. String? deviceOrganzationName,
  232. }) {
  233. return "";
  234. }
  235. /// 格式化时间
  236. ///
  237. /// [time] 后端返回的时间
  238. @override
  239. String getTime(String time) {
  240. String stringTime = '';
  241. if (time.isNotEmpty) {
  242. stringTime = DateFormat("yyyy-MM-dd HH:mm:ss").format(
  243. DateTime.fromMillisecondsSinceEpoch(
  244. DateTime.tryParse(time)?.millisecondsSinceEpoch as int,
  245. ),
  246. );
  247. }
  248. return stringTime;
  249. }
  250. ///打开报告编辑页面
  251. @override
  252. Future<void> openReportEdit(
  253. String patientCode, {
  254. String consultationCode = '',
  255. String recordCode = '',
  256. String reportCode = '',
  257. int patientTab = 0,
  258. String referralRecordCode = '',
  259. }) async {
  260. _openReportEditPage(token, patientCode, referralRecordCode, reportCode,
  261. consultationCode, recordCode);
  262. }
  263. /// 根据扫查记录获取转诊扫查图像详情
  264. @override
  265. Future<RemedicalListResult> queryReferralRemedicalListByRecordInfoAsync(
  266. String recordCode, String referralRecordCode) async {
  267. try {
  268. final getRecordsPage =
  269. await rpc.remedical.queryReferralRemedicalListByRecordInfoAsync(
  270. QueryReferralExamReportRequest(
  271. token: token,
  272. recordCode: recordCode,
  273. referralRecordCode: referralRecordCode,
  274. ),
  275. );
  276. return getRecordsPage;
  277. } catch (e) {
  278. return RemedicalListResult();
  279. }
  280. }
  281. ///打开报告编辑页
  282. void _openReportEditPage(
  283. String token,
  284. String patientCode,
  285. String referralRecordCode,
  286. String reportCode,
  287. String consultationCode,
  288. String recordCode) {
  289. var arguments = {
  290. 'token': token,
  291. 'patientCode': patientCode,
  292. 'referralRecordCode': referralRecordCode,
  293. 'reportCode': reportCode,
  294. 'consultationCode': consultationCode,
  295. 'recordCode': recordCode,
  296. };
  297. Get.to(
  298. () => ReporteditPage(),
  299. binding: BindingsBuilder(() {
  300. Get.put(ReportEditController());
  301. }),
  302. arguments: arguments,
  303. );
  304. ///SP60全屏显示撰写报告
  305. // router.to(
  306. // RouteNames.Remedical.ReportEdit,
  307. // id: NavIds.HOME,
  308. // parameters: arguments,
  309. // );
  310. }
  311. ///进入测量页面
  312. void enterVidMeasurePage({
  313. String imageUrl = '',
  314. int imageindex = 0,
  315. String remedicalCode = '',
  316. String patientCode = '',
  317. String recordCode = '',
  318. String? remedicalAISelectedInfoCode = '',
  319. bool needRouterBack = false,
  320. bool reportPageEnter = false,
  321. }) {
  322. _openMeasurePage(
  323. patientCode,
  324. remedicalCode,
  325. recordCode,
  326. 0,
  327. remedicalAISelectedInfoCode,
  328. needRouterBack: needRouterBack,
  329. reportPageEnter: reportPageEnter,
  330. );
  331. }
  332. @override
  333. Future<RemedicalListResult> getRemedicalListByRecordInfoAsync(
  334. String recordCode) async {
  335. try {
  336. final result =
  337. await rpc.remoteUltrasound.vitalGetRemedicalListByRecordInfoAsync(
  338. QueryRecordRequest(
  339. token: token,
  340. recordCode: recordCode,
  341. ),
  342. );
  343. return result;
  344. } catch (e) {
  345. return RemedicalListResult();
  346. }
  347. }
  348. ///注册微信分享api
  349. @override
  350. void initFluwx() async {
  351. try {
  352. if (kIsMobile) {
  353. await registerWxApi(
  354. appId: "wx2167274095118ddb",
  355. doOnAndroid: true,
  356. doOnIOS: true,
  357. universalLink: "https://flyinsono.com/",
  358. );
  359. }
  360. } catch (e) {
  361. logger.e('ReportManager initFluwx', e);
  362. }
  363. }
  364. /// 获取测量图像
  365. @override
  366. Future<List<RemedicalMeasuredInfoDTO>> findRemedicalMeasuredInfoAsync(
  367. String recordCode, {
  368. BusinessTypeEnum businessTypeEnum = BusinessTypeEnum.RemoteDiagnosis,
  369. }) async {
  370. List<RemedicalMeasuredInfoDTO> remedicalMeasured = [];
  371. try {
  372. final result =
  373. await rpc.remoteUltrasound.vitalFindRemedicalMeasuredInfoAsync(
  374. FindRemedicalMeasuredInfoRequest(
  375. token: token,
  376. recordCode: recordCode,
  377. businessType: businessTypeEnum,
  378. ),
  379. );
  380. remedicalMeasured = result;
  381. } catch (e) {
  382. remedicalMeasured = [];
  383. printError(
  384. info: "findRemedicalMeasuredInfoAsync exception:" + e.toString());
  385. logger.e("findRemedicalMeasuredInfoAsync exception:", e);
  386. }
  387. return remedicalMeasured;
  388. }
  389. ///跟据reportCode获取报告详情
  390. @override
  391. Future<r.ReportDTO> findReportByCodeAsync(String reportCode) async {
  392. final reportInfo = await rpc.ultrasoundReport.vitalFindReportByCodeAsync(
  393. FindReportByCodeRequest(
  394. token: token,
  395. reportCode: reportCode,
  396. ),
  397. );
  398. return reportInfo;
  399. }
  400. ///手机端底部弹窗选择分享方式
  401. @override
  402. Future<bool> showBottomSheet(r.ReportDTO reportInfo) async {
  403. bool result = false;
  404. var reportCode = reportInfo.reportCode ?? '';
  405. final url = await findReportShareUrlAsync(reportCode);
  406. String smsContent = "【杏聆荟】${reportInfo.patientName}的检查报告已生成,可点击${url}查看报告。";
  407. smsContent = smsContent.replaceAll('#', '%23');
  408. await showModalBottomSheet(
  409. context: Get.context!,
  410. backgroundColor: Colors.white, //背景颜色
  411. shape: RoundedRectangleBorder(
  412. borderRadius: BorderRadius.only(
  413. topLeft: Radius.circular(8), topRight: Radius.circular(8))),
  414. isScrollControlled: false,
  415. isDismissible: true,
  416. builder: (BuildContext context) {
  417. return Column(
  418. mainAxisSize: MainAxisSize.min, // 设置最小的弹出
  419. children: [
  420. ListTile(
  421. leading: Icon(Icons.wechat),
  422. title: Text('朋友圈'),
  423. onTap: () async {
  424. _shareWX(url, reportInfo.patientName);
  425. Get.back();
  426. },
  427. ),
  428. const RowSeparator(),
  429. ListTile(
  430. leading: const Icon(Icons.message),
  431. title: const Text("短信分享"),
  432. onTap: () async {
  433. _sendSMS(smsContent, []);
  434. Get.back();
  435. },
  436. ),
  437. const RowSeparator(),
  438. ListTile(
  439. leading: const Icon(Icons.copy),
  440. title: const Text('复制链接'),
  441. onTap: () {
  442. if (FPlatform.isWindows || FPlatform.isMacOS) {
  443. rpc.platform.copyToClipboard(url);
  444. } else {
  445. Clipboard.setData(ClipboardData(text: url));
  446. }
  447. PromptBox.toast('复制成功');
  448. Get.back();
  449. },
  450. ),
  451. ],
  452. );
  453. });
  454. return result;
  455. }
  456. ///微信分享
  457. Future<void> _shareWX(String url, String? patientName) async {
  458. var result = await isWeChatInstalled;
  459. if (!result) {
  460. PromptBox.toast('无法打开微信 请检查是否安装了微信');
  461. return;
  462. }
  463. //分享后打开的图文连接
  464. String linkUrl = url;
  465. //分享的小图片
  466. String imageUrl =
  467. "https://flyinsono-bj-1300984704.cos.ap-beijing.myqcloud.com/FlyinsonoIcon.png";
  468. /// 分享到好友
  469. WeChatShareBaseModel model = WeChatShareWebPageModel(
  470. //链接
  471. linkUrl,
  472. //标题
  473. title: '超声报告',
  474. //描述
  475. description: patientName ?? " ",
  476. //小图
  477. thumbnail: WeChatImage.network(imageUrl),
  478. //微信消息
  479. scene: WeChatScene.SESSION,
  480. );
  481. final rst = await shareToWeChat(model);
  482. logger.i("ReportManager - share by wechat|$rst|$linkUrl");
  483. }
  484. void _sendSMS(String message, List<String> recipents) async {
  485. if (FPlatform.isAndroid) {
  486. // await launchUrl(Uri.parse('sms:?body=$message'));
  487. await launch('sms:?body=$message'); // 修正乱码问题
  488. } else {
  489. String _result = await sendSMS(message: message, recipients: recipents)
  490. .catchError((onError) {
  491. print(onError);
  492. });
  493. print(_result);
  494. }
  495. }
  496. @override
  497. Future<List<r.ReportDTO>> getReportInfoAsync(String recordCode) async {
  498. try {
  499. var result = await findReportsAsync(recordCode);
  500. return result;
  501. } catch (e) {
  502. logger.e('RealTimeConsultationReportController getFollowUpVisitInfo ex:' +
  503. e.toString());
  504. return [];
  505. }
  506. }
  507. ///获取分享报告链接
  508. @override
  509. Future<String> findReportShareUrlAsync(String reportCode) async {
  510. var reportUrlToShare = await rpc.report.findReportShareUrlAsync(
  511. FindReportShareUrlRequest(reportCode: reportCode, token: token));
  512. return reportUrlToShare;
  513. }
  514. ///打开报告预览页面
  515. @override
  516. Future<void> openReportPreviewPage(
  517. String recordCode,
  518. String referralRecordCode, {
  519. String reportCode = '',
  520. }) async {
  521. try {
  522. final reports = await findReportsAsync(recordCode);
  523. if (reports.isEmpty) {
  524. PromptBox.toast("暂无报告");
  525. return;
  526. }
  527. _openReportPreview(token, reportCode, recordCode);
  528. } catch (e) {
  529. printError(
  530. info: "getRemedicalListByRecordInfoAsync exception:" + e.toString());
  531. logger.e("getRemedicalListByRecordInfoAsync exception:", e);
  532. }
  533. }
  534. void _openReportPreview(String token, String reportCode, String recordCode) {
  535. var arguments = {
  536. 'token': token,
  537. 'reportCode': reportCode,
  538. 'recordCode': recordCode,
  539. };
  540. Get.to(
  541. () => ReportPreviewPage(),
  542. binding: BindingsBuilder(() {
  543. Get.put(ReportPreviewController());
  544. }),
  545. arguments: arguments,
  546. );
  547. }
  548. Future<void> _initPreviewReport(String reportCode) async {
  549. try {
  550. if (reportCode.isNotEmpty) {
  551. final reportInfo =
  552. await rpc.ultrasoundReport.vitalFindReportByCodeAsync(
  553. FindReportByCodeRequest(
  554. token: token,
  555. reportCode: reportCode,
  556. ),
  557. );
  558. final jsonStr = reportInfo.reportDatasJson;
  559. final measureJsonStr = reportInfo.reportMeasureDatasJson;
  560. if (jsonStr != null && jsonStr.isNotEmpty) {
  561. final jsonItems = jsonDecode(jsonStr) as List<dynamic>;
  562. if (measureJsonStr.isNotNullOrEmpty) {
  563. final measureJsonItems =
  564. jsonDecode(measureJsonStr!) as List<dynamic>;
  565. jsonItems.addAll(measureJsonItems);
  566. }
  567. var reportTemplate = reportInfo.reportTemplateJson;
  568. var reportTempalteDoc =
  569. ReportTemplateDocument.fromJson(jsonDecode(reportTemplate!));
  570. FReportInfo.instance.init(
  571. reportTempalteDoc,
  572. DateTime.now(),
  573. "",
  574. revoke: i18nBook.common.revoke.t,
  575. selectEntry: i18nBook.remedical.selectWord.t,
  576. selectImageHint: i18nBook.remedical.clickAndSelectImage.t,
  577. );
  578. FReportInfo.instance.fromJson(jsonItems);
  579. }
  580. }
  581. } catch (e) {
  582. print(e);
  583. }
  584. }
  585. void _openMeasurePage(
  586. String patientCode,
  587. String remedicalCode,
  588. String recordCode,
  589. int type,
  590. String? remedicalAISelectedInfoCode, {
  591. bool needRouterBack = false,
  592. bool reportPageEnter = false,
  593. }) async {
  594. var parasmeters = {
  595. "page": "measure",
  596. "patientCode": patientCode,
  597. "remedicalCode": remedicalCode,
  598. "recordCode": recordCode,
  599. "source": type.toString(),
  600. "token": token,
  601. "remedicalAISelectedInfoCode": remedicalAISelectedInfoCode ?? '',
  602. };
  603. /// parasmeters 添加参数 needBack
  604. parasmeters["needRouterBack"] = needRouterBack.toString();
  605. Get.to(
  606. () => MeasureHomePage(),
  607. binding: BindingsBuilder(() {
  608. Get.put(MeasureHomeController());
  609. }),
  610. arguments: parasmeters,
  611. );
  612. // await router.to(
  613. // RouteNames.Remedical.Measure,
  614. // id: (kIsMobile || (reportPageEnter && needRouterBack))
  615. // ? null
  616. // : NavIds.HOME,
  617. // parameters: parasmeters,
  618. // );
  619. }
  620. }
  621. // enum VidImageSource {
  622. // Remedical,
  623. // Consultation,
  624. // Laboratory,
  625. // AiResultModifier,
  626. // }