controller.dart 13 KB

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