ソースを参照

feat(流程管理)

- 新增流程模式导入功能
- 新增流程模式启用状态
- 新增流程模式删除功能
- 优化流程模式列表UI和交互

feat(指纹)

- 指纹录入修改为按压三次
- 优化指纹校验方式
- 支持指纹分组,每个指纹组包含三次按压数据

refactor(数据库)

- 新增数据库版本4
- is_workflow_mode表新增status字段
- sys_user_characteristic表新增group字段

fix(作业)

- 修复分组作业钥匙未归还仍可继续操作的问题
- 修复底栏在部分页面未隐藏的问题
周文健 3 ヶ月 前
コミット
601b2a905f
39 ファイル変更615 行追加308 行削除
  1. 14 31
      app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt
  2. 13 25
      app/src/main/java/com/grkj/iscs/features/login/dialog/LoginDialog.kt
  3. 0 14
      app/src/main/java/com/grkj/iscs/features/login/viewmodel/LoginViewModel.kt
  4. 14 1
      app/src/main/java/com/grkj/iscs/features/main/activity/MainActivity.kt
  5. 10 26
      app/src/main/java/com/grkj/iscs/features/main/dialog/CheckFaceDialog.kt
  6. 9 5
      app/src/main/java/com/grkj/iscs/features/main/dialog/user_info/AddFingerprintDialog.kt
  7. 12 0
      app/src/main/java/com/grkj/iscs/features/main/entity/MetaInfo.kt
  8. 9 0
      app/src/main/java/com/grkj/iscs/features/main/entity/WorkflowExportPackage.kt
  9. 11 2
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobExecuteFragment.kt
  10. 50 19
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/WorkflowManageFragment.kt
  11. 5 5
      app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SetFaceFragment.kt
  12. 69 80
      app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SetFingerprintFragment.kt
  13. 123 3
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/WorkflowViewModel.kt
  14. 21 7
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/user_info/UserInfoViewModel.kt
  15. 1 0
      app/src/main/res/layout/dialog_add_fingerprint.xml
  16. 7 2
      app/src/main/res/layout/fragment_workflow_manage.xml
  17. 51 0
      app/src/main/res/layout/item_workflow_manage.xml
  18. 14 0
      app/src/main/res/values-en/strings.xml
  19. 14 0
      app/src/main/res/values-zh/strings.xml
  20. 14 0
      app/src/main/res/values/strings.xml
  21. 1 1
      data/src/main/java/com/grkj/data/dao/UserDao.kt
  22. 13 2
      data/src/main/java/com/grkj/data/dao/WorkflowStepDao.kt
  23. 2 0
      data/src/main/java/com/grkj/data/data/CommonConstants.kt
  24. 41 8
      data/src/main/java/com/grkj/data/database/ISCSMigrations.kt
  25. 2 0
      data/src/main/java/com/grkj/data/model/dos/SysUserCharacteristicDo.kt
  26. 5 0
      data/src/main/java/com/grkj/data/model/dos/WorkflowMode.kt
  27. 21 0
      data/src/main/java/com/grkj/data/model/vo/FingerprintDataVo.kt
  28. 2 4
      data/src/main/java/com/grkj/data/repository/IUserRepository.kt
  29. 10 0
      data/src/main/java/com/grkj/data/repository/IWorkflowRepository.kt
  30. 1 9
      data/src/main/java/com/grkj/data/repository/impl/network/NetworkUserRepository.kt
  31. 8 0
      data/src/main/java/com/grkj/data/repository/impl/network/NetworkWorkflowRepository.kt
  32. 13 34
      data/src/main/java/com/grkj/data/repository/impl/standard/UserRepository.kt
  33. 14 2
      data/src/main/java/com/grkj/data/repository/impl/standard/WorkflowRepository.kt
  34. 18 2
      shared/src/main/java/com/grkj/shared/utils/BiometricVerifier.kt
  35. 0 13
      ui-base/src/main/java/com/grkj/ui_base/base/BaseViewModel.kt
  36. 0 10
      ui-base/src/main/java/com/grkj/ui_base/utils/fingerprint/FingerprintUtil.kt
  37. 1 1
      ui-base/src/main/res/values-en/strings.xml
  38. 1 1
      ui-base/src/main/res/values-zh/strings.xml
  39. 1 1
      ui-base/src/main/res/values/strings.xml

+ 14 - 31
app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt

@@ -101,7 +101,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
         super.initData()
         viewModel.registerFaceFeature().observe(this) {}
         //todo 测试用,直接创建管理员账号
-        viewModel.insertAdminAccount().observe(this){}
+        viewModel.insertAdminAccount().observe(this) {}
         requestPermissionsIfNeeded(*Constants.needPermission) {
             if (it) {
                 logger.info("权限获取成功")
@@ -154,36 +154,19 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
         FingerprintUtil.start()
         FingerprintUtil.setScanListener(object : FingerprintUtil.OnScanListener {
             override fun onScan(bitmap: Bitmap) {
-                if (ISCSConfig.isNetVersion) {
-                    LoadingEvent.sendLoadingEvent(
-                        CommonUtils.getStr(com.grkj.ui_base.R.string.doing_login),
-                        true
-                    )
-                    viewModel.loginWithFingerprint(
-                        ImageConvertUtils.bitmapToBase64(bitmap).toString()
-                    )
-                        .observe(this@LoginActivity) { isSuccess ->
-                            LoadingEvent.sendLoadingEvent()
-                            if (isSuccess) {
-                                startActivity(Intent(this@LoginActivity, MainActivity::class.java))
-                            } else {
-                                PopTip.build().tip(CommonUtils.getStr(R.string.login_failed))
-                            }
-                        }
-                }
-            }
-
-            override fun onScan(temp: ByteArray?) {
-                if (!ISCSConfig.isNetVersion) {
-                    viewModel.loginWithFingerprint(temp)
-                        .observe(this@LoginActivity) { isSuccess ->
-                            LoadingEvent.sendLoadingEvent()
-                            if (isSuccess) {
-                                startActivity(Intent(this@LoginActivity, MainActivity::class.java))
-                            } else {
-                                PopTip.tip(CommonUtils.getStr(R.string.login_failed))
-                            }
-                        }
+                LoadingEvent.sendLoadingEvent(
+                    CommonUtils.getStr(com.grkj.ui_base.R.string.doing_login),
+                    true
+                )
+                viewModel.loginWithFingerprint(
+                    ImageConvertUtils.bitmapToBase64(bitmap).toString()
+                ).observe(this@LoginActivity) { isSuccess ->
+                    LoadingEvent.sendLoadingEvent()
+                    if (isSuccess) {
+                        startActivity(Intent(this@LoginActivity, MainActivity::class.java))
+                    } else {
+                        PopTip.build().tip(CommonUtils.getStr(R.string.login_failed))
+                    }
                 }
             }
         })

+ 13 - 25
app/src/main/java/com/grkj/iscs/features/login/dialog/LoginDialog.kt

@@ -123,31 +123,19 @@ class LoginDialog(
                     FingerprintUtil.start()
                     FingerprintUtil.setScanListener(object : FingerprintUtil.OnScanListener {
                         override fun onScan(bitmap: Bitmap) {
-                            if (ISCSConfig.isNetVersion) {
-                                LoadingEvent.sendLoadingEvent(
-                                    CommonUtils.getStr(com.grkj.ui_base.R.string.doing_login),
-                                    true
-                                )
-                                viewModel.loginWithFingerprint(
-                                    ImageConvertUtils.bitmapToBase64(
-                                        bitmap
-                                    ).toString()
-                                )
-                                    .observe(lifecycleOwner) { isSuccess ->
-                                        LoadingEvent.sendLoadingEvent()
-                                        callBack?.invoke(isSuccess)
-                                    }
-                            }
-                        }
-
-                        override fun onScan(temp: ByteArray?) {
-                            if (!ISCSConfig.isNetVersion) {
-                                viewModel.loginWithFingerprint(temp)
-                                    .observe(lifecycleOwner) { isSuccess ->
-                                        LoadingEvent.sendLoadingEvent()
-                                        callBack?.invoke(isSuccess)
-                                    }
-                            }
+                            LoadingEvent.sendLoadingEvent(
+                                CommonUtils.getStr(com.grkj.ui_base.R.string.doing_login),
+                                true
+                            )
+                            viewModel.loginWithFingerprint(
+                                ImageConvertUtils.bitmapToBase64(
+                                    bitmap
+                                ).toString()
+                            )
+                                .observe(lifecycleOwner) { isSuccess ->
+                                    LoadingEvent.sendLoadingEvent()
+                                    callBack?.invoke(isSuccess)
+                                }
                         }
                     })
                 }

+ 0 - 14
app/src/main/java/com/grkj/iscs/features/login/viewmodel/LoginViewModel.kt

@@ -65,20 +65,6 @@ class LoginViewModel @Inject constructor(
         }
     }
 
-    /**
-     * 指纹登录
-     */
-    fun loginWithFingerprint(fingerprint: ByteArray?): LiveData<Boolean> {
-        return liveData(Dispatchers.IO) {
-            if (fingerprint == null) {
-                emit(false)
-            } else {
-                val loginSuccess = userRepository.loginWithFingerprint(fingerprint)
-                emit(loginSuccess)
-            }
-        }
-    }
-
     /**
      * 人脸登录
      */

+ 14 - 1
app/src/main/java/com/grkj/iscs/features/main/activity/MainActivity.kt

@@ -75,6 +75,15 @@ class MainActivity() : BaseActivity<ActivityMainBinding>() {
         ),
     )
 
+    private val bottomNavDestinations = setOf(
+        R.id.homeFragment,
+        R.id.dataManageHomeFragment,
+        R.id.jobManageHomeFragment,
+        R.id.hardwareManageHomeFragment,
+        R.id.exceptionManageHomeFragment,
+        R.id.userInfoHomeFragment
+    )
+
     override fun navHostFragmentId() = R.id.nav_host_fragment
 
     override fun getLayoutId(): Int {
@@ -120,13 +129,17 @@ class MainActivity() : BaseActivity<ActivityMainBinding>() {
                 replaceNavGraph(R.navigation.nav_user_info)
             }
         }
+        navController.addOnDestinationChangedListener { _, destination, _ ->
+            // 如果是我们定义的底栏图表对应的 NavGraph,就 show,否则 hide
+            binding.navBar.isVisible = bottomNavDestinations.contains(destination.id)
+        }
     }
 
     override fun onEvent(event: EventBean<Any>) {
         super.onEvent(event)
         when (event.code) {
             EventConstants.EVENT_BOTTOM_NAV_VISIBILITY -> {
-                binding.navBar.isVisible = (event.data as BottomNavVisibilityEvent).show
+//                binding.navBar.isVisible = (event.data as BottomNavVisibilityEvent).show
             }
 
             EventConstants.EVENT_LOGOUT -> {

+ 10 - 26
app/src/main/java/com/grkj/iscs/features/main/dialog/CheckFaceDialog.kt

@@ -100,35 +100,19 @@ class CheckFaceDialog(
                 FingerprintUtil.start()
                 FingerprintUtil.setScanListener(object : FingerprintUtil.OnScanListener {
                     override fun onScan(bitmap: Bitmap) {
-                        if (ISCSConfig.isNetVersion){
-                            LoadingEvent.sendLoadingEvent(
-                                CommonUtils.getStr(com.grkj.ui_base.R.string.doing_checking), true
-                            )
-                            viewModel.checkFinger(
-                                ImageConvertUtils.bitmapToBase64(bitmap).toString()
-                            ).observe(lifecycleOwner) {
-                                if (it) {
-                                    callBack?.invoke(it)
-                                } else {
-                                    PopTip.tip(R.string.verify_failed)
-                                }
+                        LoadingEvent.sendLoadingEvent(
+                            CommonUtils.getStr(com.grkj.ui_base.R.string.doing_checking), true
+                        )
+                        viewModel.checkFinger(
+                            ImageConvertUtils.bitmapToBase64(bitmap).toString()
+                        ).observe(lifecycleOwner) {
+                            if (it) {
+                                callBack?.invoke(it)
+                            } else {
+                                PopTip.tip(R.string.verify_failed)
                             }
                         }
-                    }
 
-                    override fun onScan(temp: ByteArray?) {
-                        if (!ISCSConfig.isNetVersion){
-                            LoadingEvent.sendLoadingEvent(
-                                CommonUtils.getStr(com.grkj.ui_base.R.string.doing_checking), true
-                            )
-                            viewModel.checkFinger(temp).observe(lifecycleOwner) {
-                                if (it) {
-                                    callBack?.invoke(it)
-                                } else {
-                                    PopTip.tip(R.string.verify_failed)
-                                }
-                            }
-                        }
                     }
                 })
             }

+ 9 - 5
app/src/main/java/com/grkj/iscs/features/main/dialog/user_info/AddFingerprintDialog.kt

@@ -1,6 +1,7 @@
 package com.grkj.iscs.features.main.dialog.user_info
 
 import android.view.View
+import android.widget.TextView
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.DialogAddFingerprintBinding
 import com.grkj.ui_base.utils.CommonUtils
@@ -11,16 +12,19 @@ import com.sik.sikcore.extension.setDebouncedClickListener
 /**
  * 添加指纹弹窗
  */
-class AddFingerprintDialog(val onCancel: () -> Unit) :
+class AddFingerprintDialog(
+    val onCancel: (CustomDialog) -> Unit,
+    val updateTip: (TextView) -> Unit
+) :
     OnBindView<CustomDialog>(R.layout.dialog_add_fingerprint) {
     private lateinit var binding: DialogAddFingerprintBinding
     override fun onBind(dialog: CustomDialog, p1: View) {
         binding = DialogAddFingerprintBinding.bind(p1)
         dialog?.setMaskColor(CommonUtils.getColor(com.grkj.ui_base.R.color.scrim))
         dialog.isCancelable = false
+        updateTip(binding.pressTip)
         binding.cancel.setDebouncedClickListener {
-            onCancel()
-            dialog.dismiss()
+            onCancel(dialog)
         }
     }
 
@@ -29,9 +33,9 @@ class AddFingerprintDialog(val onCancel: () -> Unit) :
          * 显示弹窗
          */
         @JvmStatic
-        fun show(onCancel: () -> Unit): CustomDialog {
+        fun show(onCancel: (CustomDialog) -> Unit, updateTip: (TextView) -> Unit): CustomDialog {
             return CustomDialog.show(
-                AddFingerprintDialog(onCancel),
+                AddFingerprintDialog(onCancel, updateTip),
                 CustomDialog.ALIGN.CENTER
             ).setCancelable(false)
         }

+ 12 - 0
app/src/main/java/com/grkj/iscs/features/main/entity/MetaInfo.kt

@@ -0,0 +1,12 @@
+package com.grkj.iscs.features.main.entity
+
+/**
+ * MetaInfo 元信息
+ */
+data class MetaInfo(
+    var version: Int,                    // 数据结构版本
+    var releaseTime: Long,              // 发布时间戳(毫秒)
+    var publisher: String,              // 发布者名称或账号
+    var summary: String,                // 人类可读摘要(如更新内容)
+    var compatibleAppVersions: List<String> = emptyList()
+)

+ 9 - 0
app/src/main/java/com/grkj/iscs/features/main/entity/WorkflowExportPackage.kt

@@ -0,0 +1,9 @@
+package com.grkj.iscs.features.main.entity
+
+/**
+ * 流程模式包装类
+ */
+data class WorkflowExportPackage<T>(
+    var meta: MetaInfo,   // 元信息
+    var data: T           // 泛型流程数据内容
+)

+ 11 - 2
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobExecuteFragment.kt

@@ -158,12 +158,22 @@ class JobExecuteFragment : BaseFragment<FragmentJobExecuteBinding>() {
     }
 
     private fun toLock(groupId: Long) {
+        val groupKey = viewModel.ticketKey.find { it.groupId == groupId && it.ticketType == 0 }
+        if (groupKey?.collectTime != null && groupKey.giveBackTime == null) {
+            showToast(getString(R.string.group_job_in_progress))
+            return
+        }
         viewModel.toLock(groupId).observe(this) {
 
         }
     }
 
     private fun toUnLock(groupId: Long) {
+        val groupKey = viewModel.ticketKey.find { it.groupId == groupId && it.ticketType == 1 }
+        if (groupKey?.collectTime != null && groupKey.giveBackTime == null) {
+            showToast(getString(R.string.group_job_in_progress))
+            return
+        }
         viewModel.toUnLock(groupId).observe(this) {
 
         }
@@ -628,8 +638,7 @@ class JobExecuteFragment : BaseFragment<FragmentJobExecuteBinding>() {
                     binding.jobNameTv.text = viewModel.ticketData!!.ticketName
                     binding.listRv.models = viewModel.ticketPoints
                     binding.stepRv.models = viewModel.ticketStep
-                    binding.stepDescriptionTv.text =
-                        viewModel.currentStepData?.stepDescription
+                    binding.stepDescriptionTv.text = viewModel.currentStepData?.stepDescription
                     refreshTicketUser()
                     checkCurrentStep()
                     checkLayout(currentTab)

+ 50 - 19
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/WorkflowManageFragment.kt

@@ -8,24 +8,18 @@ import com.drake.brv.utils.divider
 import com.drake.brv.utils.linear
 import com.drake.brv.utils.models
 import com.drake.brv.utils.setup
-import com.grkj.data.enums.JobTicketStatusEnum
-import com.grkj.data.model.vo.JobTicketManageVo
+import com.grkj.data.model.vo.WorkflowModeVo
 import com.grkj.iscs.R
 import com.grkj.iscs.common.DataTransferConstants
-import com.grkj.iscs.databinding.FragmentJobManageBinding
 import com.grkj.iscs.databinding.FragmentWorkflowManageBinding
-import com.grkj.iscs.databinding.ItemJobManageBinding
+import com.grkj.iscs.databinding.ItemWorkflowManageBinding
 import com.grkj.iscs.features.main.viewmodel.WorkflowViewModel
-import com.grkj.iscs.features.main.viewmodel.job_manage.JobManageViewModel
 import com.grkj.ui_base.base.BaseFragment
 import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
-import com.grkj.ui_base.utils.extension.tip
-import com.kongzue.dialogx.dialogs.PopTip
 import com.sik.sikcore.data.GlobalDataTempStore
 import com.sik.sikcore.extension.setDebouncedClickListener
 import dagger.hilt.android.AndroidEntryPoint
-import kotlin.getValue
 
 /**
  * 流程模式管理
@@ -45,7 +39,9 @@ class WorkflowManageFragment : BaseFragment<FragmentWorkflowManageBinding>() {
             deleteSelected()
         }
         binding.importWorkflow.setDebouncedClickListener {
-
+            viewModel.importWorkflowMode().observe(this){
+                getData()
+            }
         }
         binding.listRv.linear().divider {
             this.setColor(Color.BLACK)
@@ -53,7 +49,7 @@ class WorkflowManageFragment : BaseFragment<FragmentWorkflowManageBinding>() {
             this.endVisible = true
             this.orientation = DividerOrientation.VERTICAL
         }.setup {
-            addType<JobTicketManageVo>(R.layout.item_job_manage)
+            addType<WorkflowModeVo>(R.layout.item_workflow_manage)
             onBind {
                 onListDataBinding(this)
             }
@@ -67,14 +63,14 @@ class WorkflowManageFragment : BaseFragment<FragmentWorkflowManageBinding>() {
             return
         }
         TipDialog.show(
-            msg = CommonUtils.getStr(R.string.check_delete_job).toString(),
+            msg = CommonUtils.getStr(R.string.check_delete_workflow_mode).toString(),
             countDownTime = 10,
             onConfirmClick = {
                 viewModel.deleteSelectedWorkflowMode().observe(this) {
                     if (it) {
                         TipDialog.show(
                             dialogType = TipDialog.DialogType.SUCCESS,
-                            msg = CommonUtils.getStr(R.string.job_manage_delete_succeed)
+                            msg = CommonUtils.getStr(R.string.workflow_mode_manage_delete_succeed)
                                 .toString(),
                             showConfirm = false,
                             countDownTime = 10,
@@ -100,18 +96,53 @@ class WorkflowManageFragment : BaseFragment<FragmentWorkflowManageBinding>() {
 
     private fun setSelectAllListener() {
         binding.selectAll.setOnCheckedChangeListener { v, checked ->
-            viewModel.workflowModeData.forEach { it.isSelected = checked }
+            viewModel.workflowModeData.filter { !it.isPreset }.forEach { it.isSelected = checked }
             binding.listRv.adapter?.notifyDataSetChanged()
         }
     }
 
     private fun BindingAdapter.BindingViewHolder.onListDataBinding(holder: BindingAdapter.BindingViewHolder) {
-        val itemBinding = holder.getBinding<ItemJobManageBinding>()
-        val item = holder.getModel<JobTicketManageVo>()
-        itemBinding.jobName.text = item.ticketName
-        itemBinding.status.text = JobTicketStatusEnum.getTicketStatusStr(item.ticketStatus)
-        itemBinding.select.setOnCheckedChangeListener(null)
-        itemBinding.select.isChecked = item.isSelected
+        val itemBinding = holder.getBinding<ItemWorkflowManageBinding>()
+        val item = holder.getModel<WorkflowModeVo>()
+        itemBinding.name.text = item.modeName
+        itemBinding.status.isChecked = item.status
+        itemBinding.status.isEnabled = item.isPreset == false
+        itemBinding.select.isEnabled = item.isPreset == false
+        itemBinding.status.setOnCheckedChangeListener { v, checked ->
+            item.status = checked
+            viewModel.updateWorkflowMode(item).observe(this@WorkflowManageFragment) {
+                if (it) {
+                    showToast(getString(R.string.workflow_mode_status_update_succeed))
+                } else {
+                    showToast(getString(R.string.workflow_mode_status_update_failed))
+                }
+            }
+        }
+        itemBinding.select.setOnCheckedChangeListener { v, checked ->
+            item.isSelected = checked
+            binding.selectAll.setOnCheckedChangeListener(null)
+            binding.selectAll.isChecked =
+                viewModel.workflowModeData.filter { !it.isPreset }.all { it.isSelected }
+            setSelectAllListener()
+        }
+        itemBinding.root.setDebouncedClickListener {
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_WORKFLOW_SETTING_MODE_ID,
+                    item.modeId
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_TITLE_DATA,
+                    CommonUtils.getStr(R.string.workflow_manage_title).toString()
+                )
+            GlobalDataTempStore.getInstance()
+                .saveData(
+                    DataTransferConstants.KEY_PREVIEW_STEP_ICON_DATA,
+                    R.mipmap.icon_workflow_setting
+                )
+            navController.navigate(R.id.action_workflowManageFragment_to_workflowSettingFragment)
+        }
     }
 
     override fun onResume() {

+ 5 - 5
app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SetFaceFragment.kt

@@ -118,14 +118,14 @@ class SetFaceFragment : BaseFragment<FragmentSetFaceBinding>() {
         viewModel.getFaceData().observe(this) {
             binding.faceViewLayout.isVisible = true
             binding.faceSetLayout.isVisible = false
-            binding.faceNotSetIv.isVisible = viewModel.sysBiometricDataVo.isEmpty()
-            binding.faceSetIv.isVisible = viewModel.sysBiometricDataVo.isNotEmpty()
-            if (viewModel.sysBiometricDataVo.isEmpty()) {
+            binding.faceNotSetIv.isVisible = viewModel.faceData.isEmpty()
+            binding.faceSetIv.isVisible = viewModel.faceData.isNotEmpty()
+            if (viewModel.faceData.isEmpty()) {
                 binding.setOrResetFace.text = getString(R.string.set_data_tv)
                 binding.faceSetTipTv.text = getString(R.string.face_not_set_tip)
             } else {
-                logger.info("人脸图片地址:${viewModel.sysBiometricDataVo.toJson()}")
-                val faceFile = viewModel.sysBiometricDataVo[0].content.file()
+                logger.info("人脸图片地址:${viewModel.faceData.toJson()}")
+                val faceFile = viewModel.faceData[0].content.file()
                 if (faceFile.exists()) {
                     val faceBase64 = faceFile.readText()
                     val faceBitmap = ImageConvertUtils.base64ToBitmap(faceBase64)

+ 69 - 80
app/src/main/java/com/grkj/iscs/features/main/fragment/user_info/SetFingerprintFragment.kt

@@ -2,37 +2,29 @@ package com.grkj.iscs.features.main.fragment.user_info
 
 import android.graphics.Bitmap
 import android.graphics.Color
+import android.widget.TextView
 import androidx.fragment.app.viewModels
 import com.drake.brv.annotaion.DividerOrientation
 import com.drake.brv.utils.divider
 import com.drake.brv.utils.linear
 import com.drake.brv.utils.models
 import com.drake.brv.utils.setup
-import com.grkj.data.data.CommonConstants
-import com.grkj.data.data.MainDomainData
 import com.grkj.data.model.vo.SysBiometricDataVo
-import com.grkj.data.utils.FileStorageUtils
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.FragmentSetFingerprintBinding
 import com.grkj.iscs.databinding.ItemSetFingerprintBinding
 import com.grkj.iscs.features.main.dialog.user_info.AddFingerprintDialog
 import com.grkj.iscs.features.main.viewmodel.user_info.UserInfoViewModel
 import com.grkj.ui_base.base.BaseFragment
-import com.grkj.ui_base.config.ISCSConfig
 import com.grkj.ui_base.dialog.TipDialog
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.fingerprint.FingerprintUtil
 import com.kongzue.dialogx.dialogs.CustomDialog
-import com.kongzue.dialogx.dialogs.PopTip
-import com.sik.sikcore.date.TimeUtils
 import com.sik.sikcore.extension.deleteIfExists
 import com.sik.sikcore.extension.setDebouncedClickListener
 import com.sik.sikimage.ImageConvertUtils
-import com.zkteco.android.biometric.module.fingerprintreader.FingerprintCaptureListener
-import com.zkteco.android.biometric.module.fingerprintreader.FingerprintConstant
-import com.zkteco.android.biometric.module.fingerprintreader.FingerprintSensor
-import com.zkteco.zkfinger.FingerprintService
 import dagger.hilt.android.AndroidEntryPoint
+import java.util.UUID
 
 /**
  * 设置指纹
@@ -41,6 +33,16 @@ import dagger.hilt.android.AndroidEntryPoint
 class SetFingerprintFragment : BaseFragment<FragmentSetFingerprintBinding>() {
     private val viewModel: UserInfoViewModel by viewModels()
     private var fingerprintTempData = mutableListOf<ByteArray>()
+
+    private var mFingerprintLimit: Int = 5
+    private var mFingerprintPressTimes: Int = 0
+    private var mFingerprintInputErrorTimes: Int = 0
+    private var mFingerprintGroupName: String = ""
+    private val maxPressTimes = 3
+    private val inputFingerprintErrorTimes = 3
+    private var pressTip: TextView? = null
+    private val inputFingerprintIds: MutableList<Long> = mutableListOf()
+
     override fun getLayoutId(): Int {
         return R.layout.fragment_set_fingerprint
     }
@@ -51,8 +53,12 @@ class SetFingerprintFragment : BaseFragment<FragmentSetFingerprintBinding>() {
         }
         binding.add.setDebouncedClickListener {
             fingerprintTempData.clear()
-            AddFingerprintDialog.show {
+            AddFingerprintDialog.show({
                 FingerprintUtil.stop()
+
+                it.dismiss()
+            }) {
+                pressTip = it
             }.apply {
                 startCaptureFingerprint(this)
             }
@@ -64,12 +70,15 @@ class SetFingerprintFragment : BaseFragment<FragmentSetFingerprintBinding>() {
                 ).toString(),
                 countDownTime = 10,
                 onConfirmClick = {
-                    viewModel.sysBiometricDataVo.filter { it.isSelected }.forEach { item ->
-                        if (item.content.isNotEmpty()) {
-                            item.content.deleteIfExists()
+                    viewModel.fingerprintData.filter { it.isSelected }.forEach { item ->
+                        if (item.fingerprintData.isNotEmpty()) {
+                            item.fingerprintData.forEach {
+                                it.content.deleteIfExists()
+                            }
                         }
                     }
-                    viewModel.deleteFingerprintByIds(viewModel.sysBiometricDataVo.map { it.recordId })
+                    viewModel.deleteFingerprintByIds(viewModel.fingerprintData.flatMap { it.fingerprintData }
+                        .map { it.recordId })
                         .observe(this) {
                             if (it) {
                                 TipDialog.showSuccess(getString(R.string.delete_success))
@@ -115,7 +124,7 @@ class SetFingerprintFragment : BaseFragment<FragmentSetFingerprintBinding>() {
                 itemBinding.select.setOnCheckedChangeListener { _, checked ->
                     item.isSelected = checked
                     binding.selectAll.setOnCheckedChangeListener(null)
-                    binding.selectAll.isChecked = viewModel.sysBiometricDataVo.all { it.isSelected }
+                    binding.selectAll.isChecked = viewModel.fingerprintData.all { it.isSelected }
                     setSelectAllListener()
                 }
             }
@@ -126,77 +135,57 @@ class SetFingerprintFragment : BaseFragment<FragmentSetFingerprintBinding>() {
     private fun startCaptureFingerprint(dialog: CustomDialog) {
         FingerprintUtil.init(requireContext())
         FingerprintUtil.start()
+        mFingerprintGroupName = UUID.randomUUID().toString()
         FingerprintUtil.setScanListener(object : FingerprintUtil.OnScanListener {
             override fun onScan(bitmap: Bitmap) {
-                if (ISCSConfig.isNetVersion) {
-                    FingerprintUtil.stop()
-                    val saveFileName =
-                        "${MainDomainData.userInfo?.userId}_fingerprint_${
-                            TimeUtils.nowString(
-                                "yyyyMMddHHmmss"
-                            )
-                        }"
-                    FileStorageUtils.writeText(
-                        CommonConstants.FINGERPRINT_FOLDER,
-                        saveFileName,
-                        ImageConvertUtils.bitmapToBase64(bitmap).toString()
-                    )
-                    val savePath = FileStorageUtils.getFilePath(
-                        CommonConstants.FINGERPRINT_FOLDER,
-                        saveFileName
-                    )
-                    viewModel.saveUserFingerprint(savePath)
-                        .observe(this@SetFingerprintFragment) {
+                viewModel.saveUserFingerprint(
+                    ImageConvertUtils.bitmapToBase64(bitmap) ?: "",
+                    mFingerprintGroupName
+                ).observe(this@SetFingerprintFragment) {
+                    if (it != null) {
+                        logger.info("添加指纹:${it}")
+                        inputFingerprintIds.add(it)
+                        mFingerprintPressTimes++
+                        if (mFingerprintPressTimes == maxPressTimes) {
+                            dialog?.dismiss()
+                            showToast(getString(com.grkj.ui_base.R.string.fingerprint_add_success_tip))
                             getData()
-                            TipDialog.showSuccess(getString(com.grkj.ui_base.R.string.save_success))
-                            dialog.dismiss()
-                        }
-                }
-
-            }
-
-            override fun onScan(temp: ByteArray?) {
-                if (!ISCSConfig.isNetVersion && temp != null) {
-                    fingerprintTempData.add(temp)
-                    logger.info("指纹数量:${fingerprintTempData.size}-${temp.size}")
-                    if (fingerprintTempData.size == 3) {
-                        val newFingerprintTemp = ByteArray(FingerprintConstant.MAX_DATA_LENGTH)
-                        val mergeCode = FingerprintService.merge(
-                            fingerprintTempData[0],
-                            fingerprintTempData[1],
-                            fingerprintTempData[2],
-                            newFingerprintTemp
-                        )
-                        logger.info("指纹模板长度:${newFingerprintTemp.size}-${mergeCode}")
-                        if (newFingerprintTemp.isNotEmpty()) {
-                            val saveFileName =
-                                "${MainDomainData.userInfo?.userId}_fingerprint_${
-                                    TimeUtils.nowString(
-                                        "yyyyMMddHHmmss"
-                                    )
-                                }"
-                            FileStorageUtils.writeBytes(
-                                CommonConstants.FINGERPRINT_FOLDER,
-                                saveFileName,
-                                newFingerprintTemp
-                            )
-                            val savePath = FileStorageUtils.getFilePath(
-                                CommonConstants.FINGERPRINT_FOLDER,
-                                saveFileName
+                        } else if (mFingerprintInputErrorTimes == inputFingerprintErrorTimes) {
+                            mFingerprintGroupName = UUID.randomUUID().toString()
+                            mFingerprintPressTimes = 0
+                            mFingerprintInputErrorTimes = 0
+                            pressTip?.text = getString(
+                                com.grkj.ui_base.R.string.fingerprint_scan_tip,
+                                maxPressTimes - mFingerprintPressTimes
                             )
-                            viewModel.saveUserFingerprint(savePath)
+                            viewModel.deleteFingerprintByIds(inputFingerprintIds)
                                 .observe(this@SetFingerprintFragment) {
-                                    FingerprintUtil.stop()
                                     getData()
-                                    TipDialog.showSuccess(getString(com.grkj.ui_base.R.string.save_success))
-                                    dialog.dismiss()
                                 }
+                            showToast(getString(R.string.please_re_press_fingerprint_again))
                         } else {
-                            PopTip.tip(R.string.fingerprint_register_failed)
-                            fingerprintTempData.clear()
+                            pressTip?.text = getString(
+                                com.grkj.ui_base.R.string.fingerprint_scan_tip,
+                                maxPressTimes - mFingerprintPressTimes
+                            )
+                            showToast(getString(R.string.please_press_fingerprint_again))
                         }
                     } else {
-                        PopTip.tip(R.string.please_press_the_fingerprint_again)
+                        mFingerprintInputErrorTimes++
+                        if (mFingerprintInputErrorTimes == inputFingerprintErrorTimes) {
+                            mFingerprintGroupName = UUID.randomUUID().toString()
+                            mFingerprintPressTimes = 0
+                            mFingerprintInputErrorTimes = 0
+                            pressTip?.text = getString(
+                                com.grkj.ui_base.R.string.fingerprint_scan_tip,
+                                maxPressTimes - mFingerprintPressTimes
+                            )
+                            viewModel.deleteFingerprintByIds(inputFingerprintIds)
+                                .observe(this@SetFingerprintFragment) {
+                                    getData()
+                                }
+                            showToast(getString(R.string.please_re_press_fingerprint_again))
+                        }
                     }
                 }
             }
@@ -205,7 +194,7 @@ class SetFingerprintFragment : BaseFragment<FragmentSetFingerprintBinding>() {
 
     private fun setSelectAllListener() {
         binding.selectAll.setOnCheckedChangeListener { v, checked ->
-            viewModel.sysBiometricDataVo.forEach { it.isSelected = checked }
+            viewModel.fingerprintData.forEach { it.isSelected = checked }
             binding.listRv.adapter?.notifyDataSetChanged()
         }
     }
@@ -217,12 +206,12 @@ class SetFingerprintFragment : BaseFragment<FragmentSetFingerprintBinding>() {
 
     private fun getData() {
         viewModel.getFingerprintData().observe(this) {
-            if (viewModel.sysBiometricDataVo.isEmpty()) {
+            if (viewModel.fingerprintData.isEmpty()) {
                 binding.state.showEmpty()
             } else {
                 binding.state.showContent()
             }
-            binding.listRv.models = viewModel.sysBiometricDataVo
+            binding.listRv.models = viewModel.fingerprintData
         }
     }
 }

+ 123 - 3
app/src/main/java/com/grkj/iscs/features/main/viewmodel/WorkflowViewModel.kt

@@ -2,21 +2,37 @@ package com.grkj.iscs.features.main.viewmodel
 
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.liveData
+import com.google.common.reflect.TypeToken
+import com.google.gson.Gson
+import com.grkj.data.data.CommonConstants
 import com.grkj.data.model.dos.WorkflowMode
 import com.grkj.data.model.vo.WorkflowModeVo
 import com.grkj.data.repository.IWorkflowRepository
-import com.grkj.data.repository.impl.standard.WorkflowRepository
+import com.grkj.iscs.R
+import com.grkj.iscs.features.main.entity.WorkflowExportPackage
+import com.grkj.shared.config.AESConfig
 import com.grkj.ui_base.base.BaseViewModel
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.grkj.ui_base.utils.event.LoadingEvent
 import com.sik.sikcore.data.BeanUtils
+import com.sik.sikcore.extension.file
+import com.sik.sikcore.zip.ZipListener
+import com.sik.sikcore.zip.ZipUtils
+import com.sik.sikencrypt.EncryptUtils
+import com.sik.sikencrypt.MessageDigestTypes
+import com.sik.sikencrypt.MessageDigestUtils
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.Dispatchers
+import java.io.File
 import javax.inject.Inject
 
 /**
  * 流程模式
  */
 @HiltViewModel
-class WorkflowViewModel @Inject constructor(val workflowRepository: IWorkflowRepository) : BaseViewModel() {
+class WorkflowViewModel @Inject constructor(val workflowRepository: IWorkflowRepository) :
+    BaseViewModel() {
     /**
      * 流程模式数据
      */
@@ -38,7 +54,111 @@ class WorkflowViewModel @Inject constructor(val workflowRepository: IWorkflowRep
      */
     fun deleteSelectedWorkflowMode(): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
-            workflowRepository
+            workflowRepository.updateWorkflowModeDeleted(
+                workflowModeData.filter { it.isSelected }.map { it.modeId },
+                true
+            )
+        }
+    }
+
+    fun updateWorkflowMode(item: WorkflowModeVo): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            val isSuccess = workflowRepository.updateWorkflowMode(
+                BeanUtils.copyProperties(
+                    item,
+                    WorkflowMode::class.java
+                )
+            )
+            emit(isSuccess)
+        }
+    }
+
+    /**
+     * 导入流程模式
+     */
+    fun importWorkflowMode(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            try {
+                CommonConstants.workflowModeZipFilePath.file().apply {
+                    ZipUtils.unzip(this, this.parentFile!!, object : ZipListener {
+                        override fun error(errorMsg: String) {
+                            LoadingEvent.sendLoadingEvent()
+                            showTip(errorMsg)
+                        }
+
+                        override fun progress(progress: Float) {
+                            LoadingEvent.sendLoadingEvent(
+                                CommonUtils.getStr(
+                                    R.string.unzip,
+                                    progress
+                                )
+                            )
+                        }
+
+                        override fun unzipEnd(fileList: MutableList<File>) {
+                            val workflowModeData = fileList.find { it.name.endsWith(".dat") }
+                            val workflowModeSha = fileList.find { it.name.endsWith(".sha") }
+                            if (workflowModeSha == null) {
+                                LoadingEvent.sendLoadingEvent()
+                                TipDialog.showError(
+                                    CommonUtils.getStr(R.string.the_verification_file_not_exists)
+                                        .toString()
+                                )
+                                return
+                            }
+                            if (workflowModeData == null) {
+                                LoadingEvent.sendLoadingEvent()
+                                TipDialog.showError(
+                                    CommonUtils.getStr(R.string.data_file_not_exists).toString()
+                                )
+                                return
+                            }
+                            if (MessageDigestUtils.getMode(MessageDigestTypes.SHA256)
+                                    .digestToHex(workflowModeData.readBytes()) != workflowModeSha.readText()
+                            ) {
+                                TipDialog.showError(
+                                    CommonUtils.getStr(R.string.data_file_is_corrupted).toString()
+                                )
+                                return
+                            }
+                            try {
+                                val workflowModeDataJson =
+                                    EncryptUtils.getAlgorithm(AESConfig.defaultConfig)
+                                        .decryptFromHex(workflowModeData.readText())
+                                logger.info("流程模式字符串: $workflowModeDataJson")
+                                try {
+                                    val workflowExportPackage =
+                                        Gson().fromJson<WorkflowExportPackage<List<WorkflowModeVo>>>(
+                                            workflowModeDataJson,
+                                            object :
+                                                TypeToken<WorkflowExportPackage<List<WorkflowModeVo>>>() {}.type
+                                        )
+
+                                } catch (e: Exception) {
+                                    LoadingEvent.sendLoadingEvent()
+                                    TipDialog.showError(
+                                        CommonUtils.getStr(R.string.data_content_error).toString()
+                                    )
+                                }
+                            } catch (e: Exception) {
+                                LoadingEvent.sendLoadingEvent()
+                                TipDialog.showError(
+                                    CommonUtils.getStr(R.string.data_decrypt_failed).toString()
+                                )
+                            }
+
+                        }
+
+                        override fun zipEnd(file: File) {
+
+                        }
+
+                    })
+                }
+            } catch (e: Exception) {
+                showTip(CommonUtils.getStr(R.string.file_not_exists).toString())
+            }
+            emit(true)
         }
     }
 }

+ 21 - 7
app/src/main/java/com/grkj/iscs/features/main/viewmodel/user_info/UserInfoViewModel.kt

@@ -5,6 +5,7 @@ import androidx.lifecycle.liveData
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.model.dos.IsJobCard
 import com.grkj.data.model.dos.SysUserCharacteristicDo
+import com.grkj.data.model.vo.FingerprintDataVo
 import com.grkj.data.model.vo.SysBiometricDataVo
 import com.grkj.data.repository.IHardwareRepository
 import com.grkj.data.repository.IUserRepository
@@ -23,7 +24,13 @@ class UserInfoViewModel @Inject constructor(
     /**
      * 生物数据
      */
-    var sysBiometricDataVo: MutableList<SysBiometricDataVo> = mutableListOf()
+    var fingerprintData: List<FingerprintDataVo> =
+        mutableListOf()
+
+    /**
+     * 人脸数据
+     */
+    var faceData: List<SysBiometricDataVo> = mutableListOf()
 
     /**
      * 工卡数据
@@ -62,8 +69,14 @@ class UserInfoViewModel @Inject constructor(
      */
     fun getFingerprintData(): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
-            sysBiometricDataVo =
+            fingerprintData =
                 userRepository.getFingerprintDataByUserId(MainDomainData.userInfo?.userId)
+                    .groupBy { it.group }.map {
+                        FingerprintDataVo().apply {
+                            group = it.key
+                            fingerprintData = it.value
+                        }
+                    }
             emit(true)
         }
     }
@@ -73,7 +86,7 @@ class UserInfoViewModel @Inject constructor(
      */
     fun getFaceData(): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
-            sysBiometricDataVo =
+            faceData =
                 userRepository.getFaceDataByUserId(MainDomainData.userInfo?.userId)
             emit(true)
         }
@@ -92,14 +105,15 @@ class UserInfoViewModel @Inject constructor(
     /**
      * 保存用户指纹
      */
-    fun saveUserFingerprint(savePath: String): LiveData<Boolean> {
+    fun saveUserFingerprint(b64: String, group: String): LiveData<Long> {
         return liveData(Dispatchers.IO) {
             val sysUserCharacteristicDo = SysUserCharacteristicDo()
             sysUserCharacteristicDo.userId = MainDomainData.userInfo?.userId!!
-            sysUserCharacteristicDo.content = savePath
+            sysUserCharacteristicDo.content = b64
             sysUserCharacteristicDo.type = "1"
-            userRepository.saveUserCharacteristic(sysUserCharacteristicDo)
-            emit(true)
+            sysUserCharacteristicDo.group = group
+            val fingerprintId = userRepository.saveUserCharacteristic(sysUserCharacteristicDo)
+            emit(fingerprintId)
         }
     }
 

+ 1 - 0
app/src/main/res/layout/dialog_add_fingerprint.xml

@@ -17,6 +17,7 @@
             android:tint="@color/black" />
 
         <TextView
+            android:id="@+id/press_tip"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_marginTop="@dimen/common_spacing"

+ 7 - 2
app/src/main/res/layout/fragment_workflow_manage.xml

@@ -69,7 +69,8 @@
                 android:paddingHorizontal="@dimen/common_spacing_2x"
                 android:text="@string/delete"
                 android:textColor="@color/black"
-                android:textSize="@dimen/common_btn_text_size" />
+                android:textSize="@dimen/common_btn_text_size"
+                android:visibility="gone" />
 
             <TextView
                 android:id="@+id/import_workflow"
@@ -97,13 +98,15 @@
                 android:layout_width="30dp"
                 android:layout_height="30dp"
                 android:layout_gravity="center"
-                android:layout_margin="@dimen/common_spacing" />
+                android:layout_margin="@dimen/common_spacing"
+                android:visibility="gone" />
 
             <TextView
                 android:layout_width="0dp"
                 android:layout_height="match_parent"
                 android:layout_weight="1"
                 android:gravity="center"
+                android:paddingVertical="@dimen/common_spacing"
                 android:text="@string/workflow_name"
                 android:textSize="@dimen/common_text_size" />
 
@@ -112,6 +115,7 @@
                 android:layout_height="match_parent"
                 android:layout_weight="1"
                 android:gravity="center"
+                android:paddingVertical="@dimen/common_spacing"
                 android:text="@string/status"
                 android:textSize="@dimen/common_text_size" />
 
@@ -120,6 +124,7 @@
                 android:layout_height="match_parent"
                 android:layout_weight="1"
                 android:gravity="center"
+                android:paddingVertical="@dimen/common_spacing"
                 android:text="@string/detail"
                 android:textSize="@dimen/common_text_size" />
         </LinearLayout>

+ 51 - 0
app/src/main/res/layout/item_workflow_manage.xml

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:divider="@drawable/divider_table"
+        android:showDividers="middle">
+
+        <CheckBox
+            android:id="@+id/select"
+            android:layout_width="30dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:visibility="gone"
+            android:layout_margin="@dimen/common_spacing" />
+
+        <TextView
+            android:id="@+id/name"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:ellipsize="end"
+            android:gravity="center"
+            android:singleLine="true"
+            android:textSize="@dimen/common_text_size" />
+
+        <FrameLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1">
+
+            <com.google.android.material.materialswitch.MaterialSwitch
+                android:id="@+id/status"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_gravity="center"
+                android:gravity="center"
+                android:textSize="@dimen/common_text_size" />
+        </FrameLayout>
+
+        <TextView
+            android:id="@+id/view"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:text="@string/user_manage_view"
+            android:textSize="@dimen/common_text_size" />
+    </LinearLayout>
+</layout>

+ 14 - 0
app/src/main/res/values-en/strings.xml

@@ -513,5 +513,19 @@
     <string name="workflow_manage_title">Workflow manager</string>
     <string name="workflow_name">Workflow name</string>
     <string name="import_str">Import</string>
+    <string name="workflow_mode_status_update_failed">Status update failed</string>
+    <string name="workflow_mode_status_update_succeed">Status update succeed</string>
+    <string name="check_delete_workflow_mode">Are you sure you want to delete the selected process mode</string>
+    <string name="workflow_mode_manage_delete_succeed">The process mode has been successfully deleted</string>
+    <string name="please_re_press_fingerprint_again">Please re-press your fingerprint</string>
+    <string name="please_press_fingerprint_again">Please press fingerprint again</string>
+    <string name="group_job_in_progress">Group job in progress</string>
+    <string name="file_not_exists">File not exists</string>
+    <string name="unzip">Unzip...%s</string>
+    <string name="the_verification_file_not_exists">The verification file does not exist</string>
+    <string name="data_file_not_exists">Data file not exists</string>
+    <string name="data_file_is_corrupted">Data file is corrupted</string>
+    <string name="data_decrypt_failed">Data decrypt failed</string>
+    <string name="data_content_error">Data content error</string>
 
 </resources>

+ 14 - 0
app/src/main/res/values-zh/strings.xml

@@ -513,5 +513,19 @@
     <string name="workflow_manage_title">流程模式管理</string>
     <string name="workflow_name">流程模式名称</string>
     <string name="import_str">导入</string>
+    <string name="workflow_mode_status_update_failed">状态修改失败</string>
+    <string name="workflow_mode_status_update_succeed">状态修改成功</string>
+    <string name="check_delete_workflow_mode">确定要删除选中的流程模式吗</string>
+    <string name="workflow_mode_manage_delete_succeed">删除流程模式成功</string>
+    <string name="please_re_press_fingerprint_again">请重新按压指纹</string>
+    <string name="please_press_fingerprint_again">请再次按压指纹</string>
+    <string name="group_job_in_progress">分组作业进行中</string>
+    <string name="file_not_exists">文件不存在</string>
+    <string name="unzip">解压中...%s</string>
+    <string name="the_verification_file_not_exists">校验文件不存在</string>
+    <string name="data_file_not_exists">数据文件不存在</string>
+    <string name="data_file_is_corrupted">数据文件已损坏</string>
+    <string name="data_decrypt_failed">数据解密失败</string>
+    <string name="data_content_error">数据内容错误</string>
 
 </resources>

+ 14 - 0
app/src/main/res/values/strings.xml

@@ -516,5 +516,19 @@
     <string name="workflow_manage_title">流程模式管理</string>
     <string name="workflow_name">流程模式名称</string>
     <string name="import_str">导入</string>
+    <string name="workflow_mode_status_update_failed">状态修改失败</string>
+    <string name="workflow_mode_status_update_succeed">状态修改成功</string>
+    <string name="check_delete_workflow_mode">确定要删除选中的流程模式吗</string>
+    <string name="workflow_mode_manage_delete_succeed">删除流程模式成功</string>
+    <string name="please_re_press_fingerprint_again">请重新按压指纹</string>
+    <string name="please_press_fingerprint_again">请再次按压指纹</string>
+    <string name="group_job_in_progress">分组作业进行中</string>
+    <string name="file_not_exists">文件不存在</string>
+    <string name="unzip">解压中...%.2f</string>
+    <string name="the_verification_file_not_exists">校验文件不存在</string>
+    <string name="data_file_not_exists">数据文件不存在</string>
+    <string name="data_file_is_corrupted">数据文件已损坏</string>
+    <string name="data_decrypt_failed">数据解密失败</string>
+    <string name="data_content_error">数据内容错误</string>
 
 </resources>

+ 1 - 1
data/src/main/java/com/grkj/data/dao/UserDao.kt

@@ -267,7 +267,7 @@ interface UserDao {
      * 保存用户生物数据
      */
     @Insert(onConflict = OnConflictStrategy.REPLACE)
-    fun saveUserCharacteristic(saveUserCharacteristic: SysUserCharacteristicDo)
+    fun saveUserCharacteristic(saveUserCharacteristic: SysUserCharacteristicDo): Long
 
     /**
      * 根据用户id删除人脸数据

+ 13 - 2
data/src/main/java/com/grkj/data/dao/WorkflowStepDao.kt

@@ -50,7 +50,8 @@ interface WorkflowStepDao {
      * 判定在同一模式下 "解锁" 步骤是否排在 "上锁" 步骤之前
      * 通过比较两者的最小 step_index
      */
-    @Query("""
+    @Query(
+        """
     SELECT CASE
       -- 如果没有“解锁”步骤,直接返回 false
       WHEN (SELECT MIN(step_index)
@@ -72,7 +73,8 @@ interface WorkflowStepDao {
         THEN 1
       ELSE 0
     END
-    """)
+    """
+    )
     fun isUnlockBeforeLock(modeId: Long): Boolean
 
 
@@ -81,4 +83,13 @@ interface WorkflowStepDao {
 
     @Query("select * from is_workflow_mode where mode_id = :modeId")
     fun getWorkflowModeByModeId(modeId: Long): WorkflowMode?
+
+    @Update
+    fun updateWorkflowMode(workflowMode: WorkflowMode)
+
+    /**
+     * 删除流程模式
+     */
+    @Query("update is_workflow_mode set deleted = :deleted where mode_id in (:workflowModeId)")
+    fun updateWorkflowModeDeleted(workflowModeId: List<Long>,deleted: Boolean)
 }

+ 2 - 0
data/src/main/java/com/grkj/data/data/CommonConstants.kt

@@ -33,4 +33,6 @@ object CommonConstants {
      * 头像文件夹
      */
     const val AVATAR_FOLDER = "avatar"
+
+    val workflowModeZipFilePath = "/sdcard/iscs/workflowMode.zip"
 }

+ 41 - 8
data/src/main/java/com/grkj/data/database/ISCSMigrations.kt

@@ -11,12 +11,12 @@ object ISCSMigrations {
     /**
      * 版本号
      */
-    const val VERSION = 2
+    const val VERSION = 4
 
     /**
      * 升级数据
      */
-    val migrationData: Array<Migration> by lazy { arrayOf(migration1To2) }
+    val migrationData: Array<Migration> by lazy { arrayOf(migration1To2, migration2To3,migration3To4) }
 
     /**
      * 数据库升级
@@ -25,7 +25,8 @@ object ISCSMigrations {
         override fun migrate(database: SupportSQLiteDatabase) {
 
             // 1. 创建新表(不包含 workflow_step_id,加入新字段)
-            database.execSQL("""
+            database.execSQL(
+                """
             CREATE TABLE IF NOT EXISTS is_job_ticket_step_new (
                 step_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
                 ticket_id INTEGER NOT NULL,
@@ -62,9 +63,11 @@ object ISCSMigrations {
                 update_time TEXT,
                 remark TEXT
             );
-        """.trimIndent())
+        """.trimIndent()
+            )
 
-            database.execSQL("""
+            database.execSQL(
+                """
             INSERT INTO is_job_ticket_step_new (
                 step_id, ticket_id, step_index, step_status,
                 step_content, android_step_content, del_flag
@@ -73,12 +76,14 @@ object ISCSMigrations {
             SELECT step_id, ticket_id, step_index, step_status,
                    step_content, android_step_content, del_flag
             FROM is_job_ticket_step;
-        """.trimIndent())
+        """.trimIndent()
+            )
 
             database.execSQL("DROP TABLE is_job_ticket_step;")
             database.execSQL("ALTER TABLE is_job_ticket_step_new RENAME TO is_job_ticket_step;")
             // 5. 增加 is_sop_workflow_step 表
-            database.execSQL("""
+            database.execSQL(
+                """
             CREATE TABLE IF NOT EXISTS is_sop_workflow_step (
                 step_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
                 sop_id INTEGER NOT NULL,
@@ -110,7 +115,35 @@ object ISCSMigrations {
                 deleted INTEGER NOT NULL DEFAULT 0,
                 tenant_id INTEGER NOT NULL DEFAULT 0
             );
-        """.trimIndent())
+        """.trimIndent()
+            )
+        }
+    }
+
+    val migration2To3 = object : Migration(2, 3) {
+        override fun migrate(db: SupportSQLiteDatabase) {
+            db.execSQL(
+                """
+                  ALTER TABLE is_workflow_mode
+                  ADD COLUMN status INTEGER NOT NULL DEFAULT 0
+                """.trimIndent()
+            )
+            db.execSQL("""
+                UPDATE is_workflow_mode
+                SET status = 1
+                WHERE is_preset = 1
+            """.trimIndent())
+        }
+    }
+
+    val migration3To4= object : Migration(3, 4) {
+        override fun migrate(db: SupportSQLiteDatabase) {
+            db.execSQL(
+                """
+                  ALTER TABLE sys_user_characteristic
+                    ADD COLUMN `group` TEXT
+                """.trimIndent()
+            )
         }
     }
 }

+ 2 - 0
data/src/main/java/com/grkj/data/model/dos/SysUserCharacteristicDo.kt

@@ -26,6 +26,8 @@ open class SysUserCharacteristicDo : BaseBean() {
     @ColumnInfo("type")
     var type: String = ""
 
+    var group: String? = null
+
     @ColumnInfo("content")
     var content: String = ""
 

+ 5 - 0
data/src/main/java/com/grkj/data/model/dos/WorkflowMode.kt

@@ -42,6 +42,11 @@ open class WorkflowMode {
     @ColumnInfo(name = "is_colock_support")
     var isColockSupport: Boolean = false
 
+    /**
+     * 是否启用
+     */
+    var status: Boolean = false
+
     /** 创建者 */
     @ColumnInfo(name = "creator")
     var creator: String? = MainDomainData.userInfo?.userName

+ 21 - 0
data/src/main/java/com/grkj/data/model/vo/FingerprintDataVo.kt

@@ -0,0 +1,21 @@
+package com.grkj.data.model.vo
+
+import androidx.room.Ignore
+
+/**
+ * 指纹数据
+ */
+class FingerprintDataVo {
+    /**
+     * 分组
+     */
+    var group: String? = null
+
+    /**
+     * 指纹数据
+     */
+    var fingerprintData: List<SysBiometricDataVo> = mutableListOf()
+
+    @Ignore
+    var isSelected = false
+}

+ 2 - 4
data/src/main/java/com/grkj/data/repository/IUserRepository.kt

@@ -33,13 +33,11 @@ interface IUserRepository {
      * 指纹登录
      */
     fun loginWithFingerprint(fingerprint: String): Boolean
-    fun loginWithFingerprint(fingerprint: ByteArray): Boolean
 
     /**
      * 检查指纹
      */
     fun checkFingerprint(fingerprint: String): Boolean
-    fun checkFingerprint(fingerprint: ByteArray): Boolean
 
     /**
      * 人脸登录
@@ -68,7 +66,7 @@ interface IUserRepository {
     /**
      * 根据用户id获取用户数据
      */
-    fun getJobUserDataByUserIdAndTicketId(ticketId:Long,userIds: List<Long>): List<JobUserVo>
+    fun getJobUserDataByUserIdAndTicketId(ticketId: Long, userIds: List<Long>): List<JobUserVo>
 
     /**
      * 获取所有用户数据
@@ -133,7 +131,7 @@ interface IUserRepository {
     /**
      * 保存用户数据
      */
-    fun saveUserCharacteristic(sysUserCharacteristicDo: SysUserCharacteristicDo)
+    fun saveUserCharacteristic(sysUserCharacteristicDo: SysUserCharacteristicDo): Long
 
     /**
      * 根据用户id删除人脸数据

+ 10 - 0
data/src/main/java/com/grkj/data/repository/IWorkflowRepository.kt

@@ -44,4 +44,14 @@ interface IWorkflowRepository {
      * 获取流程模式
      */
     fun getWorkflowModes(): List<WorkflowMode>
+
+    /**
+     * 更新流程模式
+     */
+    fun updateWorkflowMode(workflowMode: WorkflowMode?): Boolean
+
+    /**
+     * 删除流程模式
+     */
+    fun updateWorkflowModeDeleted(workflowModeId: List<Long>, deleted: Boolean)
 }

+ 1 - 9
data/src/main/java/com/grkj/data/repository/impl/network/NetworkUserRepository.kt

@@ -38,18 +38,10 @@ class NetworkUserRepository @Inject constructor() : BaseRepository(), IUserRepos
         TODO("Not yet implemented")
     }
 
-    override fun loginWithFingerprint(fingerprint: ByteArray): Boolean {
-        TODO("Not yet implemented")
-    }
-
     override fun checkFingerprint(fingerprint: String): Boolean {
         TODO("Not yet implemented")
     }
 
-    override fun checkFingerprint(fingerprint: ByteArray): Boolean {
-        TODO("Not yet implemented")
-    }
-
     override fun loginWithFace(face: String): Boolean {
         TODO("Not yet implemented")
     }
@@ -137,7 +129,7 @@ class NetworkUserRepository @Inject constructor() : BaseRepository(), IUserRepos
         TODO("Not yet implemented")
     }
 
-    override fun saveUserCharacteristic(sysUserCharacteristicDo: SysUserCharacteristicDo) {
+    override fun saveUserCharacteristic(sysUserCharacteristicDo: SysUserCharacteristicDo): Long {
         TODO("Not yet implemented")
     }
 

+ 8 - 0
data/src/main/java/com/grkj/data/repository/impl/network/NetworkWorkflowRepository.kt

@@ -47,4 +47,12 @@ class NetworkWorkflowRepository @Inject constructor()  : BaseRepository(), IWork
     override fun getWorkflowModes(): List<WorkflowMode> {
         TODO("Not yet implemented")
     }
+
+    override fun updateWorkflowMode(workflowMode: WorkflowMode?): Boolean {
+        TODO("Not yet implemented")
+    }
+
+    override fun updateWorkflowModeDeleted(workflowModeId: List<Long>, deleted: Boolean) {
+        TODO("Not yet implemented")
+    }
 }

+ 13 - 34
data/src/main/java/com/grkj/data/repository/impl/standard/UserRepository.kt

@@ -40,7 +40,7 @@ class UserRepository @Inject constructor(
     override fun loginWithAccount(
         username: String, password: String
     ): Boolean {
-        var sysUserDo = userDao.getUserInfoByUsername(username)
+        val sysUserDo = userDao.getUserInfoByUsername(username)
         if (sysUserDo == null) {
             return false
         }
@@ -68,8 +68,10 @@ class UserRepository @Inject constructor(
 
     override fun loginWithCard(cardnfc: String): Boolean {
         val userId = hardwareDao.getUserIdByCardNfc(cardnfc)?.toString()
-        return userId?.let {
-            val sysUserDo = userDao.getUserInfoByUserId(it)
+        if (userId == null) {
+            return false
+        } else {
+            val sysUserDo = userDao.getUserInfoByUserId(userId)
             if (sysUserDo != null) {
                 MainDomainData.userInfo = sysUserDo
                 val userCardList = hardwareDao.getIsJobCardByUserId(sysUserDo.userId)
@@ -84,8 +86,8 @@ class UserRepository @Inject constructor(
                 logger.info("用户角色:{}", MainDomainData.roleKeys)
                 logger.info("用户权限:{}", MainDomainData.permissions)
             }
-            return userDao.getUserInfoByUserId(it) != null
-        } ?: false
+            return userDao.getUserInfoByUserId(userId) != null
+        }
     }
 
     override fun checkCard(cardNo: String): Boolean {
@@ -97,15 +99,6 @@ class UserRepository @Inject constructor(
     }
 
     override fun loginWithFingerprint(fingerprint: String): Boolean {
-        val fingerprintDataList = userDao.getFingerprintData()
-        if (fingerprintDataList.isEmpty()) {
-            return false
-        }
-        var hasFingerprint = false
-        return hasFingerprint
-    }
-
-    override fun loginWithFingerprint(fingerprint: ByteArray): Boolean {
         val fingerprintDataList = userDao.getFingerprintData()
         if (fingerprintDataList.isEmpty()) {
             return false
@@ -114,7 +107,7 @@ class UserRepository @Inject constructor(
         var userId: String? = null
         for (fingerprintData in fingerprintDataList) {
             if (fingerprintData.content.isNotEmpty()) {
-                val fileData = fingerprintData.content.file().readBytes()
+                val fileData = fingerprintData.content
                 if (BiometricVerifier.verifyFingerprint(fingerprint, fileData)) {
                     hasFingerprint = true
                     userId = fingerprintData.userId.toString()
@@ -148,15 +141,6 @@ class UserRepository @Inject constructor(
     }
 
     override fun checkFingerprint(fingerprint: String): Boolean {
-        val fingerprintDataList = userDao.getFingerprintData()
-        if (fingerprintDataList.isEmpty()) {
-            return false
-        }
-        var hasFingerprint = false
-        return hasFingerprint
-    }
-
-    override fun checkFingerprint(fingerprint: ByteArray): Boolean {
         val fingerprintDataList = userDao.getFingerprintData()
         if (fingerprintDataList.isEmpty()) {
             return false
@@ -165,9 +149,8 @@ class UserRepository @Inject constructor(
         var userId: String? = null
         for (fingerprintData in fingerprintDataList) {
             if (fingerprintData.content.isNotEmpty()) {
-                val fileData = fingerprintData.content.file().readBytes()
+                val fileData = fingerprintData.content.file().readText()
                 if (BiometricVerifier.verifyFingerprint(fingerprint, fileData)) {
-                    hasFingerprint = true
                     userId = fingerprintData.userId.toString()
                     break
                 }
@@ -175,11 +158,7 @@ class UserRepository @Inject constructor(
         }
         if (userId != null) {
             val sysUserDo = userDao.getUserInfoByUserId(userId)
-            hasFingerprint = if (sysUserDo != null) {
-                true
-            } else {
-                false
-            }
+            hasFingerprint = sysUserDo != null
         } else {
             hasFingerprint = false
         }
@@ -282,7 +261,7 @@ class UserRepository @Inject constructor(
         ticketId: Long,
         userIds: List<Long>
     ): List<JobUserVo> {
-        return userDao.getJobUserDataByUserIdAndTicketId(ticketId,userIds)
+        return userDao.getJobUserDataByUserIdAndTicketId(ticketId, userIds)
     }
 
     override fun getAllUserDataWithWorkstation(workstationId: Long): List<UserManageVo> {
@@ -366,8 +345,8 @@ class UserRepository @Inject constructor(
         userDao.deleteFingerprintByIds(fingerprintIds)
     }
 
-    override fun saveUserCharacteristic(sysUserCharacteristicDo: SysUserCharacteristicDo) {
-        userDao.saveUserCharacteristic(sysUserCharacteristicDo)
+    override fun saveUserCharacteristic(sysUserCharacteristicDo: SysUserCharacteristicDo): Long {
+        return userDao.saveUserCharacteristic(sysUserCharacteristicDo)
     }
 
     override fun deleteFaceDataByUserId(userId: Long) {

+ 14 - 2
data/src/main/java/com/grkj/data/repository/impl/standard/WorkflowRepository.kt

@@ -23,7 +23,19 @@ class WorkflowRepository @Inject constructor(val workflowStepDao: WorkflowStepDa
     override fun getStepsByMode(modeId: Long): List<WorkflowStep> =
         workflowStepDao.getStepsByMode(modeId)
 
-    override fun getWorkflowModeByModeId(modeId: Long): WorkflowMode? = workflowStepDao.getWorkflowModeByModeId(modeId)
+    override fun getWorkflowModeByModeId(modeId: Long): WorkflowMode? =
+        workflowStepDao.getWorkflowModeByModeId(modeId)
+
+    override fun updateWorkflowMode(workflowMode: WorkflowMode?): Boolean {
+        return workflowMode?.let {
+            workflowStepDao.updateWorkflowMode(it)
+            true
+        } ?: false
+    }
+
+    override fun updateWorkflowModeDeleted(workflowModeId: List<Long>, deleted: Boolean) {
+        workflowStepDao.updateWorkflowModeDeleted(workflowModeId, deleted)
+    }
 
     override fun getNextStepIndex(modeId: Long): Int = workflowStepDao.getNextStepIndex(modeId)
 
@@ -32,5 +44,5 @@ class WorkflowRepository @Inject constructor(val workflowStepDao: WorkflowStepDa
     override fun isUnlockBeforeLock(modeId: Long): Boolean =
         workflowStepDao.isUnlockBeforeLock(modeId)
 
-    override fun getWorkflowModes(): List<WorkflowMode>  = workflowStepDao.getWorkflowModes()
+    override fun getWorkflowModes(): List<WorkflowMode> = workflowStepDao.getWorkflowModes()
 }

+ 18 - 2
shared/src/main/java/com/grkj/shared/utils/BiometricVerifier.kt

@@ -1,5 +1,8 @@
 package com.grkj.shared.utils
 
+import android.util.Base64
+import com.machinezoo.sourceafis.FingerprintMatcher
+import com.machinezoo.sourceafis.FingerprintTemplate
 import com.zkteco.zkfinger.FingerprintService
 
 object BiometricVerifier {
@@ -10,8 +13,21 @@ object BiometricVerifier {
      * @param b64b 指纹2 的 Base64 图像
      * @return 两者是否认为同一人(score >= threshold)
      */
-    fun verifyFingerprint(b64a: ByteArray, b64b: ByteArray, threshold: Double = 23.0): Boolean {
-        val score = FingerprintService.verify(b64a, b64b)
+    fun verifyFingerprint(b64a: String, b64b: String, threshold: Double = 40.0): Boolean {
+        // 1. 解码成 Bitmap
+        val bmpA = Base64.decode(b64a, Base64.DEFAULT)
+        val bmpB = Base64.decode(b64b, Base64.DEFAULT)
+
+        // 2. 构建指纹模板
+        val templateA = FingerprintTemplate()
+            .dpi(500.0) // 根据图像 DPI 调整
+            .create(bmpA)
+        val templateB = FingerprintTemplate()
+            .dpi(500.0)
+            .create(bmpB)
+
+        // 3. 比对
+        val score = FingerprintMatcher(templateA).match(templateB)
         return score >= threshold
     }
 

+ 0 - 13
ui-base/src/main/java/com/grkj/ui_base/base/BaseViewModel.kt

@@ -58,19 +58,6 @@ open class BaseViewModel constructor(
         }
     }
 
-    /**
-     * 检查指纹
-     */
-    fun checkFinger(fingerB64: ByteArray?): LiveData<Boolean> {
-        return liveData(Dispatchers.IO) {
-            if (fingerB64 == null) {
-                emit(false)
-            } else {
-                emit(userRepository?.checkFingerprint(fingerB64) == true)
-            }
-        }
-    }
-
     /**
      * 检查卡片
      */

+ 0 - 10
ui-base/src/main/java/com/grkj/ui_base/utils/fingerprint/FingerprintUtil.kt

@@ -121,14 +121,6 @@ object FingerprintUtil {
             }
 
             override fun extractOK(fpTemplate: ByteArray?) {
-                ThreadUtils.runOnMain {
-                    onScanListener?.onScan(fpTemplate)
-                }
-                if (bRegister) {
-                    doRegister(fpTemplate)
-                } else {
-                    doIdentify(fpTemplate)
-                }
             }
 
             override fun extractError(i: Int) {
@@ -324,7 +316,5 @@ object FingerprintUtil {
 
     interface OnScanListener {
         fun onScan(bitmap: Bitmap)
-
-        fun onScan(temp: ByteArray?)
     }
 }

+ 1 - 1
ui-base/src/main/res/values-en/strings.xml

@@ -228,7 +228,7 @@
     <string name="fingerprint_config_tip">Up to 3 fingerprints can be added</string>
     <string name="fingerprint">Fingerprint</string>
     <string name="fingerprint_delete_confirm_tip">Confirm to delete %s?</string>
-    <string name="fingerprint_scan_tip">Please press fingerprint sensor</string>
+    <string name="fingerprint_scan_tip">Please press the fingerprint recognition area %d times in succession</string>
     <string name="fingerprint_add_success_tip">Fingerprint added successfully</string>
     <string name="exception_type_tip">Please select exception type</string>
     <string name="exception_level_tip">Please select severity level</string>

+ 1 - 1
ui-base/src/main/res/values-zh/strings.xml

@@ -228,7 +228,7 @@
     <string name="fingerprint_config_tip">最多可以添加3个指纹数据</string>
     <string name="fingerprint">指纹</string>
     <string name="fingerprint_delete_confirm_tip">确定要删除%s吗?</string>
-    <string name="fingerprint_scan_tip">请按压指纹识别区</string>
+    <string name="fingerprint_scan_tip">请连续按压%d次指纹识别区</string>
     <string name="fingerprint_add_success_tip">已成功添加指纹数据</string>
     <string name="exception_type_tip">请选择异常类型</string>
     <string name="exception_level_tip">请选择异常等级</string>

+ 1 - 1
ui-base/src/main/res/values/strings.xml

@@ -228,7 +228,7 @@
     <string name="fingerprint_config_tip">最多可以添加3个指纹数据</string>
     <string name="fingerprint">指纹</string>
     <string name="fingerprint_delete_confirm_tip">确定要删除%s吗?</string>
-    <string name="fingerprint_scan_tip">请按压指纹识别区</string>
+    <string name="fingerprint_scan_tip">请连续按压%d次指纹识别区</string>
     <string name="fingerprint_add_success_tip">已成功添加指纹数据</string>
     <string name="exception_type_tip">请选择异常类型</string>
     <string name="exception_level_tip">请选择异常等级</string>