controller.dart 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. import 'dart:convert';
  2. import 'dart:ui';
  3. import 'package:barcode/barcode.dart';
  4. import 'package:fis_jsonrpc/rpc.dart';
  5. import 'package:flutter/foundation.dart';
  6. import 'package:flutter/material.dart';
  7. import 'package:flutter/services.dart';
  8. import 'package:flutter_inappwebview/flutter_inappwebview.dart';
  9. import 'package:get/get.dart';
  10. import 'package:vitalapp/architecture/defines.dart';
  11. import 'package:vitalapp/architecture/storage/text_storage.dart';
  12. import 'package:vitalapp/architecture/utils/datetime.dart';
  13. import 'package:vitalapp/architecture/utils/prompt_box.dart';
  14. import 'package:vitalapp/global.dart';
  15. import 'package:vitalapp/managers/interfaces/exam.dart';
  16. import 'package:vitalapp/managers/interfaces/health_check_record.dart';
  17. import 'package:vitalapp/managers/interfaces/registration.dart';
  18. import 'package:vitalapp/pages/check/health_check_record/components/health_check.dart';
  19. import 'package:vitalapp/pages/check/health_check_record/state.dart';
  20. import 'package:vitalapp/pages/check/models/form.dart';
  21. import 'package:vitalapp/pages/medical_checkup_station/registration/controller.dart';
  22. import 'package:vitalapp/pages/medical_checkup_station/registration/state/list.dart';
  23. import 'package:vitalapp/pages/medical_checkup_station/usb_print/module/printer_info.dart';
  24. import 'package:vitalapp/rpc.dart';
  25. import 'package:vitalapp/store/store.dart';
  26. import 'package:webviewx/webviewx.dart';
  27. import 'package:barcode_image/barcode_image.dart';
  28. import 'package:flutter_svg/flutter_svg.dart';
  29. import 'controllers/list_controller.dart';
  30. class HealthCheckRecordController extends FControllerBase {
  31. late final ListController listController;
  32. /// 表格loading
  33. bool tableLoading = true;
  34. /// 当前表格页数
  35. int currPageIndex = 1;
  36. /// 预约列表的总数
  37. int appointmentModelListLength = 0;
  38. /// 体检列表
  39. List<ResidentModel> residentList = [];
  40. HealthCheckRecordController() {
  41. listController = ListController(this);
  42. }
  43. final state = HealthCheckRecordState();
  44. final _healthCheckRecordManager = Get.find<IHealthCehckRecordManager>();
  45. final _examManager = Get.find<IExamManager>();
  46. final registrationManager = Get.find<IRegistrationManager>();
  47. late String patientCode;
  48. PrinterInfo? printInfo;
  49. List<MenuItem> menuList = [
  50. MenuItem(label: "一般状况", value: 'ZZYBZK'),
  51. MenuItem(label: "辅助检查", value: 'FZJC'),
  52. MenuItem(label: "生活方式", value: 'SHFS'),
  53. MenuItem(label: "脏器及查体", value: 'ZQCT'),
  54. MenuItem(label: "现存主要健康问题", value: 'XCZYWT'),
  55. MenuItem(label: "住院及用药情况", value: 'ZYYYFMYGHYFJZS'),
  56. MenuItem(label: "健康评价及指导", value: 'JKPJJZD'),
  57. ];
  58. @override
  59. void onReady() {
  60. super.onReady();
  61. _initData();
  62. }
  63. _initData() {
  64. update(["contract_records"]);
  65. patientCode = Store.user.currentSelectPatientInfo?.code ?? '';
  66. _getExamRecordList();
  67. getRegisterInfoPage(keyword: patientCode);
  68. }
  69. Future<void> _getExamRecordList() async {
  70. try {
  71. var result =
  72. await _healthCheckRecordManager.getExamRecordList(patientCode);
  73. if (result != null) {
  74. state.examRecordDTOList = result;
  75. }
  76. } catch (e) {
  77. print(e);
  78. return;
  79. }
  80. }
  81. Future<void> updateExamByBatchNumberAsync(key, batchNumber, data) async {
  82. if (!kIsOnline) {
  83. // TODO: 暂不支持离线体检
  84. PromptBox.toast("请检查网络连接");
  85. return;
  86. }
  87. TextStorage template = TextStorage(
  88. fileName: key,
  89. directory: "patient/$patientCode/exam",
  90. );
  91. final result = await _examManager
  92. .updateExamByBatchNumberAsync(UpdateExamByBatchNumberRequest(
  93. key: key,
  94. batchNumber: batchNumber,
  95. examData: data,
  96. patientCode: patientCode,
  97. ));
  98. if (result ?? false) {
  99. try {
  100. template.save(data);
  101. PromptBox.toast('保存成功');
  102. await _getExamRecordList();
  103. } catch (err) {
  104. PromptBox.toast('保存失败');
  105. }
  106. }
  107. }
  108. ///读取静态Html
  109. Future<String> loadingLocalAsset() async {
  110. ///加载
  111. String htmlData = await rootBundle.loadString('assets/docs/examTable.html');
  112. print("加载数据完成 $htmlData");
  113. return htmlData;
  114. }
  115. Future<void> toExamDetailPage(int index, ExamRecordDTO dto) async {
  116. var initialData = await loadingLocalAsset();
  117. var examRecordDatas = state.examRecordDTOList[index].examRecordDatas;
  118. Map<String, dynamic> examData = {};
  119. examRecordDatas?.forEach((element) {
  120. examData.addAll(jsonDecode(element.examData ?? '{}'));
  121. });
  122. // 将数据转换为 JSON 字符串
  123. var jsonData = jsonEncode({
  124. "patientInfo": {
  125. "patientName": dto.patientName,
  126. },
  127. "examData": examData,
  128. "docterName": dto.examDoctor,
  129. "examTime":
  130. DataTimeUtils.formatDateString(dto.examTime ?? DateTime.now()),
  131. });
  132. _openExamDetail(initialData, jsonData);
  133. }
  134. Future<void> toCheckPage(
  135. ExamRecordDTO dto,
  136. List<ExamRecordDataDTO>? dataDTO,
  137. int index,
  138. ) async {
  139. Get.dialog(
  140. HealthCheckDialog(
  141. examRecord: dto,
  142. examRecordDatas: dataDTO,
  143. index: index,
  144. ),
  145. );
  146. }
  147. String getHealthCheckRecordType(String key) {
  148. switch (key) {
  149. case 'ZZYBZK':
  150. return '症状、一般状况';
  151. case 'SHFS':
  152. return '生活方式';
  153. case 'ZQCT':
  154. return '脏器、查体';
  155. case 'FZJC':
  156. return '辅助检查';
  157. case 'XCZYWT':
  158. return '现存主要问题';
  159. case 'ZYYYFMYGHYFJZS':
  160. return '住院、用药、非免疫规划预防接种史';
  161. case 'JKPJJZD':
  162. return '健康评价及指导';
  163. default:
  164. return "";
  165. }
  166. }
  167. String examStateTransition(ExamStateEnum state) {
  168. switch (state) {
  169. case ExamStateEnum.Unchecked:
  170. return "未体检";
  171. case ExamStateEnum.Invalid:
  172. return "已作废";
  173. case ExamStateEnum.Inspected:
  174. return "已体检";
  175. default:
  176. return "";
  177. }
  178. }
  179. MaterialColor examStateColors(ExamStateEnum state) {
  180. switch (state) {
  181. case ExamStateEnum.Unchecked:
  182. return Colors.grey;
  183. case ExamStateEnum.Invalid:
  184. return Colors.red;
  185. case ExamStateEnum.Inspected:
  186. return Colors.green;
  187. default:
  188. return Colors.blue;
  189. }
  190. }
  191. void _openExamDetail(
  192. String initialData,
  193. String jsonData,
  194. ) {
  195. // 将 JSON 字符串作为参数传递给 evaluateJavascript 方法
  196. Get.dialog(Column(
  197. children: [
  198. Container(
  199. width: 900,
  200. alignment: Alignment.centerRight,
  201. padding: const EdgeInsets.only(right: 20, top: 10),
  202. color: Colors.white,
  203. child: IconButton(
  204. onPressed: () {
  205. Get.back();
  206. },
  207. icon: const Icon(
  208. Icons.close,
  209. ),
  210. ),
  211. ),
  212. Expanded(
  213. child: Container(
  214. color: Colors.white,
  215. width: 900,
  216. height: double.maxFinite,
  217. child: _buildWebView(initialData, jsonData),
  218. ),
  219. ),
  220. ],
  221. ));
  222. }
  223. Widget _buildAppWebView(
  224. String initialData,
  225. String jsonData,
  226. ) {
  227. return InAppWebView(
  228. initialData: InAppWebViewInitialData(
  229. data: '<body style="padding:30px 100px;">$initialData </body>',
  230. mimeType: 'text/html',
  231. encoding: 'utf-8',
  232. ),
  233. onWebViewCreated: (controller) {
  234. controller.evaluateJavascript(source: "window.globalData = $jsonData;");
  235. },
  236. onConsoleMessage: (controller, consoleMessage) {
  237. // print(consoleMessage);
  238. },
  239. );
  240. }
  241. Widget _buildWebwebview(
  242. String initialData,
  243. String jsonData,
  244. ) {
  245. return WebViewX(
  246. height: MediaQuery.of(Get.context!).size.height,
  247. width: MediaQuery.of(Get.context!).size.width,
  248. initialSourceType: SourceType.html,
  249. initialContent:
  250. '<html><body style="padding:30px 100px;">$initialData </body></html>',
  251. javascriptMode: JavascriptMode.unrestricted,
  252. onWebResourceError: (p0) {},
  253. onWebViewCreated: (controller) async {
  254. await controller.evalRawJavascript(
  255. "window.globalData = $jsonData;",
  256. inGlobalContext: true,
  257. );
  258. },
  259. );
  260. // return FutureBuilder<String>(
  261. // future: _loadLocalHtml(context,
  262. // '<html><body style="padding: 100px;">${controller.state.templateContent}</body></html>'),
  263. // builder: (context, snapshot) {
  264. // if (!snapshot.hasData) {
  265. // return const Center(child: CircularProgressIndicator());
  266. // // return FCenter(child: FText("${i18nBook.common.loading.t}..."));
  267. // } else {
  268. // return WebViewX(
  269. // height: MediaQuery.of(Get.context!).size.height,
  270. // width: MediaQuery.of(Get.context!).size.width,
  271. // initialSourceType: SourceType.html,
  272. // initialContent:
  273. // '<html><body style="padding: 100px;">${controller.state.templateContent}</body></html>',
  274. // javascriptMode: JavascriptMode.unrestricted,
  275. // onWebResourceError: (p0) {},
  276. // onWebViewCreated: (controller) {
  277. // // webviewControllerManager.initWebviewController(controller);
  278. // },
  279. // );
  280. // }
  281. // },
  282. // );
  283. }
  284. Widget _buildWebView(
  285. String initialData,
  286. String jsonData,
  287. ) {
  288. if (kIsWeb) {
  289. return _buildWebwebview(initialData, jsonData);
  290. } else {
  291. return _buildAppWebView(initialData, jsonData);
  292. }
  293. }
  294. void getExamLabelsByExamNoAsync(ResidentModel rowData) async {
  295. List<String> barCodes = [];
  296. var labels = await registrationManager.getExamLabelsByExamNoAsync(
  297. physicalExamNumber: rowData.physicalExamNumber!);
  298. for (var element in labels.eaxmLabels!) {
  299. var barCode = await drawRedRect(element, rowData);
  300. if (barCode.isNotEmpty) {
  301. barCodes.add(barCode);
  302. }
  303. }
  304. if (barCodes.length > 0) {
  305. rpc.platform.printLabels(barCodes);
  306. return;
  307. }
  308. }
  309. Future<String> drawRedRect(
  310. HealthExamLabelDTO labelDto, ResidentModel rowData) async {
  311. const size = Size(360, 240);
  312. final svg = Barcode.code128().toSvg(labelDto.uniquedCode!, height: 80);
  313. final PictureInfo pictureInfo =
  314. await vg.loadPicture(SvgStringLoader(svg), null);
  315. var a = await _capturePainterToImage(
  316. MyPainter(pictureInfo, labelDto, rowData),
  317. BgPainter(pictureInfo),
  318. size);
  319. String base64Image = base64Encode(a!);
  320. print(base64Image);
  321. return base64Image;
  322. }
  323. /// 将CustomPainter绘制的内容转换为图片
  324. Future<Uint8List?> _capturePainterToImage(
  325. CustomPainter painter, CustomPainter bgPainter, Size size) async {
  326. final bounds = Offset.zero & size;
  327. // final bounds2 = Offset.zero & Size(size.width - 20, size.height - 20);
  328. final picture = PictureRecorder();
  329. final pictureCanvas = Canvas(picture);
  330. // 给Canvas设置绘制范围
  331. pictureCanvas.clipRect(bounds);
  332. painter.paint(pictureCanvas, size);
  333. // 在Canvas上进行绘制
  334. bgPainter.paint(pictureCanvas, size);
  335. // 结束绘制
  336. final recordedPicture = picture.endRecording();
  337. final image =
  338. await recordedPicture.toImage(size.width.toInt(), size.height.toInt());
  339. // 转换为字节数组
  340. final byteData = await image.toByteData(format: ImageByteFormat.png);
  341. final bytes = byteData?.buffer.asUint8List();
  342. return bytes;
  343. }
  344. Future<void> getRegisterInfoPage({
  345. int? pageSize = 10,
  346. int? pageIndex = 1,
  347. String? keyword = "",
  348. DateTime? startTime,
  349. DateTime? endTime,
  350. }) async {
  351. tableLoading = true;
  352. currPageIndex = pageIndex!;
  353. var result = await registrationManager.getRegisterInfoPageAsync(
  354. pageSize: pageSize,
  355. pageIndex: pageIndex,
  356. keyword: keyword,
  357. startTime: startTime,
  358. endTime: endTime,
  359. );
  360. List<ResidentModel> _residentList = [];
  361. if (result?.pageData != null) {
  362. for (RegisterInfoDTO i in result!.pageData!) {
  363. String statusStr = '';
  364. ExamStateEnum status = i.state;
  365. switch (status) {
  366. case ExamStateEnum.Unchecked:
  367. statusStr = "未体检";
  368. break;
  369. case ExamStateEnum.Invalid:
  370. statusStr = "已作废";
  371. break;
  372. case ExamStateEnum.Inspected:
  373. statusStr = "已体检";
  374. break;
  375. case ExamStateEnum.Reported:
  376. statusStr = "已报告";
  377. break;
  378. }
  379. _residentList.add(
  380. ResidentModel(
  381. name: i.name ?? '',
  382. idNumber: i.iDCardNo ?? '',
  383. code: i.code ?? '',
  384. homeAddress: i.adress,
  385. age: getAgeOfIdNumber(i.iDCardNo ?? ''),
  386. physicalExamNumber: i.physicalExamNumber,
  387. phone: i.phone,
  388. finishedExamKeys: i.finishedExamKeys,
  389. physicalExamStatus: statusStr,
  390. birthDay: i.birthday,
  391. sex: i.sex,
  392. batchNumber: i.batchNumber,
  393. ),
  394. );
  395. }
  396. appointmentModelListLength = result.totalCount;
  397. }
  398. residentList = _residentList;
  399. tableLoading = false;
  400. update(["HealCheckRecord"]);
  401. }
  402. String getAgeOfIdNumber(String idNumber) {
  403. if (idNumber.isEmpty) return '';
  404. final idCardRegex = RegExp(r'^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})(\d|X)$');
  405. final match = idCardRegex.firstMatch(idNumber);
  406. if (match != null) {
  407. final year = int.parse(match.group(2)!);
  408. final month = int.parse(match.group(3)!);
  409. final day = int.parse(match.group(4)!);
  410. final birthDate = DateTime(year, month, day);
  411. String age = DataTimeUtils.calculateAge(birthDate);
  412. return age;
  413. }
  414. return ''; // 返回一个默认值
  415. }
  416. }