report_manager.dart 20 KB

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