controller.dart 11 KB

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