list.dart 19 KB

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