list.dart 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. import 'package:fis_common/event/event_type.dart';
  2. import 'package:fis_common/logger/logger.dart';
  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:vitalapp/architecture/utils/advance_debounce.dart';
  8. import 'package:vitalapp/architecture/utils/datetime.dart';
  9. import 'package:vitalapp/architecture/utils/prompt_box.dart';
  10. import 'package:vitalapp/components/appbar.dart';
  11. import 'package:vitalapp/components/button.dart';
  12. import 'package:vitalapp/components/table/table_column.dart';
  13. import 'package:vitalapp/managers/interfaces/patient.dart';
  14. import 'package:vitalapp/managers/interfaces/registration.dart';
  15. import 'package:vitalapp/pages/check/examination/controller.dart';
  16. import 'package:vitalapp/pages/check/examination/view.dart';
  17. import 'package:vitalapp/pages/medical/controller.dart';
  18. import 'package:vitalapp/pages/medical/views/exam_medical.dart';
  19. import 'package:vitalapp/pages/medical_checkup_station/registration/controller.dart';
  20. import 'package:vitalapp/pages/medical_checkup_station/registration/state/list.dart';
  21. import 'package:vitalapp/pages/medical_checkup_station/registration/widgets/form/index.dart';
  22. import 'package:vitalapp/pages/medical_checkup_station/registration/widgets/report/report_preview.dart';
  23. import 'package:vitalapp/pages/medical_checkup_station/usb_print/module/printer_info.dart';
  24. import 'package:vitalapp/pages/medical_checkup_station/usb_print/page/temp/print_preview.dart';
  25. import 'package:vitalapp/pages/widgets/overflow_tooltip_wrapper.dart';
  26. import 'package:vitalapp/store/store.dart';
  27. class RegistrationListController {
  28. late RegistrationController registrationController;
  29. RegistrationListController(RegistrationController controller) {
  30. registrationController = controller;
  31. }
  32. final state = ListState();
  33. final _registrationManager = Get.find<IRegistrationManager>();
  34. ResidentModel currentResident = ResidentModel(idNumber: "");
  35. List<String> _allExam = [
  36. "HEIBasic",
  37. "HEIUrinalysis",
  38. "HEIUltrasonic",
  39. "HEIBiochemical",
  40. "HEIBloodRoutine",
  41. "HEIECG",
  42. "HEITCMC"
  43. ];
  44. Map<String, dynamic> examData = {};
  45. List<String> examList() {
  46. List<String> missingData = _allExam
  47. .where((element) =>
  48. !(currentResident.finishedExamKeys ?? []).contains(element))
  49. .toList();
  50. return missingData;
  51. }
  52. /// 是否有健康检测的页面权限
  53. bool _hasHealthMonitor() {
  54. bool hasAuth = false;
  55. Store.user.menuPermissionList?.forEach((element) {
  56. if (element.code == "JKJC") {
  57. hasAuth = true;
  58. }
  59. });
  60. return hasAuth;
  61. }
  62. Future<void> getRegisterInfoPage({
  63. int? pageSize = 10,
  64. int? pageIndex = 1,
  65. String? keyword = "",
  66. }) async {
  67. registrationController.tableLoading = true;
  68. registrationController.currPageIndex = pageIndex!;
  69. var result = await registrationController.registrationManager
  70. .getRegisterInfoPageAsync(
  71. pageSize: pageSize,
  72. pageIndex: pageIndex,
  73. keyword: keyword,
  74. startTime: state.startTime,
  75. endTime: state.endTime,
  76. );
  77. List<ResidentModel> _residentList = [];
  78. if (result?.pageData != null) {
  79. for (RegisterInfoDTO i in result!.pageData!) {
  80. String statusStr = '';
  81. ExamStateEnum status = i.state;
  82. switch (status) {
  83. case ExamStateEnum.Unchecked:
  84. statusStr = "未体检";
  85. break;
  86. case ExamStateEnum.Invalid:
  87. statusStr = "已作废";
  88. break;
  89. case ExamStateEnum.Inspected:
  90. statusStr = "体检中";
  91. break;
  92. case ExamStateEnum.Reported:
  93. statusStr = "已报告";
  94. break;
  95. }
  96. _residentList.add(
  97. ResidentModel(
  98. name: i.name ?? '',
  99. idNumber: i.iDCardNo ?? '',
  100. code: i.code ?? '',
  101. homeAddress: i.adress,
  102. age: getAgeOfIdNumber(i.iDCardNo ?? ''),
  103. physicalExamNumber: i.physicalExamNumber,
  104. phone: i.phone,
  105. finishedExamKeys: i.finishedExamKeys,
  106. physicalExamStatus: statusStr,
  107. birthDay: i.birthday?.toLocal(),
  108. sex: i.sex,
  109. batchNumber: i.batchNumber,
  110. resultsAndSuggestions: i.resultsAndSuggestions,
  111. createdDoctorName: i.createdDoctorName,
  112. physicalExamTime: i.updateTime,
  113. ),
  114. );
  115. }
  116. registrationController.appointmentModelListLength = result.totalCount;
  117. }
  118. registrationController.residentList = _residentList;
  119. registrationController.tableLoading = false;
  120. registrationController.update(["registration_table"]);
  121. registrationController.update(["registration_table_pagination"]);
  122. }
  123. String getAgeOfIdNumber(String idNumber) {
  124. if (idNumber.isEmpty) return '';
  125. final idCardRegex = RegExp(r'^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})(\d|X)$');
  126. final match = idCardRegex.firstMatch(idNumber);
  127. if (match != null) {
  128. final year = int.parse(match.group(2)!);
  129. final month = int.parse(match.group(3)!);
  130. final day = int.parse(match.group(4)!);
  131. final birthDate = DateTime(year, month, day);
  132. String age = DataTimeUtils.calculateAge(birthDate);
  133. return age;
  134. }
  135. return ''; // 返回一个默认值
  136. }
  137. /// 登记列表表头
  138. List<TableColumn<ResidentModel>> buildTableColumns(
  139. BuildContext context,
  140. bool? isVital,
  141. ) {
  142. /// 是否是一体机
  143. bool isIntegralDesk = isVital ?? false;
  144. var textStyle = TextStyle(
  145. fontSize: 18,
  146. overflow: TextOverflow.ellipsis,
  147. fontFamily: "NotoSansSC",
  148. );
  149. return <TableColumn<ResidentModel>>[
  150. TableColumn<ResidentModel>(
  151. headerText: "体检号",
  152. flex: 4,
  153. render: (rowData, index) => Container(
  154. padding: const EdgeInsets.symmetric(vertical: kIsWeb ? 10 : 16),
  155. child: OverflowTooltipWrapper(
  156. child: Text(
  157. rowData.physicalExamNumber ?? '',
  158. style: textStyle,
  159. ),
  160. ),
  161. ),
  162. ),
  163. TableColumn<ResidentModel>(
  164. flex: 3,
  165. headerText: "姓名",
  166. render: (rowData, index) => Center(
  167. child: Text(
  168. rowData.name ?? '',
  169. style: textStyle,
  170. ),
  171. ),
  172. ),
  173. if (isIntegralDesk)
  174. TableColumn<ResidentModel>(
  175. headerText: "年龄",
  176. flex: 2,
  177. render: (rowData, index) => Center(
  178. child: Text(
  179. rowData.age != null ? rowData.age.toString() : "",
  180. style: textStyle,
  181. ),
  182. ),
  183. ),
  184. TableColumn<ResidentModel>(
  185. headerText: "身份证号",
  186. flex: 6,
  187. render: (rowData, index) => Center(
  188. child: Text(
  189. rowData.idNumber,
  190. style: textStyle,
  191. ),
  192. ),
  193. ),
  194. TableColumn<ResidentModel>(
  195. headerText: "手机号",
  196. flex: 4,
  197. render: (rowData, index) => Center(
  198. child: Text(
  199. rowData.phone == "" ? "-" : rowData.phone ?? '-',
  200. style: textStyle,
  201. ),
  202. ),
  203. ),
  204. TableColumn<ResidentModel>(
  205. headerRender: () {
  206. return Center(
  207. child: Padding(
  208. padding: EdgeInsets.only(right: 15),
  209. child: Text(
  210. "体检时间",
  211. style: TextStyle(
  212. fontSize: 20,
  213. ),
  214. ),
  215. ),
  216. );
  217. },
  218. flex: 3,
  219. render: (rowData, index) => Center(
  220. child: Text(
  221. rowData.physicalExamTime == null
  222. ? ""
  223. : DataTimeUtils.formatDateString(rowData.physicalExamTime!),
  224. style: textStyle,
  225. ),
  226. ),
  227. ),
  228. if (isIntegralDesk)
  229. TableColumn<ResidentModel>(
  230. headerText: "责任医生",
  231. // maxWidth: 120,
  232. flex: 3,
  233. render: (rowData, index) => Center(
  234. child: Text(
  235. rowData.createdDoctorName ?? "",
  236. style: textStyle,
  237. ),
  238. ),
  239. ),
  240. if (!isIntegralDesk)
  241. TableColumn<ResidentModel>(
  242. headerRender: () {
  243. return Center(
  244. child: Padding(
  245. padding: EdgeInsets.only(right: 15),
  246. child: Text(
  247. "体检状态",
  248. style: TextStyle(
  249. fontSize: 20,
  250. ),
  251. ),
  252. ),
  253. );
  254. },
  255. flex: 3,
  256. render: (rowData, index) => Center(
  257. child: Text(
  258. rowData.physicalExamStatus ?? "",
  259. style: textStyle,
  260. ),
  261. ),
  262. ),
  263. TableColumn<ResidentModel>(
  264. headerText: "操作",
  265. flex: 7,
  266. render: (rowData, index) => FittedBox(
  267. child: Row(
  268. mainAxisAlignment: MainAxisAlignment.center,
  269. children: [
  270. if (!kIsWeb && !isIntegralDesk) ...[
  271. _buildPrint(context, rowData),
  272. ],
  273. _buildEditRegistration(rowData),
  274. if (kIsWeb) _buildEditingExcepting(rowData),
  275. _buildHealthCheck(rowData, isIntegralDesk),
  276. _buildShowReport(rowData),
  277. ],
  278. ),
  279. ),
  280. ),
  281. ];
  282. }
  283. /// 打印按钮
  284. Widget _buildPrint(BuildContext context, ResidentModel rowData) {
  285. return TextButton(
  286. onPressed: () async {
  287. advanceDebounce(() async {
  288. Store.app.setBusy("加载中...");
  289. try {
  290. await registrationController.getExamLabelsByExamNoAsync(rowData);
  291. } catch (e) {
  292. print(e);
  293. }
  294. Store.app.busy = false;
  295. Get.dialog(
  296. Center(
  297. child: Container(
  298. width: MediaQuery.of(context).size.width * 0.7,
  299. height: MediaQuery.of(context).size.height,
  300. child: Center(
  301. child: PrintPreview(
  302. imageList: registrationController.barCodeList,
  303. printInfo:
  304. registrationController.printInfo ?? PrinterInfo(),
  305. ),
  306. ),
  307. ),
  308. ),
  309. );
  310. }, "print label", 3000);
  311. },
  312. child: const Text(
  313. "打印标签",
  314. style: TextStyle(fontSize: 18),
  315. ),
  316. );
  317. }
  318. /// 编辑按钮
  319. Widget _buildEditRegistration(ResidentModel rowData) {
  320. return TextButton(
  321. onPressed: () async {
  322. final RegistrationInfoModel? patient = RegistrationInfoModel(
  323. patientName: rowData.name,
  324. phone: rowData.phone,
  325. patientAddress: rowData.homeAddress,
  326. cardNo: rowData.idNumber,
  327. code: rowData.code,
  328. birthday: rowData.birthDay,
  329. patientGender: RegistrationInfoModel.getGenderEnum(rowData.sex),
  330. );
  331. final RegistrationInfoModel? result =
  332. await Get.dialog<RegistrationInfoModel>(
  333. RegistrationFormDialog(
  334. patient: patient,
  335. isEdit: true,
  336. ),
  337. barrierDismissible: false,
  338. );
  339. registrationController.formController.editResident(result);
  340. print(result);
  341. },
  342. child: const Text(
  343. "编辑",
  344. style: TextStyle(fontSize: 18),
  345. ),
  346. );
  347. }
  348. /// 健康指导
  349. Widget _buildEditingExcepting(ResidentModel rowData) {
  350. return TextButton(
  351. onPressed: () {
  352. state.resultsAndSuggestions = rowData.resultsAndSuggestions;
  353. Get.dialog(
  354. Dialog(
  355. backgroundColor: Colors.white, // 设置对话框背景颜色为白色
  356. child: Container(
  357. padding: EdgeInsets.all(16.0),
  358. child: Stack(
  359. children: [
  360. Column(
  361. children: [
  362. SizedBox(
  363. height: 20,
  364. ),
  365. Expanded(
  366. child: TextField(
  367. expands: true,
  368. maxLines: null,
  369. controller: TextEditingController(
  370. text: rowData.resultsAndSuggestions,
  371. ),
  372. decoration: InputDecoration(
  373. hintText: '请输入健康指导信息...',
  374. border: InputBorder.none,
  375. ),
  376. onChanged: (value) {
  377. state.resultsAndSuggestions = value;
  378. },
  379. ),
  380. ),
  381. ElevatedButton(
  382. onPressed: () async {
  383. final result = await _registrationManager
  384. .updateResultsAndSuggestionsAsync(
  385. rowData.code ?? "",
  386. state.resultsAndSuggestions ?? '');
  387. if (result) {
  388. rowData.resultsAndSuggestions =
  389. state.resultsAndSuggestions; // 内存更新
  390. Get.back(); // Close the dialog
  391. } else {
  392. PromptBox.toast("保存失败");
  393. }
  394. },
  395. child: Text(
  396. '提交',
  397. style: TextStyle(fontSize: 18),
  398. ),
  399. ),
  400. ],
  401. ),
  402. Positioned(
  403. top: 1.0,
  404. right: 1.0,
  405. child: IconButton(
  406. icon: Icon(Icons.close),
  407. onPressed: () {
  408. Get.back();
  409. },
  410. ),
  411. ),
  412. ],
  413. ),
  414. ),
  415. ),
  416. barrierDismissible:
  417. false, // Prevent dialog from closing on outside tap
  418. );
  419. },
  420. child: const Text(
  421. "健康指导",
  422. style: TextStyle(fontSize: 18),
  423. ));
  424. }
  425. /// 查看报告
  426. Widget _buildShowReport(ResidentModel rowData) {
  427. return TextButton(
  428. onPressed: () {
  429. advanceDebounce(
  430. () async {
  431. List<ReportDTO2>? reportList = await registrationController
  432. .registrationManager
  433. .getVitalReportInfoAsync(
  434. physicalExamNumber: rowData.physicalExamNumber ?? '',
  435. );
  436. ReportDTO2? report = reportList
  437. ?.firstWhereOrNull((element) => element.key == "Part");
  438. if (report != null) {
  439. logger.i("show report:${report.code}");
  440. Get.dialog(
  441. ReportPreview(
  442. pdfUrl: report.previewInfo?.fileToken ?? "",
  443. ),
  444. );
  445. } else {
  446. PromptBox.toast("暂无报告");
  447. }
  448. },
  449. "show report preview",
  450. );
  451. },
  452. child: const Text(
  453. "查看报告",
  454. style: TextStyle(fontSize: 18),
  455. ),
  456. );
  457. }
  458. Future<void> _setCurrentSelectPatientInfo(ResidentModel rowData) async {
  459. final _patientManager = Get.find<IPatientManager>();
  460. final PatientDTO? dto = await _patientManager.getDetail(rowData.idNumber);
  461. if (dto != null) {
  462. Store.user.currentSelectPatientInfo = dto;
  463. } else {
  464. Store.user.currentSelectPatientInfo = PatientDTO(
  465. cardNo: rowData.idNumber,
  466. patientName: rowData.name,
  467. code: rowData.idNumber,
  468. patientAddress: rowData.homeAddress,
  469. );
  470. }
  471. }
  472. void _examDialog(ResidentModel rowData, bool isIntegralDesk) async {
  473. final FEventHandler<bool> onSubmitEvent = FEventHandler<bool>();
  474. /// 需要检测页面回调数据
  475. Get.put(MedicalController());
  476. Get.dialog(
  477. Scaffold(
  478. body: Stack(
  479. children: [
  480. ExaminationPage(
  481. idCard: rowData.idNumber,
  482. onSubmitEvent: onSubmitEvent,
  483. physicalExamNumber: rowData.physicalExamNumber ?? '',
  484. ),
  485. Positioned(
  486. left: 16,
  487. bottom: 16,
  488. child: _buildMedicalButton(),
  489. )
  490. ],
  491. ),
  492. appBar: VAppBar(
  493. titleText: "体检",
  494. actions: [
  495. TextButton.icon(
  496. onPressed: () async {
  497. onSubmitEvent.emit(this, true);
  498. },
  499. label: Text(
  500. '保存',
  501. style: TextStyle(fontSize: 20, color: Colors.white),
  502. ),
  503. icon: Icon(Icons.save, size: 32, color: Colors.white),
  504. ),
  505. const SizedBox(width: 8),
  506. ],
  507. iconBack: () {
  508. onSubmitEvent.emit(this, true);
  509. Get.delete<MedicalController>();
  510. },
  511. ),
  512. ),
  513. );
  514. }
  515. Widget _buildMedicalButton() {
  516. if (_hasHealthMonitor()) {
  517. return Container(
  518. width: 214,
  519. margin: EdgeInsets.only(right: 10),
  520. child: VButton(
  521. onTap: () {
  522. Get.dialog<MedicalController>(
  523. Scaffold(
  524. body: ExamMedicalPage(
  525. isHealthCheck: true,
  526. ),
  527. appBar: VAppBar(
  528. titleText: "检测",
  529. ),
  530. ),
  531. );
  532. },
  533. child: Text(
  534. '检测',
  535. style: TextStyle(fontSize: 26),
  536. ),
  537. ),
  538. );
  539. }
  540. return SizedBox();
  541. }
  542. /// 体检
  543. Widget _buildHealthCheck(ResidentModel rowData, bool isIntegralDesk) {
  544. return TextButton(
  545. onPressed: () {
  546. advanceDebounce(
  547. () {
  548. if (Get.isRegistered<ExaminationController>()) {
  549. Get.delete<ExaminationController>();
  550. }
  551. Get.put(
  552. ExaminationController(
  553. patientCode: rowData.idNumber,
  554. batchNumber: rowData.batchNumber ?? '',
  555. ),
  556. );
  557. _setCurrentSelectPatientInfo(rowData);
  558. _examDialog(rowData, isIntegralDesk);
  559. },
  560. "HealthCheck",
  561. );
  562. },
  563. child: const Text(
  564. "填表",
  565. style: TextStyle(fontSize: 18),
  566. ),
  567. );
  568. }
  569. }