controller.dart 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. import 'package:fis_jsonrpc/rpc.dart';
  2. import 'package:flutter/material.dart';
  3. import 'package:get/get.dart';
  4. import 'package:vitalapp/architecture/defines.dart';
  5. import 'package:vitalapp/architecture/utils/prompt_box.dart';
  6. import 'package:vitalapp/architecture/values/features.dart';
  7. import 'package:vitalapp/components/alert_dialog.dart';
  8. import 'package:vitalapp/global.dart';
  9. import 'package:vitalapp/managers/interfaces/device.dart';
  10. import 'package:vitalapp/managers/interfaces/models/device.dart';
  11. import 'package:vitalapp/managers/interfaces/patient.dart';
  12. import 'package:vitalapp/pages/controllers/crowd_labels.dart';
  13. import 'package:vitalapp/pages/controllers/home_nav_mixin.dart';
  14. import 'package:vitalapp/pages/patient/bluetooth_card_reader/view.dart';
  15. import 'package:vitalapp/pages/patient/card_reader/index.dart';
  16. import 'package:vitalapp/pages/patient/create/state.dart';
  17. import 'package:vitalapp/pages/patient/list/controller.dart';
  18. import 'package:vitalapp/store/store.dart';
  19. import 'package:vnote_device_plugin/consts/types.dart';
  20. import 'package:fis_common/logger/logger.dart';
  21. import '../../facial_recognition/index.dart';
  22. import '../../id_card_scan/index.dart';
  23. class CreatePatientController extends FControllerBase with HomeNavMixin {
  24. final _patientManager = Get.find<IPatientManager>();
  25. final crowdLabelsController = Get.find<CrowdLabelsController>();
  26. final _patientListController = Get.find<PatientListController>();
  27. final _deviceManager = Get.find<IDeviceManager>();
  28. final state = CreatePatientState();
  29. @override
  30. Future<void> onLoad() {
  31. final params = Get.parameters;
  32. if (params.containsKey("from")) {
  33. if (params["from"] == "list") {
  34. state.isCreateOnly = true;
  35. }
  36. }
  37. return super.onLoad();
  38. }
  39. /// 保存档案,调整签约
  40. void gotoSignContract() async {
  41. setBusy("正在保存...");
  42. String? code;
  43. try {
  44. code = await _submitForm();
  45. if (code == null) {
  46. busy = false;
  47. PromptBox.toast("保存失败");
  48. return;
  49. }
  50. // final code = "13B95A2B2790464BBFD9B30A71F15C95";
  51. busy = false;
  52. Future.delayed(
  53. const Duration(milliseconds: 800),
  54. () {
  55. state.reset(); // 重置状态
  56. },
  57. );
  58. } finally {
  59. if (code != null) {
  60. Get.toNamed(
  61. "/contract/package_list",
  62. parameters: {"patientCode": code},
  63. );
  64. }
  65. }
  66. // Get.find<HomeController>().switchNavByName("/patient/list");
  67. // Future.delayed(
  68. // const Duration(milliseconds: 800),
  69. // () {
  70. // // TODO:
  71. // Get.find<PatientListController>().gotoDetail(code);
  72. // busy = false;
  73. // },
  74. // );
  75. }
  76. /// 存为档案,调整到档案详情
  77. void saveAndBack() async {
  78. final validateMsg = await _validateForm();
  79. if (validateMsg != null) {
  80. toast(validateMsg);
  81. return null;
  82. }
  83. final code = await _submitForm();
  84. if (code == null) {
  85. busy = false;
  86. logger.e('CreatePatientController saveAndBack code is null');
  87. PromptBox.toast("保存失败");
  88. return;
  89. } else {
  90. busy = false;
  91. await queryIsNeedFaceInput();
  92. Future.delayed(
  93. ///这个800ms不能移除,移除后会导致建档失败
  94. const Duration(milliseconds: 800),
  95. () {
  96. state.reset(); // 重置状态
  97. busy = false;
  98. PromptBox.toast("保存成功");
  99. _patientListController.reloadList();
  100. },
  101. );
  102. }
  103. ///不跳转到详情页
  104. // Get.find<HomeController>().switchNavByName("/patient/list");
  105. // Get.put(PatientListController());
  106. Future.delayed(
  107. ///这个800ms不能移除,移除后会导致建档失败
  108. const Duration(milliseconds: 800),
  109. () {
  110. Get.find<PatientListController>().gotoDetail(code);
  111. busy = false;
  112. },
  113. );
  114. }
  115. /// 询问是否需要人脸录入
  116. Future<void> queryIsNeedFaceInput() async {
  117. /// 拥有人脸权限并且不是身份证扫码建档
  118. if (Store.user.hasFeature(FeatureKeys.FaceRecognition) && kIsOnline) {
  119. await Get.dialog(VAlertDialog(
  120. title: '提示',
  121. content: Container(
  122. margin: const EdgeInsets.only(bottom: 20),
  123. child: const Text(
  124. '是否需要采集人像,采集人像后可以使用人脸识别功能',
  125. style: TextStyle(fontSize: 20),
  126. textAlign: TextAlign.center,
  127. ),
  128. ),
  129. confirmLabel: '采集',
  130. cancelLabel: '暂不采集',
  131. showCancel: true,
  132. onConfirm: () async {
  133. await onFaceEntryClicked();
  134. Get.back();
  135. },
  136. onCanceled: () {
  137. Get.back();
  138. },
  139. ));
  140. }
  141. }
  142. Future<DeviceModel?> getDevice(String type) async {
  143. List<DeviceModel> devices = await _deviceManager.getDeviceList();
  144. return devices.firstWhereOrNull((element) => element.type == type);
  145. }
  146. /// 点击读卡事件
  147. void onReadCardClicked() async {
  148. final DeviceModel? device = await getDevice(DeviceTypes.IC_READER);
  149. final CardReaderResult? result;
  150. if (device != null) {
  151. // return;
  152. result = await Get.dialog<CardReaderResult>(
  153. const BluetoothCardReaderDialog(),
  154. );
  155. } else {
  156. result = await Get.dialog<CardReaderResult>(
  157. const CardReaderDialog(),
  158. );
  159. }
  160. if (result != null && result.success) {
  161. PromptBox.toast("读取成功");
  162. state.cardNo = result.cardNo; // 回填身份证号
  163. state.name = result.name; // 回填姓名
  164. state.gender = result.gender; // 回填性别
  165. state.nation = result.nation; // 回填民族
  166. state.birthday = result.birthday; // 回填出生日期
  167. state.censusRegister = result.address; // 回填户籍地址
  168. if (state.isSyncAddresses) {
  169. state.address = result.address; // 回填现住地址
  170. }
  171. } else {
  172. print("读卡取消");
  173. }
  174. }
  175. /// 点击身份识别建档(拍摄扫描身份证)
  176. void onIdCardScanClicked() async {
  177. Store.user.isShowUserCard = false;
  178. final IdCardScanResult? result = await Get.to<IdCardScanResult>(
  179. () => const IdCardScanPage(),
  180. );
  181. if (result != null && result.success) {
  182. PromptBox.toast("身份证信息识别成功");
  183. PatientBaseDTO patientInfo = result.patientBaseDTO;
  184. state.cardNo = patientInfo.cardNo ?? ""; // 回填身份证号
  185. state.name = patientInfo.patientName ?? ""; // 回填姓名
  186. state.gender = patientInfo.patientGender; // 回填性别
  187. state.nation = patientInfo.nationality ?? ""; // 回填民族
  188. state.birthday = patientInfo.birthday; // 回填出生日期
  189. state.censusRegister = patientInfo.patientAddress ?? ""; // 回填户籍地址
  190. if (state.isSyncAddresses) {
  191. state.address = patientInfo.patientAddress ?? ""; // 回填现住地址
  192. }
  193. } else {
  194. print("识别取消");
  195. }
  196. Store.user.isShowUserCard = true;
  197. }
  198. /// 点击人脸识别
  199. void onFaceIdLoginClicked() async {
  200. Store.user.isShowUserCard = false;
  201. final FaceRecognitionResult? result = await Get.to<FaceRecognitionResult>(
  202. () => const FacialRecognitionPage(
  203. mode: FacialRecognitionMode.faceRecognition,
  204. ),
  205. );
  206. if (result != null && result.success) {
  207. final patient = result.patientInfo;
  208. PromptBox.toast('人脸识别成功,姓名:${patient.patientName}');
  209. // PromptBox.toast("录入成功");
  210. final patientDTO = PatientDTO(
  211. code: patient.cardNo,
  212. cardNo: patient.cardNo,
  213. patientName: patient.patientName,
  214. nationality: patient.nationality,
  215. patientGender: patient.patientGender,
  216. birthday: patient.birthday,
  217. patientAddress: patient.patientAddress,
  218. );
  219. Store.user.currentSelectPatientInfo = patientDTO;
  220. Get.find<PatientListController>().gotoDetail(patientDTO.code!);
  221. } else {
  222. print("识别取消");
  223. }
  224. Store.user.isShowUserCard = true;
  225. }
  226. /// 点击录入人脸
  227. Future<void> onFaceEntryClicked() async {
  228. final PatientDTO patientInfo = PatientDTO(
  229. patientName: state.name,
  230. phone: state.phoneNo,
  231. cardNo: state.cardNo,
  232. nationality: state.nation,
  233. birthday: state.birthday,
  234. cardType: state.cardType,
  235. patientGender: state.gender,
  236. code: state.cardNo,
  237. );
  238. bool? result = await Get.to<bool>(
  239. () => FacialRecognitionPage(
  240. mode: FacialRecognitionMode.faceInput,
  241. patientInfo: patientInfo,
  242. ),
  243. );
  244. if (result != null && result) {
  245. PromptBox.toast('人脸数据存入成功');
  246. }
  247. }
  248. /// 处理 “同户籍地址” 勾选变更事件
  249. void onSyncAddressCheckChanged(bool isChecked) {
  250. state.isSyncAddresses = isChecked;
  251. if (isChecked) {
  252. // 同步户籍地址到现住地址
  253. state.address = state.censusRegister;
  254. } else {
  255. state.address = "";
  256. }
  257. }
  258. /// 处理户籍地址变更
  259. void onCensusRegisterChanged(String value) {
  260. state.censusRegister = value;
  261. if (state.isSyncAddresses) {
  262. state.address = value;
  263. }
  264. }
  265. Future<String?> _submitForm() async {
  266. setBusy("正在保存...");
  267. final crowdLabelCodes = crowdLabelsController.state.selectedCodes;
  268. final request = CreatePatientRequest(
  269. patientName: state.name,
  270. phone: state.phoneNo,
  271. patientGender: state.gender,
  272. nationality: state.nation,
  273. birthday: state.birthday?.toUtc(),
  274. cardType: state.cardType,
  275. cardNo: state.cardNo,
  276. patientAddress: state.address,
  277. permanentResidenceAddress: state.censusRegister,
  278. crowdLabels: crowdLabelCodes,
  279. );
  280. final result = await _patientManager.create(request);
  281. return result;
  282. }
  283. bool validateIDCard(String idCard) {
  284. // 校验身份证号码长度
  285. if (idCard.length != 18) {
  286. return false;
  287. }
  288. // 校验前17位是否为数字
  289. String idCard17 = idCard.substring(0, 17);
  290. if (!isNumeric(idCard17)) {
  291. return false;
  292. }
  293. // 校验最后一位校验码
  294. String checkCode = getCheckCode(idCard17);
  295. if (idCard[17].toUpperCase() != checkCode) {
  296. return false;
  297. }
  298. return true;
  299. }
  300. bool isNumeric(String str) {
  301. if (str.isEmpty) {
  302. return false;
  303. }
  304. return double.tryParse(str) != null;
  305. }
  306. bool isAlphaNumeric(String str) {
  307. final RegExp alphaNumericRegExp = RegExp(r'^[a-zA-Z0-9]+$');
  308. return alphaNumericRegExp.hasMatch(str);
  309. }
  310. bool isAlphaNumericChineseWithSpace(String str) {
  311. final RegExp alphaNumericChineseWithSpaceRegExp =
  312. RegExp(r'^[a-zA-Z0-9\u4e00-\u9fa5\s]+$');
  313. return alphaNumericChineseWithSpaceRegExp.hasMatch(str);
  314. }
  315. String getCheckCode(String idCard17) {
  316. List<int> coefficients = [
  317. 7,
  318. 9,
  319. 10,
  320. 5,
  321. 8,
  322. 4,
  323. 2,
  324. 1,
  325. 6,
  326. 3,
  327. 7,
  328. 9,
  329. 10,
  330. 5,
  331. 8,
  332. 4,
  333. 2
  334. ];
  335. List<String> checkCodes = [
  336. '1',
  337. '0',
  338. 'X',
  339. '9',
  340. '8',
  341. '7',
  342. '6',
  343. '5',
  344. '4',
  345. '3',
  346. '2'
  347. ];
  348. int sum = 0;
  349. for (int i = 0; i < idCard17.length; i++) {
  350. int digit = int.parse(idCard17[i]);
  351. sum += digit * coefficients[i];
  352. }
  353. int remainder = sum % 11;
  354. return checkCodes[remainder];
  355. }
  356. Future<String?> _validateForm() async {
  357. if (state.name.isEmpty) {
  358. return "请填写姓名";
  359. }
  360. // else {
  361. // if (!isAlphaNumericChineseWithSpace(state.name) ||
  362. // state.name.length >= 20) {
  363. // return "姓名只能由中文、字母或数字组成,并且小于20个字符";
  364. // }
  365. // }
  366. if (state.cardNo.isEmpty) {
  367. return "请填写证件号";
  368. }
  369. if (state.phoneNo.isNotEmpty) {
  370. if (state.phoneNo.length != 11 || !isNumeric(state.phoneNo)) {
  371. return "请填写正确的手机号";
  372. }
  373. }
  374. if (state.cardType == CardTypeEnum.Identity) {
  375. bool isNotIDCard = validateIDCard(state.cardNo);
  376. if (!isNotIDCard) {
  377. return "请填写正确的证件号";
  378. }
  379. }
  380. if (state.cardType == CardTypeEnum.SocialInsurance) {
  381. if (state.cardNo.length != 18 || !isNumeric(state.cardNo)) {
  382. return "请填写正确的社保号";
  383. }
  384. }
  385. if (state.cardType == CardTypeEnum.Passport) {
  386. if (state.cardNo.length != 9 || !isAlphaNumeric(state.cardNo)) {
  387. return "请填写正确的护照号";
  388. }
  389. }
  390. /// TODO 需求变更暂时删除
  391. // final selectedNormalCodes = crowdLabelsController.state.selectedNormalCodes;
  392. // if (selectedNormalCodes.length > 1) {
  393. // return "人群分类:一般人群、儿童、孕妇、老年人,只可选择其一!";
  394. // }
  395. // final crowdLabelCodes = crowdLabelsController.state.selectedCodes;
  396. // if (crowdLabelCodes.isEmpty) {
  397. // return "请选择人群分类";
  398. // }
  399. // if (state.gender == GenderEnum.Male &&
  400. // crowdLabelCodes.contains('RQFL_YF')) {
  401. // return "当前居民性别为“男”,人群分类不可选择孕妇!";
  402. // }
  403. return null;
  404. }
  405. }