report_manager.dart 19 KB

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