import 'dart:convert'; import 'package:fis_jsonrpc/rpc.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:vitalapp/architecture/defines.dart'; import 'package:vitalapp/architecture/utils/prompt_box.dart'; import 'package:vitalapp/architecture/values/features.dart'; import 'package:vitalapp/components/alert_dialog.dart'; import 'package:vitalapp/global.dart'; import 'package:vitalapp/managers/interfaces/device.dart'; import 'package:vitalapp/managers/interfaces/models/device.dart'; import 'package:vitalapp/managers/interfaces/patient.dart'; import 'package:vitalapp/managers/interfaces/permission.dart'; import 'package:vitalapp/pages/controllers/blue_location_mixin.dart'; import 'package:vitalapp/pages/controllers/crowd_labels.dart'; import 'package:vitalapp/pages/controllers/home_nav_mixin.dart'; import 'package:vitalapp/pages/patient/bluetooth_card_reader/view.dart'; import 'package:vitalapp/pages/patient/card_reader/index.dart'; import 'package:vitalapp/pages/patient/create/state.dart'; import 'package:vitalapp/pages/patient/create/widgets/face_result_dialog.dart'; import 'package:vitalapp/pages/patient/list/controller.dart'; import 'package:vitalapp/routes/routes.dart'; import 'package:vitalapp/store/store.dart'; import 'package:vnote_device_plugin/consts/types.dart'; import 'package:fis_common/logger/logger.dart'; import '../../facial_recognition/index.dart'; import '../../id_card_scan/index.dart'; class CreatePatientController extends FControllerBase with HomeNavMixin, BluetoothAndLocationMixin { final _patientManager = Get.find(); final crowdLabelsController = Get.find(); final _patientListController = Get.find(); final _deviceManager = Get.find(); final state = CreatePatientState(); @override Future onLoad() { final params = Get.parameters; if (params.containsKey("from")) { if (params["from"] == "list") { state.isCreateOnly = true; } } if (Routes.parameters.containsKey('patientInfo')) { if (Routes.parameters['patientInfo'] != null) { PatientBaseDTO patientInfo = PatientBaseDTO.fromJson( jsonDecode( Routes.parameters['patientInfo']!, ), ); patientInfoParse(patientInfo); } } return super.onLoad(); } /// 保存档案,调整签约 void gotoSignContract() async { setBusy("正在保存..."); String? code; try { final validateMsg = await _validateForm(); if (validateMsg != null) { toast(validateMsg); return null; } code = await _submitForm(); if (code == null) { busy = false; PromptBox.toast("保存失败"); return; } // final code = "13B95A2B2790464BBFD9B30A71F15C95"; busy = false; Future.delayed( const Duration(milliseconds: 800), () { state.reset(); // 重置状态 }, ); } finally { if (code != null) { Get.toNamed( "/contract/package_list", parameters: {"patientCode": code}, ); } } // Get.find().switchNavByName("/patient/list"); // Future.delayed( // const Duration(milliseconds: 800), // () { // // TODO: // Get.find().gotoDetail(code); // busy = false; // }, // ); } /// 存为档案,调整到档案详情 void saveAndBack() async { final validateMsg = await _validateForm(); if (validateMsg != null) { toast(validateMsg); return null; } final code = await _submitForm(); if (code == null) { busy = false; logger.e('CreatePatientController saveAndBack code is null'); // PromptBox.toast("保存失败"); return; } else { busy = false; // await queryIsNeedFaceInput(); //询问是否录入人脸 if (Store.user.hasFeature(FeatureKeys.FaceRecognition) && kIsOnline) { var photoUrl = await getPatientPhoto(state.cardNo); if (photoUrl.isEmpty) { await onFaceEntryClicked(); //不询问直接录入 } } Future.delayed( ///这个800ms不能移除,移除后会导致建档失败 const Duration(milliseconds: 800), () { state.reset(); // 重置状态 busy = false; PromptBox.toast("保存成功"); _patientListController.reloadList(); }, ); } ///不跳转到详情页 // Get.find().switchNavByName("/patient/list"); // Get.put(PatientListController()); Future.delayed( ///这个800ms不能移除,移除后会导致建档失败 const Duration(milliseconds: 800), () async { onIdcardInfoIsCreateRecord(code); await Get.find().gotoDetail( code, ); busy = false; }, ); } /// 询问是否需要人脸录入 Future queryIsNeedFaceInput() async { /// 拥有人脸权限并且不是身份证扫码建档 if (Store.user.hasFeature(FeatureKeys.FaceRecognition) && kIsOnline) { var photoUrl = await getPatientPhoto(state.cardNo); if (photoUrl.isNotEmpty) { await Get.dialog(VAlertDialog( contentPadding: const EdgeInsets.fromLTRB(20, 1, 20, 1), title: '提示', content: Container( margin: const EdgeInsets.only(bottom: 20), child: Column( mainAxisSize: MainAxisSize.min, children: [ Image.network(photoUrl, width: 300, height: 220, fit: BoxFit.cover, loadingBuilder: (context, child, progress) { if (progress == null || progress.cumulativeBytesLoaded == progress.expectedTotalBytes) { return Image.network( photoUrl, width: 300, height: 220, fit: BoxFit.cover, ); } return const CircularProgressIndicator( color: Colors.blueAccent); }), const Text("该居民已采集过人脸,是否重新采集?", style: TextStyle(fontSize: 20), textAlign: TextAlign.center), ], ), ), confirmLabel: '重新采集', cancelLabel: '跳过', showCancel: true, onConfirm: () async { await onFaceEntryClicked(); Get.back(); }, onCanceled: () { Get.back(); }, )); } else { await Get.dialog(VAlertDialog( contentPadding: const EdgeInsets.fromLTRB(20, 1, 20, 1), title: '提示', content: Container( margin: const EdgeInsets.only(bottom: 20), child: const Text( '请采集人像,完成后可以使用人脸识别功能', style: TextStyle(fontSize: 20), textAlign: TextAlign.center, ), ), confirmLabel: '采集', cancelLabel: '暂不采集', showCancel: true, onConfirm: () async { await onFaceEntryClicked(); Get.back(); }, onCanceled: () { Get.back(); }, )); } } } Future getDevice(String type) async { List devices = await _deviceManager.getDeviceList(); return devices.firstWhereOrNull((element) => element.type == type); } /// 点击读卡事件 void onReadCardClicked() async { final DeviceModel? device = await getDevice(DeviceTypes.IC_READER); CardReaderResult? result; if (device != null) { final envPassed = await checkDeviceConnectEnv(); if (envPassed) { result = await Get.dialog( const BluetoothCardReaderDialog(), barrierDismissible: false, ); } } else { result = await Get.dialog( const CardReaderDialog(), barrierDismissible: false, ); } if (result != null && result.success) { PromptBox.toast("读取成功"); state.cardNo = result.cardNo; // 回填身份证号 state.name = result.name; // 回填姓名 state.gender = result.gender; // 回填性别 state.nation = result.nation; // 回填民族 state.birthday = result.birthday; // 回填出生日期 state.censusRegister = result.address; // 回填户籍地址 if (state.isSyncAddresses) { state.address = result.address; // 回填现住地址 } } else { print("读卡取消"); } } /// 点击读卡事件 (若已建档跳详情页面 若未建档跳创建页面) void onReadCardClickedToDetail() async { final DeviceModel? device = await getDevice(DeviceTypes.IC_READER); CardReaderResult? result; if (device != null) { final envPassed = await checkDeviceConnectEnv(); if (envPassed) { result = await Get.dialog( const BluetoothCardReaderDialog(), barrierDismissible: false, ); } } else { result = await Get.dialog( const CardReaderDialog(), barrierDismissible: false, ); } if (result != null && result.success) { PromptBox.toast("读取成功"); PatientBaseDTO patientInfo = PatientBaseDTO(); patientInfo.cardNo = result.cardNo; // 回填身份证号 patientInfo.patientName = result.name; // 回填姓名 patientInfo.patientGender = result.gender; // 回填性别 patientInfo.nationality = result.nation; // 回填民族 patientInfo.birthday = result.birthday; // 回填出生日期 patientInfo.patientAddress = result.address; // 回填户籍地址 onIdcardInfoIsCreateRecord(patientInfo.cardNo!, patientInfo); } else { print("读卡取消"); } } /// 点击身份识别建档(拍摄扫描身份证) void onIdCardScanClicked() async { var verifyResult = await _verifyCameraPermissions(); if (!verifyResult) { return; } final IdCardScanResult? result = await Get.to( () => const IdCardScanPage(), ); if (result != null && result.success) { PromptBox.toast("身份证信息识别成功"); PatientBaseDTO patientInfo = result.patientBaseDTO; patientInfoParse(patientInfo); } else { print("识别取消"); } } /// 点击身份识别(拍摄扫描身份证) void onIdCardScanClickedToDetail() async { var verifyResult = await _verifyCameraPermissions(); if (!verifyResult) { return; } final IdCardScanResult? result = await Get.to( () => const IdCardScanPage(), ); if (result != null && result.success) { PromptBox.toast("身份证信息识别成功"); PatientBaseDTO patientInfo = result.patientBaseDTO; onIdcardInfoIsCreateRecord(patientInfo.cardNo!, patientInfo); } else { print("识别取消"); } } void patientInfoParse(PatientBaseDTO patientInfo) { state.cardNo = patientInfo.cardNo ?? ""; // 回填身份证号 state.name = patientInfo.patientName ?? ""; // 回填姓名 state.gender = patientInfo.patientGender; // 回填性别 state.nation = patientInfo.nationality ?? ""; // 回填民族 state.birthday = patientInfo.birthday; // 回填出生日期 state.censusRegister = patientInfo.patientAddress ?? ""; // 回填户籍地址 if (state.isSyncAddresses) { state.address = patientInfo.patientAddress ?? ""; // 回填现住地址 } } Future _verifyCameraPermissions() async { IPermissionManager permissionManager = Get.find(); var isCameraPermissions = await permissionManager.requestCameraPermissions(); if (!isCameraPermissions) { await Get.dialog( VAlertDialog( title: "提示", width: 420, content: Container( height: 32, padding: const EdgeInsets.symmetric(horizontal: 24), alignment: Alignment.center, child: const Text( "未授予相机权限,前去设置", style: TextStyle(fontSize: 20), ), ), showCancel: false, onConfirm: () async { Get.back(); await permissionManager.openAppSettingsAsync(); }, ), barrierDismissible: false, barrierColor: Colors.black.withOpacity(.4), ); } return isCameraPermissions; } /// 点击人脸识别 void onFaceIdLoginClicked() async { var verifyResult = await _verifyCameraPermissions(); if (!verifyResult) { return; } final FaceRecognitionResult? result = await Get.to( () => const FacialRecognitionPage( mode: FacialRecognitionMode.faceRecognition, ), ); if (result != null && result.success) { final patient = result.patientInfo; final hasConfirmed = await FaceResultDialog.show(patient, true); if (hasConfirmed) { await _checkinPatient(patient); } } else { print("识别取消"); } } void onIdcardInfoIsCreateRecord( String cardNo, [ PatientBaseDTO? patientInfo, ]) async { PatientDTO? patientInfoDto = await _patientManager.getDetail(cardNo); if (patientInfoDto != null) { Store.user.currentSelectPatientInfo = patientInfoDto; } if (patientInfo != null) { final hasConfirmed = await FaceResultDialog.show(patientInfo, patientInfoDto != null); if (hasConfirmed) { if (patientInfoDto != null) { await Get.find() .gotoDetail(patientInfo.cardNo!, patientInfoDto, patientInfo); } else { await Get.find().gotoCreate( patientInfo.cardNo!, patientInfoDto, patientInfo, ); } } } } /// 点击录入人脸 Future onFaceEntryClicked() async { final PatientDTO patientInfo = PatientDTO( patientName: state.name, phone: state.phoneNo, cardNo: state.cardNo, nationality: state.nation, birthday: state.birthday, cardType: state.cardType, patientGender: state.gender!, code: state.cardNo, ); bool? result = await Get.to( () => FacialRecognitionPage( mode: FacialRecognitionMode.faceInput, patientInfo: patientInfo, ), ); if (result != null && result) { PromptBox.toast('人脸数据存入成功'); } } /// 处理 “同户籍地址” 勾选变更事件 void onSyncAddressCheckChanged(bool isChecked) { state.isSyncAddresses = isChecked; if (isChecked) { // 同步户籍地址到现住地址 state.address = state.censusRegister; } else { state.address = ""; } } /// 处理户籍地址变更 void onCensusRegisterChanged(String value) { state.censusRegister = value; if (state.isSyncAddresses) { state.address = value; } } Future getPatientPhoto(String patientCode) async { final dto = await _patientManager.getDetail(patientCode); if (dto != null && dto.photos != null && dto.photos!.isNotEmpty) { return dto.photos![0]; } return ""; } Future _submitForm() async { setBusy("正在保存..."); final crowdLabelCodes = crowdLabelsController.state.selectedCodes; final request = CreatePatientRequest2( patientName: state.name, phone: state.phoneNo, patientGender: state.gender!, nationality: state.nation, birthday: state.birthday?.toUtc(), cardType: state.cardType, cardNo: state.cardNo, patientAddress: state.address, permanentResidenceAddress: state.censusRegister, crowdLabels: crowdLabelCodes, ); final result = await _patientManager.create(request); return result; } bool validateIDCard(String idCard) { // 校验身份证号码长度 if (idCard.length != 18) { return false; } // 校验前17位是否为数字 String idCard17 = idCard.substring(0, 17); if (!isNumeric(idCard17)) { return false; } // 校验最后一位校验码 String checkCode = getCheckCode(idCard17); if (idCard[17].toUpperCase() != checkCode) { print("checkCode: $checkCode"); return false; } return true; } bool isNumeric(String str) { if (str.isEmpty) { return false; } return double.tryParse(str) != null; } bool isAlphaNumeric(String str) { final RegExp alphaNumericRegExp = RegExp(r'^[a-zA-Z0-9]+$'); return alphaNumericRegExp.hasMatch(str); } bool isAlphaNumericChineseWithSpace(String str) { final RegExp alphaNumericChineseWithSpaceRegExp = RegExp(r'^[a-zA-Z0-9\u4e00-\u9fa5\s]+$'); return alphaNumericChineseWithSpaceRegExp.hasMatch(str); } String getCheckCode(String idCard17) { List coefficients = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ]; List checkCodes = [ '1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2' ]; int sum = 0; for (int i = 0; i < idCard17.length; i++) { int digit = int.parse(idCard17[i]); sum += digit * coefficients[i]; } int remainder = sum % 11; return checkCodes[remainder]; } Future _validateForm() async { if (state.name.isEmpty) { return "请填写姓名"; } // else { // if (!isAlphaNumericChineseWithSpace(state.name) || // state.name.length >= 20) { // return "姓名只能由中文、字母或数字组成,并且小于20个字符"; // } // } if (state.cardNo.isEmpty) { return "请填写证件号"; } if (state.phoneNo.isNotEmpty) { if (state.phoneNo.length != 11 || !isNumeric(state.phoneNo)) { return "请填写正确的手机号"; } } if (state.cardType == CardTypeEnum.Identity) { bool isNotIDCard = validateIDCard(state.cardNo); if (!isNotIDCard) { return "请填写正确的证件号"; } } if (state.cardType == CardTypeEnum.SocialInsurance) { if (state.cardNo.length != 18 || !isNumeric(state.cardNo)) { return "请填写正确的社保号"; } } if (state.cardType == CardTypeEnum.Passport) { if (state.cardNo.length != 9 || !isAlphaNumeric(state.cardNo)) { return "请填写正确的护照号"; } } if (state.gender == null) { return "请选择性别"; } /// TODO 需求变更暂时删除 // final selectedNormalCodes = crowdLabelsController.state.selectedNormalCodes; // if (selectedNormalCodes.length > 1) { // return "人群分类:一般人群、儿童、孕妇、老年人,只可选择其一!"; // } // final crowdLabelCodes = crowdLabelsController.state.selectedCodes; // if (crowdLabelCodes.isEmpty) { // return "请选择人群分类"; // } // if (state.gender == GenderEnum.Male && // crowdLabelCodes.contains('RQFL_YF')) { // return "当前居民性别为“男”,人群分类不可选择孕妇!"; // } return null; } /// 切换当前登记居民 Future _checkinPatient(PatientBaseDTO patient) async { final patientDTO = PatientDTO( code: patient.cardNo, cardNo: patient.cardNo, patientName: patient.patientName, nationality: patient.nationality, patientGender: patient.patientGender, birthday: patient.birthday, patientAddress: patient.patientAddress, ); Store.user.currentSelectPatientInfo = patientDTO; logger.i( 'create居民 当前居民是:${patientDTO.patientName} 居民code:${patientDTO.code}'); onIdcardInfoIsCreateRecord(patientDTO.code!); await Get.find().gotoDetail( patient.cardNo!, ); } Future setGender(String cardNo) async { if (cardNo.isNotEmpty && cardNo.length > 17) { var sexNum = cardNo.substring(16, 17); if (sexNum.isNotEmpty && RegExp(r'^\d+$').hasMatch(sexNum)) { if (int.parse(sexNum) % 2 == 1) { state.gender = GenderEnum.Male; } else { state.gender = GenderEnum.Female; } } else { state.gender = GenderEnum.Unknown; } } } }