123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624 |
- import 'dart:convert';
- import 'package:fis_jsonrpc/rpc.dart';
- import 'package:flutter/material.dart';
- import 'package:get/get.dart';
- import 'package:uuid/uuid.dart';
- import 'package:vitalapp/architecture/defines.dart';
- import 'package:vitalapp/architecture/storage/text_storage.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/helper/id_card_helper.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/managers/interfaces/registration.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/home/controller.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/routes/routes.dart';
- import 'package:vitalapp/pages/patient_info/controller.dart';
- import 'package:vitalapp/pages/patient_info/state.dart';
- import 'package:vitalapp/rpc.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<IPatientManager>();
- final crowdLabelsController = Get.find<CrowdLabelsController>();
- final _deviceManager = Get.find<IDeviceManager>();
- final patientInfomationController = Get.find<PatientInfomationController>();
- PatientInfomationState get patientInfomationState =>
- patientInfomationController.state;
- final state = CreatePatientState();
- bool isClearPrompt = true;
- @override
- void onReady() {
- final params = Get.parameters;
- if (params.containsKey("from")) {
- if (params["from"] == "list") {
- state.isCreateOnly = true;
- }
- }
- isClearPrompt = true;
- if (Routes.parameters.containsKey('patientInfo')) {
- if (Routes.parameters['patientInfo'] != null) {
- ///由于PatientBaseDTO中没有headImage字段,所以这里单独解析这个json,尝试获取人脸图像并绑定
- var jsonString = Routes.parameters['patientInfo'];
- // 解析 JSON 字符串并转换为 Map
- final parsedJson = json.decode(jsonString);
- //获取“isVital”字段的值
- final bool? isVital = parsedJson['isVital'];
- if (isVital != null) {
- state.isVital = isVital;
- }
- // 获取 'headImage' 字段的值
- final String? headImageUrl = parsedJson['headImage'];
- if (headImageUrl != null) {
- state.headImage = headImageUrl;
- } else {
- PatientBaseDTO patientInfo = PatientBaseDTO.fromJson(
- jsonDecode(
- Routes.parameters['patientInfo']!,
- ),
- );
- patientInfoParse(patientInfo);
- }
- }
- }
- return super.onReady();
- }
- /// 存为档案,调整到档案详情
- void saveAndBack() async {
- final validateMsg = await _validateForm();
- if (validateMsg != null) {
- toast(validateMsg);
- return null;
- }
- setBusy("正在保存...");
- await Future.delayed(const Duration(milliseconds: 500));
- final code = await _submitForm();
- if (code == null || code.isEmpty) {
- busy = false;
- logger.e('CreatePatientController saveAndBack code is null');
- PromptBox.toast("保存失败");
- return;
- }
- isClearPrompt = false;
- // await queryIsNeedFaceInput(); //询问是否录入人脸
- if (state.headImage.isNotEmpty) {
- await rpc.vitalPatient.savePatientBaseByFaceImageAsync(
- SavePatientBaseByFaceImageRequest(
- cardNo: code,
- token: Store.user.token,
- image: state.headImage,
- ),
- );
- ///TODO(Loki):提交体检记录
- _submitRegiterInfo();
- } else if (kIsOnline &&
- Store.user.hasFeature(FeatureKeys.FaceRecognition)) {
- var photoUrl = await getPatientPhoto(patientInfomationState.cardNo);
- if (photoUrl.isEmpty) {
- busy = false;
- await onFaceEntryClicked(); //不询问直接录入
- setBusy("正在保存...");
- }
- }
- Future.delayed(
- ///这个800ms不能移除,移除后会导致建档失败
- const Duration(milliseconds: 800),
- () async {
- final switchReult = await _switchCurrentPatient(code);
- // 重置状态
- state.reset();
- busy = false;
- if (switchReult) {
- PromptBox.toast("保存成功");
- Future.delayed(const Duration(milliseconds: 500));
- Get.find<HomeController>().switchNavByName("/patient/detail");
- }
- },
- );
- }
- /// 询问是否需要人脸录入
- Future<void> queryIsNeedFaceInput() async {
- /// 拥有人脸权限并且不是身份证扫码建档
- if (Store.user.hasFeature(FeatureKeys.FaceRecognition) && kIsOnline) {
- var photoUrl = await getPatientPhoto(patientInfomationState.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<DeviceModel?> getDevice(String type) async {
- List<DeviceModel> 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<CardReaderResult>(
- const BluetoothCardReaderDialog(),
- barrierDismissible: false,
- );
- }
- } else {
- result = await Get.dialog<CardReaderResult>(
- const CardReaderDialog(),
- barrierDismissible: false,
- );
- }
- if (result != null && result.success) {
- PromptBox.toast("读取成功");
- final isCardNoChanged = patientInfomationState.cardNo != result.cardNo;
- // patientInfomationState.cardNo = result.cardNo; // 回填身份证号
- final passed =
- await patientInfomationController.onCardNoChanged(result.cardNo);
- if (!passed) {
- return;
- }
- if (isCardNoChanged) {
- patientInfomationState.phoneNo = "";
- patientInfomationState.emergencyPhone = "";
- }
- patientInfomationState.name = result.name; // 回填姓名
- patientInfomationState.gender = result.gender; // 回填性别
- patientInfomationState.nation = result.nation; // 回填民族
- patientInfomationState.birthday = result.birthday; // 回填出生日期
- patientInfomationState.permanentResidenceAddress =
- result.address; // 回填户籍地址
- if (patientInfomationState.isSyncAddresses) {
- patientInfomationState.address = result.address; // 回填现住地址
- }
- if (passed) {
- // 缓存填充
- patientInfomationController.checkOfflinePatientExists(result.cardNo);
- }
- } else {
- print("读卡取消");
- }
- }
- void onIdCardScanClicked() async {
- var verifyResult = await _verifyCameraPermissions();
- if (!verifyResult) {
- return;
- }
- final IdCardScanResult? result = await Get.to<IdCardScanResult>(
- () => const IdCardScanPage(),
- );
- if (result != null && result.success) {
- PromptBox.toast("身份证信息识别成功");
- PatientBaseDTO patientInfo = result.patientBaseDTO;
- logger.i(
- "CreatePatientController onReadCardClickedToDetail patientInfo${jsonEncode(patientInfo.toJson())}");
- await patientInfoParse(patientInfo);
- } else {
- print("识别取消");
- }
- }
- Future<void> patientInfoParse(PatientBaseDTO patientInfo) async {
- final isCardNoChanged = patientInfomationState.cardNo != patientInfo.cardNo;
- // patientInfomationState.cardNo = patientInfo.cardNo ?? ""; // 回填身份证号
- final passed = await patientInfomationController
- .onCardNoChanged(patientInfo.cardNo ?? "");
- if (!passed) {
- return;
- }
- if (isCardNoChanged) {
- patientInfomationState.phoneNo = "";
- patientInfomationState.emergencyPhone = "";
- }
- patientInfomationState.name = patientInfo.patientName ?? ""; // 回填姓名
- patientInfomationState.gender = patientInfo.patientGender; // 回填性别
- patientInfomationState.nation = patientInfo.nationality ?? ""; // 回填民族
- patientInfomationState.birthday = patientInfo.birthday; // 回填出生日期
- patientInfomationState.permanentResidenceAddress =
- patientInfo.patientAddress ?? ""; // 回填户籍地址
- if (patientInfomationState.isSyncAddresses) {
- patientInfomationState.address =
- patientInfo.patientAddress ?? ""; // 回填现住地址
- }
- if (passed) {
- // 缓存填充
- patientInfomationController
- .checkOfflinePatientExists(patientInfo.cardNo ?? "");
- }
- }
- Future<bool> _verifyCameraPermissions() async {
- IPermissionManager permissionManager = Get.find<IPermissionManager>();
- 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<FaceRecognitionResult>(
- () => const FacialRecognitionPage(
- mode: FacialRecognitionMode.faceRecognition,
- ),
- );
- if (result != null && result.success) {
- final patient = result.patientInfo;
- final hasConfirmed = await FaceResultDialog.show(patient, true);
- if (hasConfirmed &&
- patient.createdOrgCode == Store.user.organizationCode) {
- await _checkinPatient(patient, false);
- }
- } else {
- print("识别取消");
- }
- }
- Future<bool> _switchCurrentPatient(String cardNo) async {
- PatientDTO? patientInfoDto = await _patientManager.getDetail(cardNo);
- if (patientInfoDto != null) {
- await _patientManager.switchCurrentPatient(patientInfoDto);
- logger.i(
- 'create居民 当前居民是:${patientInfoDto.patientName} 居民code:${patientInfoDto.code}');
- return true;
- } else {
- PromptBox.toast("切换居民失败!");
- return false;
- }
- }
- /// 点击录入人脸
- Future<void> onFaceEntryClicked() async {
- final PatientDTO patientInfo = PatientDTO(
- patientName: patientInfomationState.name,
- phone: patientInfomationState.phoneNo,
- emergencyPhone: patientInfomationState.emergencyPhone,
- cardNo: patientInfomationState.cardNo,
- nationality: patientInfomationState.nation,
- birthday: patientInfomationState.birthday,
- cardType: patientInfomationState.cardType,
- patientGender: patientInfomationState.gender ?? GenderEnum.Unspecified,
- code: patientInfomationState.cardNo,
- );
- bool? result = await Get.to<bool>(
- () => FacialRecognitionPage(
- mode: FacialRecognitionMode.faceInput,
- patientInfo: patientInfo,
- ),
- );
- if (result != null && result) {
- PromptBox.toast('人脸数据存入成功');
- }
- }
- /// 处理 “同户籍地址” 勾选变更事件
- void onSyncAddressCheckChanged(bool isChecked) {
- patientInfomationState.isSyncAddresses = isChecked;
- if (isChecked) {
- // 同步户籍地址到现住地址
- patientInfomationState.address =
- patientInfomationState.permanentResidenceAddress;
- } else {
- patientInfomationState.address = "";
- }
- }
- /// 处理户籍地址变更
- void onCensusRegisterChanged(String value) {
- patientInfomationState.permanentResidenceAddress = value;
- if (patientInfomationState.isSyncAddresses) {
- patientInfomationState.address = value;
- }
- }
- Future<String> 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<String?> _submitForm() async {
- final request = CreatePatientRequest2(
- patientName: patientInfomationState.name,
- phone: patientInfomationState.phoneNo,
- emergencyName: patientInfomationState.emergencyName,
- emergencyPhone: patientInfomationState.emergencyPhone,
- patientGender: patientInfomationState.gender ?? GenderEnum.Unspecified,
- nationality: patientInfomationState.nation,
- birthday: patientInfomationState.birthday?.toUtc(),
- cardType: patientInfomationState.cardType,
- cardNo: patientInfomationState.cardNo,
- patientAddress: patientInfomationState.address,
- permanentResidenceAddress:
- patientInfomationState.permanentResidenceAddress,
- crowdLabels:
- _patientManager.crowdLabelsConvert(patientInfomationState.labels),
- );
- logger.i("create CreatePatientRequest2 ${jsonEncode(request.toJson())}");
- final result = await _patientManager.create(request);
- if (result != null) {
- var request = GetPatientExtensionByCodeAndKeyRequest(
- token: Store.user.token,
- patientCode: patientInfomationState.cardNo,
- key: "PatientHealthInfo",
- );
- var result = await _patientManager
- .getPatientExtensionDetailByCodeAndKeyAsync(request);
- var healthCode = result?.code ?? '';
- _patientManager.createPatientExtension(
- patientInfomationState.cardNo,
- patientInfomationState.healthInfo,
- patientInfomationState.detailInfo,
- healthCode,
- );
- }
- return result;
- }
- 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);
- }
- Future<String?> _validateForm() async {
- patientInfomationController.state.name;
- if (patientInfomationState.name.isEmpty) {
- return "请填写姓名";
- }
- if (patientInfomationState.cardNo.isEmpty) {
- return "请填写证件号";
- }
- if (patientInfomationState.phoneNo.isNotEmpty) {
- if (patientInfomationState.phoneNo.length != 11 ||
- !isNumeric(patientInfomationState.phoneNo)) {
- return "请填写正确的联系电话";
- }
- }
- if (patientInfomationState.emergencyPhone.isNotEmpty) {
- if (patientInfomationState.emergencyPhone.length != 11 ||
- !isNumeric(patientInfomationState.emergencyPhone)) {
- return "请填写正确的联系人电话";
- }
- }
- if (patientInfomationState.cardType == CardTypeEnum.Identity) {
- bool isNotIDCard =
- IdCardHelper.validateIDCard(patientInfomationState.cardNo);
- if (!isNotIDCard) {
- return "请填写正确的证件号";
- }
- }
- if (patientInfomationState.cardType == CardTypeEnum.SocialInsurance) {
- if (patientInfomationState.cardNo.length != 18 ||
- !isNumeric(patientInfomationState.cardNo)) {
- return "请填写正确的社保号";
- }
- }
- if (patientInfomationState.cardType == CardTypeEnum.Passport) {
- if (patientInfomationState.cardNo.length != 9 ||
- !isAlphaNumeric(patientInfomationState.cardNo)) {
- return "请填写正确的护照号";
- }
- }
- return null;
- }
- /// 切换当前登记居民
- Future<void> _checkinPatient(
- PatientBaseDTO patient,
- bool isNeedConfirm,
- ) 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,
- );
- await _switchCurrentPatient(patientDTO.code!);
- Get.find<HomeController>()
- .switchNavByName("/patient/detail", null, isNeedConfirm);
- }
- Future<void> 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) {
- patientInfomationState.gender = GenderEnum.Male;
- } else {
- patientInfomationState.gender = GenderEnum.Female;
- }
- } else {
- patientInfomationState.gender = GenderEnum.Unknown;
- }
- }
- }
- void _submitRegiterInfo() async {
- Get.find<IRegistrationManager>().addRegiterInfoAsync(
- request: AddRegiterInfoRequest(
- token: Store.user.token,
- code: await saveCachedBatchNumber(patientInfomationState.cardNo),
- iDCardNo: patientInfomationState.cardNo,
- phone: patientInfomationState.phoneNo,
- name: patientInfomationState.name,
- sex: patientInfomationState.genderDesc,
- age: patientInfomationState.age,
- birthday: patientInfomationState.birthday,
- adress: patientInfomationState.address,
- ///TODO(Loki):这里projectType这个页面无法区分,且这个字段接口中不可为空,故而先用一个默认值
- projectType: state.isVital
- ? VitalProjectTypeEnum.VinnoHealth
- : VitalProjectTypeEnum.VitalStation,
- nationality: patientInfomationState.nation,
- permanentResidenceAddress:
- patientInfomationState.permanentResidenceAddress,
- ),
- );
- }
- Future<String?> saveCachedBatchNumber(String? cardNo) async {
- String batchNumber = const Uuid().v4().replaceAll('-', '');
- TextStorage cachedRecord = TextStorage(
- fileName: 'batchNumber',
- directory: "patient/${cardNo}",
- );
- await cachedRecord.save(batchNumber);
- return batchNumber;
- }
- }
|