浏览代码

add(新增)
- 硬件管理下预添加(钥匙管理、卡片管理、挂锁管理、rfid标签管理),界面基本完成,逻辑完成未测试

周文健 5 月之前
父节点
当前提交
ec5ee0fd77
共有 94 个文件被更改,包括 6817 次插入48 次删除
  1. 28 3
      app/src/main/java/com/grkj/iscs/ISCSApplication.kt
  2. 142 3
      app/src/main/java/com/grkj/iscs/features/init/fragment/InitDeviceRegistrationKeyAndLockFragment.kt
  3. 28 0
      app/src/main/java/com/grkj/iscs/features/init/model/DockData.kt
  4. 256 0
      app/src/main/java/com/grkj/iscs/features/init/viewmodel/InitDeviceRegistrationKeyAndLockViewModel.kt
  5. 1 7
      app/src/main/java/com/grkj/iscs/features/init/viewmodel/InitViewModel.kt
  6. 2 0
      app/src/main/java/com/grkj/iscs/features/login/activity/LoginActivity.kt
  7. 67 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/AddCardDialog.kt
  8. 77 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/AddKeyDialog.kt
  9. 67 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/AddLockDialog.kt
  10. 67 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/AddRfidTokenDialog.kt
  11. 51 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/FilterCardDialog.kt
  12. 53 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/FilterKeyDialog.kt
  13. 51 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/FilterLockDialog.kt
  14. 51 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/FilterRfidTokenDialog.kt
  15. 81 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/UpdateCardDialog.kt
  16. 91 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/UpdateKeyDialog.kt
  17. 81 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/UpdateLockDialog.kt
  18. 81 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/UpdateRfidTokenDialog.kt
  19. 161 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/CardManageFragment.kt
  20. 6 2
      app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/HardwareManageHomeFragment.kt
  21. 221 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/KeyManageFragment.kt
  22. 169 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/LockManageFragment.kt
  23. 166 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/RfidTokenManageFragment.kt
  24. 81 0
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/CardManageViewModel.kt
  25. 83 0
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/KeyManageViewModel.kt
  26. 78 0
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/LockManageViewModel.kt
  27. 78 0
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/RfidTokenManageViewModel.kt
  28. 6 0
      app/src/main/res/drawable/common_btn_green_bg.xml
  29. 6 0
      app/src/main/res/drawable/common_btn_red_bg.xml
  30. 8 0
      app/src/main/res/drawable/common_status_circle.xml
  31. 21 0
      app/src/main/res/drawable/dock_has_lock.xml
  32. 5 0
      app/src/main/res/drawable/dock_key_selector.xml
  33. 5 0
      app/src/main/res/drawable/dock_lock_selector.xml
  34. 9 0
      app/src/main/res/drawable/dock_no_lock.xml
  35. 203 0
      app/src/main/res/layout/dialog_add_card.xml
  36. 233 0
      app/src/main/res/layout/dialog_add_key.xml
  37. 203 0
      app/src/main/res/layout/dialog_add_lock.xml
  38. 203 0
      app/src/main/res/layout/dialog_add_rfid_token.xml
  39. 178 0
      app/src/main/res/layout/dialog_filter_card.xml
  40. 205 0
      app/src/main/res/layout/dialog_filter_key.xml
  41. 178 0
      app/src/main/res/layout/dialog_filter_lock.xml
  42. 178 0
      app/src/main/res/layout/dialog_filter_rfid_token.xml
  43. 203 0
      app/src/main/res/layout/dialog_update_card.xml
  44. 233 0
      app/src/main/res/layout/dialog_update_key.xml
  45. 203 0
      app/src/main/res/layout/dialog_update_lock.xml
  46. 203 0
      app/src/main/res/layout/dialog_update_rfid_token.xml
  47. 155 0
      app/src/main/res/layout/fragment_card_manage.xml
  48. 155 0
      app/src/main/res/layout/fragment_key_manage.xml
  49. 155 0
      app/src/main/res/layout/fragment_lock_manage.xml
  50. 155 0
      app/src/main/res/layout/fragment_rfid_token_manage.xml
  51. 40 0
      app/src/main/res/layout/item_card_manage.xml
  52. 44 0
      app/src/main/res/layout/item_device_registration_key.xml
  53. 8 0
      app/src/main/res/layout/item_device_registration_key_layout.xml
  54. 28 0
      app/src/main/res/layout/item_device_registration_lock.xml
  55. 8 0
      app/src/main/res/layout/item_device_registration_lock_layout.xml
  56. 8 0
      app/src/main/res/layout/item_device_registration_portable_layout.xml
  57. 40 0
      app/src/main/res/layout/item_key_manage.xml
  58. 40 0
      app/src/main/res/layout/item_lock_manage.xml
  59. 40 0
      app/src/main/res/layout/item_rfid_token_manage.xml
  60. 二进制
      app/src/main/res/mipmap-hdpi/dock_has_key.png
  61. 二进制
      app/src/main/res/mipmap-hdpi/dock_no_key.png
  62. 30 1
      app/src/main/res/navigation/nav_hardware_manage.xml
  63. 89 0
      app/src/main/res/values-en/strings.xml
  64. 89 0
      app/src/main/res/values-zh/strings.xml
  65. 89 0
      app/src/main/res/values/strings.xml
  66. 186 14
      data/src/main/java/com/grkj/data/dao/HardwareDao.kt
  67. 6 0
      data/src/main/java/com/grkj/data/dao/UserDao.kt
  68. 5 0
      data/src/main/java/com/grkj/data/data/EventConstants.kt
  69. 6 0
      data/src/main/java/com/grkj/data/model/dos/BaseBean.kt
  70. 2 0
      data/src/main/java/com/grkj/data/model/dos/IsJobCard.kt
  71. 11 0
      data/src/main/java/com/grkj/data/model/vo/AddCardDataVo.kt
  72. 12 0
      data/src/main/java/com/grkj/data/model/vo/AddKeyDataVo.kt
  73. 11 0
      data/src/main/java/com/grkj/data/model/vo/AddLockDataVo.kt
  74. 11 0
      data/src/main/java/com/grkj/data/model/vo/AddRfidTokenDataVo.kt
  75. 10 0
      data/src/main/java/com/grkj/data/model/vo/CardManageFilterVo.kt
  76. 11 0
      data/src/main/java/com/grkj/data/model/vo/KeyManageFilterVo.kt
  77. 10 0
      data/src/main/java/com/grkj/data/model/vo/LockManageFilterVo.kt
  78. 10 0
      data/src/main/java/com/grkj/data/model/vo/RfidTokenManageFilterVo.kt
  79. 12 0
      data/src/main/java/com/grkj/data/model/vo/UpdateCardDataVo.kt
  80. 13 0
      data/src/main/java/com/grkj/data/model/vo/UpdateKeyDataVo.kt
  81. 12 0
      data/src/main/java/com/grkj/data/model/vo/UpdateLockDataVo.kt
  82. 12 0
      data/src/main/java/com/grkj/data/model/vo/UpdateRfidTokenDataVo.kt
  83. 103 0
      data/src/main/java/com/grkj/data/repository/IHardwareRepository.kt
  84. 5 0
      data/src/main/java/com/grkj/data/repository/IUserRepository.kt
  85. 142 6
      data/src/main/java/com/grkj/data/repository/impl/HardwareRepository.kt
  86. 4 0
      data/src/main/java/com/grkj/data/repository/impl/UserRepository.kt
  87. 二进制
      shared/libs/adh_series_sdk.jar
  88. 1 0
      ui-base/build.gradle.kts
  89. 25 0
      ui-base/src/main/java/com/grkj/ui_base/business/BleBusinessManager.kt
  90. 176 2
      ui-base/src/main/java/com/grkj/ui_base/utils/ble/BleConnectionManager.kt
  91. 1 6
      ui-base/src/main/java/com/grkj/ui_base/utils/ble/BleUtil.kt
  92. 24 0
      ui-base/src/main/java/com/grkj/ui_base/utils/event/StartModbusEvent.kt
  93. 14 0
      ui-base/src/main/java/com/grkj/ui_base/utils/modbus/ModBusController.kt
  94. 8 4
      ui-base/src/main/java/com/grkj/ui_base/utils/modbus/PortManager.kt

+ 28 - 3
app/src/main/java/com/grkj/iscs/ISCSApplication.kt

@@ -3,8 +3,11 @@ package com.grkj.iscs
 import android.R
 import android.app.Application
 import android.content.Context
+import com.grkj.data.data.EventConstants
 import com.grkj.data.di.RepositoryManager
+import com.grkj.shared.model.EventBean
 import com.grkj.ui_base.business.ModbusBusinessManager
+import com.grkj.ui_base.utils.ble.BleUtil
 import com.grkj.ui_base.utils.modbus.ModBusController
 import com.kongzue.dialogx.DialogX
 import com.scwang.smart.refresh.footer.ClassicsFooter
@@ -18,6 +21,9 @@ import com.scwang.smart.refresh.layout.listener.DefaultRefreshHeaderCreator
 import com.sik.sikcore.SIKCore
 import com.sik.sikcore.thread.ThreadUtils
 import dagger.hilt.android.HiltAndroidApp
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
 
 
 /**
@@ -34,14 +40,33 @@ class ISCSApplication : Application() {
         SIKCore.init(this)
         //todo 模拟器不支持
 //        ArcSoftUtil.checkActiveStatus(this)
-
+        if (!EventBus.getDefault().isRegistered(this)) {
+            EventBus.getDefault().register(this)
+        }
         ThreadUtils.runOnIO {
-            ModBusController.initDevicesStatus()
+            BleUtil.instance.initBle(this@ISCSApplication)
             RepositoryManager.init(this@ISCSApplication)
-            ModbusBusinessManager.registerMainListener()
         }
     }
 
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    open fun onEvent(event: EventBean<*>) {
+        when (event.code) {
+            EventConstants.EVENT_START_MODBUS -> {
+                ModBusController.start()
+                ModBusController.initDevicesStatus()
+                ModbusBusinessManager.registerMainListener()
+            }
+        }
+    }
+
+    override fun onTerminate() {
+        if (EventBus.getDefault().isRegistered(this)) {
+            EventBus.getDefault().unregister(this)
+        }
+        super.onTerminate()
+    }
+
     //static 代码段可以防止内存泄露
     init {
 

+ 142 - 3
app/src/main/java/com/grkj/iscs/features/init/fragment/InitDeviceRegistrationKeyAndLockFragment.kt

@@ -1,10 +1,24 @@
 package com.grkj.iscs.features.init.fragment
 
+import android.widget.LinearLayout
+import androidx.core.view.isVisible
 import androidx.fragment.app.viewModels
+import com.drake.brv.BindingAdapter
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
 import com.grkj.iscs.R
 import com.grkj.iscs.databinding.FragmentInitDeviceRegistrationKeyAndLockBinding
-import com.grkj.iscs.features.init.viewmodel.InitViewModel
+import com.grkj.iscs.databinding.ItemDeviceRegistrationKeyBinding
+import com.grkj.iscs.databinding.ItemDeviceRegistrationKeyLayoutBinding
+import com.grkj.iscs.databinding.ItemDeviceRegistrationLockBinding
+import com.grkj.iscs.databinding.ItemDeviceRegistrationLockLayoutBinding
+import com.grkj.iscs.features.init.model.DockData
+import com.grkj.iscs.features.init.viewmodel.InitDeviceRegistrationKeyAndLockViewModel
 import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.utils.modbus.DeviceConst
+import com.grkj.ui_base.utils.modbus.DockBean
+import com.grkj.ui_base.utils.modbus.ModBusController
 import com.sik.sikcore.extension.setDebouncedClickListener
 import dagger.hilt.android.AndroidEntryPoint
 
@@ -14,17 +28,142 @@ import dagger.hilt.android.AndroidEntryPoint
 @AndroidEntryPoint
 class InitDeviceRegistrationKeyAndLockFragment :
     BaseFragment<FragmentInitDeviceRegistrationKeyAndLockBinding>() {
-    private val viewModel: InitViewModel by viewModels()
+    private val viewModel: InitDeviceRegistrationKeyAndLockViewModel by viewModels()
     override fun getLayoutId(): Int {
         return R.layout.fragment_init_device_registration_key_and_lock
     }
 
     override fun initView() {
+        viewModel.openAndDetectSlave()
+        //打开所有钥匙仓并关闭充电
+        viewModel.registerInitListener().observe(this) {
+            logger.debug("设备录入-初始化检测任务分发完成")
+        }
+        binding.reRecognize.setDebouncedClickListener {
+            viewModel.isStartCheckKey = false
+        }
         binding.previousBtn.setDebouncedClickListener {
             navController.popBackStack()
         }
         binding.nextBtn.setDebouncedClickListener {
-            navController.navigate(R.id.action_initDeviceRegistrationKeyAndLockFragment_to_initCardRegistrationFragment)
+            viewModel.deviceRegistrationData().observe(this) {
+                navController.navigate(R.id.action_initDeviceRegistrationKeyAndLockFragment_to_initCardRegistrationFragment)
+            }
+        }
+        binding.dockRv.linear().setup {
+            addType<DockData.KeyDock>(R.layout.item_device_registration_key_layout)
+            addType<DockData.LockDock>(R.layout.item_device_registration_lock_layout)
+            addType<DockData.PortableDock>(R.layout.item_device_registration_portable_layout)
+            onBind {
+                when (val model = getModel<Any>()) {
+                    is DockData.KeyDock -> {
+                        onKeyDockRVListBinding(model)
+                    }
+
+                    is DockData.LockDock -> {
+                        onLockDockRVListBinding(model)
+                    }
+
+                    is DockData.PortableDock -> {
+                        onPortableDockRVListBinding(model)
+                    }
+                }
+            }
         }
+        viewModel.isLoadComplete.observe(this) {
+            val dockData =
+                ModBusController.dockList.filter { it.type == DeviceConst.DOCK_TYPE_LOCK || it.type == DeviceConst.DOCK_TYPE_KEY || it.type == DeviceConst.DOCK_TYPE_PORTABLE }
+            val keyDock = dockData.filter { it.type == DeviceConst.DOCK_TYPE_KEY }.map {
+                DockData.KeyDock().apply {
+                    keyData.addAll(it.deviceList.filterIsInstance<DockBean.KeyBean>())
+                }
+            }
+            val lockDock = dockData.filter { it.type == DeviceConst.DOCK_TYPE_LOCK }.map {
+                DockData.LockDock().apply {
+                    lockData.addAll(it.deviceList.filterIsInstance<DockBean.LockBean>())
+                }
+            }
+            val portableDock = dockData.filter { it.type == DeviceConst.DOCK_TYPE_PORTABLE }.map {
+                DockData.PortableDock().apply {
+                    deviceData.addAll(it.deviceList.toList())
+                }
+            }
+            binding.dockRv.models = keyDock + portableDock + lockDock
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onLockDockRVListBinding(
+        lockDock: DockData.LockDock,
+    ) {
+        val itemBinding = getBinding<ItemDeviceRegistrationLockLayoutBinding>()
+        itemBinding.rvLockLayout.linear(LinearLayout.HORIZONTAL)
+            .setup {
+                addType<DockBean.LockBean>(R.layout.item_device_registration_lock)
+                onBind {
+                    val itemLockBinding = getBinding<ItemDeviceRegistrationLockBinding>()
+                    val itemLock = getModel<DockBean.LockBean>()
+                    itemLockBinding.tvNewDevice.isVisible = itemLock.newHardware
+                    itemLockBinding.root.isSelected = itemLock.isExist
+                }
+            }.models = lockDock.lockData
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onKeyDockRVListBinding(
+        keyDock: DockData.KeyDock,
+    ) {
+        val itemBinding = getBinding<ItemDeviceRegistrationKeyLayoutBinding>()
+        itemBinding.rvKeyLayout.linear(LinearLayout.HORIZONTAL)
+            .setup {
+                addType<DockBean.KeyBean>(R.layout.item_device_registration_key)
+                onBind {
+                    val itemKeyBinding = getBinding<ItemDeviceRegistrationKeyBinding>()
+                    val itemKey = getModel<DockBean.KeyBean>()
+                    itemKeyBinding.tvNewDevice.isVisible = itemKey.newHardware
+                    itemKeyBinding.tvNewDeviceMac.isVisible = itemKey.mac?.isNotEmpty() == true
+                    itemKeyBinding.tvNewDeviceMac.text = itemKey.mac
+                    itemKeyBinding.ivKey.isSelected = itemKey.isExist
+                }
+            }.models = keyDock.keyData
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onPortableDockRVListBinding(
+        portableDock: DockData.PortableDock,
+    ) {
+        val itemBinding = getBinding<ItemDeviceRegistrationKeyLayoutBinding>()
+        itemBinding.rvKeyLayout.linear(LinearLayout.HORIZONTAL)
+            .setup {
+                addType<DockBean.LockBean>(R.layout.item_device_registration_lock)
+                addType<DockBean.KeyBean>(R.layout.item_device_registration_key)
+                onBind {
+                    when (val itemPortable = getModel<Any>()) {
+                        is DockBean.KeyBean -> {
+                            val itemKeyBinding = getBinding<ItemDeviceRegistrationKeyBinding>()
+                            itemKeyBinding.tvNewDevice.isVisible = itemPortable.newHardware
+                            itemKeyBinding.tvNewDeviceMac.isVisible =
+                                itemPortable.mac?.isNotEmpty() == true
+                            itemKeyBinding.tvNewDeviceMac.text = itemPortable.mac
+                            itemKeyBinding.ivKey.isSelected = itemPortable.isExist
+                        }
+
+                        is DockBean.LockBean -> {
+                            val itemLockBinding = getBinding<ItemDeviceRegistrationLockBinding>()
+                            itemLockBinding.tvNewDevice.isVisible = itemPortable.newHardware
+                            itemLockBinding.root.isSelected = itemPortable.isExist
+                        }
+                    }
+                }
+            }.models = portableDock.deviceData
+    }
+
+    override fun onResume() {
+        super.onResume()
+        viewModel.clearKeyAndLock()
+        viewModel.isDestroy = false
+    }
+
+    override fun onDestroyView() {
+        viewModel.isDestroy = true
+        viewModel.unregisterInitListener()
+        super.onDestroyView()
     }
 }

+ 28 - 0
app/src/main/java/com/grkj/iscs/features/init/model/DockData.kt

@@ -0,0 +1,28 @@
+package com.grkj.iscs.features.init.model
+
+import com.grkj.ui_base.utils.modbus.DockBean
+
+/**
+ * Dock数据
+ */
+interface DockData {
+    /**
+     * 钥匙Dock
+     */
+    class KeyDock {
+        val keyData: MutableList<DockBean.KeyBean> = mutableListOf()
+    }
+
+    /**
+     * 挂锁Dock
+     */
+    class LockDock {
+        val lockData: MutableList<DockBean.LockBean> = mutableListOf()
+    }
+    /**
+     * 便携Dock
+     */
+    class PortableDock {
+        val deviceData: MutableList<DockBean.DeviceBean> = mutableListOf()
+    }
+}

+ 256 - 0
app/src/main/java/com/grkj/iscs/features/init/viewmodel/InitDeviceRegistrationKeyAndLockViewModel.kt

@@ -0,0 +1,256 @@
+package com.grkj.iscs.features.init.viewmodel
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.liveData
+import com.clj.fastble.BleManager
+import com.grkj.data.repository.IHardwareRepository
+import com.grkj.ui_base.base.BaseViewModel
+import com.grkj.ui_base.utils.ble.BleConnectionManager
+import com.grkj.ui_base.utils.modbus.DeviceConst
+import com.grkj.ui_base.utils.modbus.DockBean
+import com.grkj.ui_base.utils.modbus.ModBusController
+import com.sik.sikcore.thread.ThreadUtils
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.suspendCancellableCoroutine
+import java.util.concurrent.atomic.AtomicInteger
+import javax.inject.Inject
+import kotlin.coroutines.resume
+
+@HiltViewModel
+class InitDeviceRegistrationKeyAndLockViewModel @Inject constructor(val hardwareRepository: IHardwareRepository) :
+    BaseViewModel() {
+    val isLoadComplete: MutableLiveData<Boolean> = MutableLiveData(false)
+    var isStartCheckKey: Boolean = false
+    var isDestroy: Boolean = false
+    var newHardwareKeySize: Int = 0
+    var newHardwareLockSize: Int = 0
+    private val newHardwareKeyBean: MutableMap<Byte, MutableList<DockBean.KeyBean>> = mutableMapOf()
+    private val alreadyUsedMac: MutableList<String> = mutableListOf()
+
+    /**
+     * 打开并检测从机
+     */
+    fun openAndDetectSlave() {
+        ModBusController.start()
+    }
+
+    fun checkNewHardware(device: DockBean.DeviceBean, callback: () -> Unit) {
+        if (device is DockBean.KeyBean) {
+            device.mac = null
+            hardwareRepository.getKeyInfo(device.rfid.toString()) {
+                device.newHardware = it == null
+                device.mac = it?.macAddress
+                callback()
+            }
+        } else if (device is DockBean.LockBean) {
+            hardwareRepository.getLockInfo(device.rfid.toString()) {
+                device.newHardware = it == null
+                callback()
+            }
+        }
+    }
+
+    fun registerInitListener(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            isStartCheckKey = false
+            newHardwareKeyBean.clear()
+            alreadyUsedMac.clear()
+            isLoadComplete.postValue(false)
+            val allDeviceCloseCmdSend =
+                BleConnectionManager.scanOnlineKeyLockMacAndSwitchModeToClose()
+            logger.info("设备录入-是否所有关闭命令发送成功:${allDeviceCloseCmdSend}")
+            BleManager.getInstance().disconnectAllDevice()
+            ModBusController.registerStatusListener(this) {
+                if (isStartCheckKey) {
+                    return@registerStatusListener
+                }
+                isStartCheckKey = true
+                val dockList = ModBusController.dockList
+                ThreadUtils.runOnIO {
+                    val allDevice = ModBusController.dockList.map { it.deviceList }.flatten()
+                        .filter { it is DockBean.KeyBean || it is DockBean.LockBean }
+                        .filter { it.isExist }
+                    var currentCheckDevices = AtomicInteger(0)
+                    allDevice.forEach { device ->
+                        checkNewHardware(device) {
+                            currentCheckDevices.addAndGet(1)
+                            if (currentCheckDevices.get() == allDevice.size) {
+                                checkNewHardwareKey(dockList)
+                            }
+                        }
+                    }
+                    emit(true)
+                }
+            }
+        }
+    }
+
+    private fun checkNewHardwareKey(dockList: MutableList<DockBean>) {
+        ThreadUtils.runOnIO {
+            logger.info("设备录入-重新检测是否是新设备完成")
+            newHardwareKeyBean.putAll(dockList.filter { it.type == DeviceConst.DOCK_TYPE_KEY || it.type == DeviceConst.DOCK_TYPE_PORTABLE }
+                .associate {
+                    it.addr to it.deviceList.filterIsInstance<DockBean.KeyBean>()
+                        .filter { it.newHardware && it.isExist }
+                        .toMutableList()
+                })
+            alreadyUsedMac.addAll(
+                dockList.filter { it.type == DeviceConst.DOCK_TYPE_KEY || it.type == DeviceConst.DOCK_TYPE_PORTABLE }
+                    .map { it.deviceList }.flatten().filterIsInstance<DockBean.KeyBean>()
+                    .filter { it.mac?.isNotEmpty() == true }.mapNotNull { it.mac }
+            )
+            logger.debug("设备录入-新设备:${newHardwareKeyBean}")
+            for ((addr, keyBeans) in newHardwareKeyBean) {
+                for (keyBean in keyBeans) {
+                    if (isDestroy) {
+                        return@runOnIO
+                    }
+                    openChargeAndScanMac(addr, keyBean)
+                }
+            }
+            newHardwareKeySize = newHardwareKeyBean.map { it.value }.flatten().size
+            newHardwareLockSize =
+                ModBusController.dockList.filter { it.type == DeviceConst.DOCK_TYPE_LOCK || it.type == DeviceConst.DOCK_TYPE_PORTABLE }
+                    .map { it.deviceList }.flatten().filterIsInstance<DockBean.LockBean>()
+                    .count { it.newHardware && it.isExist }
+            isLoadComplete.postValue(true)
+        }
+    }
+
+    /**
+     * 打开充电并扫描蓝牙
+     */
+    suspend fun openChargeAndScanMac(addr: Byte, keyBean: DockBean.KeyBean): Boolean {
+        return suspendCancellableCoroutine<Boolean> { cont ->
+            logger.info("设备录入-关闭充电:${addr},${keyBean.idx}")
+            ModBusController.controlKeyCharge(false, keyBean.idx, addr) {
+                ThreadUtils.runOnIO {
+                    delay(800)
+                    logger.info("设备录入-打开充电:${addr},${keyBean.idx}")
+                    ModBusController.controlKeyLockAndCharge(true, keyBean.idx, addr) {
+                        ThreadUtils.runOnIO {
+                            delay(3000)
+                            logger.info("设备录入-开始扫描在线蓝牙Mac")
+                            BleConnectionManager.scanOnlineKeyLockMac { bleDevices ->
+                                logger.info(
+                                    "设备录入-在线的蓝牙设备:${keyBean.rfid},${
+                                        bleDevices?.joinToString(
+                                            ","
+                                        ) { it.mac }
+                                    }"
+                                )
+                                if (isDestroy) {
+                                    cont.cancel()
+                                    return@scanOnlineKeyLockMac
+                                }
+                                if (bleDevices?.isEmpty() == true) {
+                                    ThreadUtils.runOnIO {
+                                        openChargeAndScanMac(addr, keyBean)
+                                    }
+                                } else {
+                                    val bleDevice = bleDevices?.find {
+                                        it.mac !in alreadyUsedMac
+                                    }
+                                    if (bleDevice?.mac?.isEmpty() == true) {
+                                        ThreadUtils.runOnIO {
+                                            openChargeAndScanMac(addr, keyBean)
+                                        }
+                                        return@scanOnlineKeyLockMac
+                                    }
+                                    logger.info(
+                                        "设备录入-没有使用过的mac:${keyBean.rfid},${
+                                            bleDevice?.mac
+                                        }"
+                                    )
+                                    keyBean.mac = bleDevice?.mac!!
+                                    alreadyUsedMac.add(bleDevice.mac)
+                                    ThreadUtils.runOnIO {
+                                        keyBean.mac?.let {
+                                            val connected =
+                                                BleConnectionManager.tryConnectWithOptionalCharge(it)
+                                            if (connected) {
+                                                bleDevice?.let {
+                                                    BleConnectionManager.switchReadyMode(it)
+                                                }
+                                            }
+                                        }
+                                    }
+
+                                    cont.resume(true)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    fun unregisterInitListener() {
+        ModBusController.unregisterListener(this)
+    }
+
+    /**
+     * 设备录入挂起任务
+     */
+    private suspend fun deviceInputKeySuspend(keyNfc: String, keyMacAddress: String): Boolean {
+        return suspendCancellableCoroutine<Boolean> { cont ->
+            hardwareRepository.saveKeyInfo(keyNfc, keyMacAddress)
+            cont.resume(true)
+        }
+    }
+
+    /**
+     * 设备录入挂起任务
+     */
+    private suspend fun deviceInputLockSuspend(cardNo: String): Boolean {
+        return suspendCancellableCoroutine<Boolean> { cont ->
+            hardwareRepository.saveLockInfo(cardNo)
+            cont.resume(true)
+        }
+    }
+
+    /**
+     * 设备录入(钥匙和挂锁)
+     */
+    fun deviceRegistrationData(): LiveData<Triple<Boolean, Int, Int>> {
+        return liveData(Dispatchers.IO) {
+            val deviceList =
+                ModBusController.dockList.map { it.deviceList }.flatten()
+            val lockDevice = deviceList.filter { it.type == DeviceConst.DEVICE_TYPE_LOCK }
+                .filterIsInstance<DockBean.LockBean>().filter { it.newHardware == true }
+            val keyDevice = deviceList.filter { it.type == DeviceConst.DEVICE_TYPE_KEY }
+                .filterIsInstance<DockBean.KeyBean>().filter { it.newHardware == true }
+            lockDevice.filter { it.rfid?.isNotEmpty() == true }.forEach { lockDevice ->
+                val isBind = deviceInputLockSuspend(lockDevice.rfid.toString())
+                if (isBind) {
+                    lockDevice.newHardware = false
+                }
+            }
+            keyDevice.filter { it.rfid?.isNotEmpty() == true && it.mac?.isNotEmpty() == true }
+                .forEach { keyDevice ->
+                    val isBind =
+                        deviceInputKeySuspend(keyDevice.rfid.toString(), keyDevice.mac.toString())
+                    if (isBind) {
+                        keyDevice.newHardware = false
+                    }
+                }
+            emit(
+                Triple(
+                    true,
+                    keyDevice.count { it.rfid?.isNotEmpty() == true && it.mac?.isNotEmpty() == true },
+                    lockDevice.count { it.rfid?.isNotEmpty() == true })
+            )
+        }
+    }
+
+    fun clearKeyAndLock(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            hardwareRepository.clearKeyAndLock()
+            emit(true)
+        }
+    }
+}

+ 1 - 7
app/src/main/java/com/grkj/iscs/features/init/viewmodel/InitViewModel.kt

@@ -1,6 +1,7 @@
 package com.grkj.iscs.features.init.viewmodel
 
 import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.liveData
 import com.grkj.data.repository.IHardwareRepository
 import com.grkj.data.repository.IUserRepository
@@ -37,13 +38,6 @@ class InitViewModel @Inject constructor(
         }
     }
 
-    /**
-     * 注册硬件
-     */
-    fun registrationHardware() {
-
-    }
-
     /**
      * 注册卡片
      */

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

@@ -26,6 +26,7 @@ import com.grkj.shared.config.Constants
 import com.grkj.ui_base.base.BaseActivity
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.event.LoadingEvent
+import com.grkj.ui_base.utils.event.StartModbusEvent
 import com.grkj.ui_base.utils.extension.getAppVersionName
 import com.grkj.ui_base.utils.extension.toByteArrays
 import com.grkj.ui_base.utils.extension.toHexStrings
@@ -70,6 +71,7 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>() {
                 onLoginMenuBinding(this)
             }
         }
+        StartModbusEvent.sendStartModbusEvent()
     }
 
     private fun BindingAdapter.BindingViewHolder.onLoginMenuBinding(holder: BindingAdapter.BindingViewHolder) {

+ 67 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/AddCardDialog.kt

@@ -0,0 +1,67 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.vo.AddCardDataVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogAddCardBinding
+import com.grkj.ui_base.utils.extension.tipDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 新增卡片
+ */
+class AddCardDialog(context: Context) : BasePopupWindow(context) {
+    private lateinit var binding: DialogAddCardBinding
+    private var onConfirm: (AddCardDataVo) -> Unit = {}
+
+    init {
+        setContentView(R.layout.dialog_add_card)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogAddCardBinding.bind(contentView)
+
+        binding.cancel.setOnClickListener { dismiss() }
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            if (checkData()) {
+                val vo = AddCardDataVo(
+                    cardNfc   = binding.cardNfcEt.text.toString(),
+                    username  = binding.usernameEt.text.toString(),
+                    exStatus  = binding.statusRg.checkedRadioButtonId == binding.activateRb.id,
+                    exRemark  = binding.remarkEt.text.toString().takeIf { it.isNotBlank() }
+                )
+                onConfirm(vo)
+                // 清空
+                binding.cardNfcEt.setText("")
+                binding.usernameEt.setText("")
+                binding.remarkEt.setText("")
+                binding.statusRg.clearCheck()
+                dismiss()
+            }
+        }
+    }
+
+    private fun checkData(): Boolean {
+        if (binding.cardNfcEt.text.trim().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_card_nfc)
+            return false
+        }
+        if (binding.usernameEt.text.trim().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_card_nickname)
+            return false
+        }
+        if (binding.statusRg.checkedRadioButtonId == -1) {
+            PopTip.build().tipDialog(R.string.please_select_status)
+            return false
+        }
+        return true
+    }
+
+    fun setOnConfirmListener(listener: (AddCardDataVo) -> Unit) {
+        this.onConfirm = listener
+    }
+}

+ 77 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/AddKeyDialog.kt

@@ -0,0 +1,77 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.vo.AddKeyDataVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogAddKeyBinding
+import com.grkj.ui_base.utils.extension.tipDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 新增钥匙
+ */
+class AddKeyDialog(context: Context) : BasePopupWindow(context) {
+    private lateinit var binding: DialogAddKeyBinding
+    private var onConfirm: (AddKeyDataVo) -> Unit = {}
+
+    init {
+        setContentView(R.layout.dialog_add_key)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogAddKeyBinding.bind(contentView)
+        binding.cancel.setOnClickListener { dismiss() }
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            if (checkData()) {
+                val addUserData = AddKeyDataVo(
+                    binding.keyCodeEt.text.toString(),
+                    binding.keyNfcEt.text.toString(),
+                    binding.keyMacEt.text.toString(),
+                    binding.statusRg.checkedRadioButtonId == binding.activateRb.id,
+                    binding.remarkEt.text.toString()
+                )
+                onConfirm(addUserData)
+                binding.keyCodeEt.setText("")
+                binding.keyNfcEt.setText("")
+                binding.keyMacEt.setText("")
+                binding.remarkEt.setText("")
+                binding.statusRg.clearCheck()
+                dismiss()
+            }
+        }
+    }
+
+    /**
+     * 检查数据
+     */
+    private fun checkData(): Boolean {
+        if (binding.keyCodeEt.text.trim().toString().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_key_name)
+            return false
+        }
+        if (binding.keyNfcEt.text.trim().toString().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_key_nfc)
+            return false
+        }
+        if (binding.keyMacEt.text.trim().toString().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_key_mac)
+            return false
+        }
+        if (binding.statusRg.checkedRadioButtonId == -1) {
+            PopTip.build().tipDialog(R.string.please_select_status)
+            return false
+        }
+        return true
+    }
+
+    /**
+     * 设置确认监听
+     */
+    fun setOnConfirmListener(onConfirm: (AddKeyDataVo) -> Unit) {
+        this.onConfirm = onConfirm
+    }
+}

+ 67 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/AddLockDialog.kt

@@ -0,0 +1,67 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.vo.AddLockDataVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogAddLockBinding
+import com.grkj.ui_base.utils.extension.tipDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 新增挂锁
+ */
+class AddLockDialog(context: Context) : BasePopupWindow(context) {
+    private lateinit var binding: DialogAddLockBinding
+    private var onConfirm: (AddLockDataVo) -> Unit = {}
+
+    init {
+        setContentView(R.layout.dialog_add_lock)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogAddLockBinding.bind(contentView)
+
+        binding.cancel.setOnClickListener { dismiss() }
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            if (checkData()) {
+                val vo = AddLockDataVo(
+                    lockCode  = binding.lockCodeEt.text.toString(),
+                    lockNfc   = binding.lockNfcEt.text.toString(),
+                    exStatus  = binding.statusRg.checkedRadioButtonId == binding.activateRb.id,
+                    exRemark  = binding.remarkEt.text.toString().takeIf { it.isNotBlank() }
+                )
+                onConfirm(vo)
+                // 清空
+                binding.lockCodeEt.setText("")
+                binding.lockNfcEt.setText("")
+                binding.remarkEt.setText("")
+                binding.statusRg.clearCheck()
+                dismiss()
+            }
+        }
+    }
+
+    private fun checkData(): Boolean {
+        if (binding.lockCodeEt.text.trim().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_lock_code)
+            return false
+        }
+        if (binding.lockNfcEt.text.trim().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_lock_nfc)
+            return false
+        }
+        if (binding.statusRg.checkedRadioButtonId == -1) {
+            PopTip.build().tipDialog(R.string.please_select_status)
+            return false
+        }
+        return true
+    }
+
+    fun setOnConfirmListener(listener: (AddLockDataVo) -> Unit) {
+        this.onConfirm = listener
+    }
+}

+ 67 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/AddRfidTokenDialog.kt

@@ -0,0 +1,67 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.vo.AddRfidTokenDataVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogAddRfidTokenBinding
+import com.grkj.ui_base.utils.extension.tipDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 新增 RFID 标签
+ */
+class AddRfidTokenDialog(context: Context) : BasePopupWindow(context) {
+    private lateinit var binding: DialogAddRfidTokenBinding
+    private var onConfirm: (AddRfidTokenDataVo) -> Unit = {}
+
+    init {
+        setContentView(R.layout.dialog_add_rfid_token)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogAddRfidTokenBinding.bind(contentView)
+
+        binding.cancel.setOnClickListener { dismiss() }
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            if (checkData()) {
+                val vo = AddRfidTokenDataVo(
+                    rfidCode  = binding.rfidCodeEt.text.toString(),
+                    rfid      = binding.rfidEt.text.toString(),
+                    exStatus  = binding.statusRg.checkedRadioButtonId == binding.activateRb.id,
+                    exRemark  = binding.remarkEt.text.toString().takeIf { it.isNotBlank() }
+                )
+                onConfirm(vo)
+                // 清空
+                binding.rfidCodeEt.setText("")
+                binding.rfidEt.setText("")
+                binding.remarkEt.setText("")
+                binding.statusRg.clearCheck()
+                dismiss()
+            }
+        }
+    }
+
+    private fun checkData(): Boolean {
+        if (binding.rfidCodeEt.text.trim().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_rfid_code)
+            return false
+        }
+        if (binding.rfidEt.text.trim().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_rfid)
+            return false
+        }
+        if (binding.statusRg.checkedRadioButtonId == -1) {
+            PopTip.build().tipDialog(R.string.please_select_status)
+            return false
+        }
+        return true
+    }
+
+    fun setOnConfirmListener(listener: (AddRfidTokenDataVo) -> Unit) {
+        this.onConfirm = listener
+    }
+}

+ 51 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/FilterCardDialog.kt

@@ -0,0 +1,51 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.vo.CardManageFilterVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogFilterCardBinding
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 筛选卡片
+ */
+class FilterCardDialog(context: Context) : BasePopupWindow(context) {
+    private var onConfirm: (CardManageFilterVo) -> Unit = {}
+    private lateinit var binding: DialogFilterCardBinding
+
+    init {
+        setContentView(R.layout.dialog_filter_card)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogFilterCardBinding.bind(contentView)
+
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            val filter = CardManageFilterVo(
+                cardNfc  = binding.cardNfcEt.text.toString().takeIf { it.isNotBlank() },
+                username = binding.usernameEt.text.toString().takeIf { it.isNotBlank() },
+                status   = when (binding.statusRg.checkedRadioButtonId) {
+                    binding.activateRb.id   -> true
+                    binding.deactivateRb.id -> false
+                    else                    -> null
+                }
+            )
+            onConfirm(filter)
+            dismiss()
+            binding.cardNfcEt.setText("")
+            binding.usernameEt.setText("")
+            binding.statusRg.clearCheck()
+        }
+        binding.cancel.setOnClickListener { dismiss() }
+    }
+
+    /**
+     * 设置确认事件
+     */
+    fun setOnConfirmListener(listener: (CardManageFilterVo) -> Unit) {
+        this.onConfirm = listener
+    }
+}

+ 53 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/FilterKeyDialog.kt

@@ -0,0 +1,53 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.vo.KeyManageFilterVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogFilterKeyBinding
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 筛选钥匙
+ */
+class FilterKeyDialog(context: Context) : BasePopupWindow(context) {
+    private var onConfirm: (KeyManageFilterVo) -> Unit = {}
+    private lateinit var binding: DialogFilterKeyBinding
+
+    init {
+        setContentView(R.layout.dialog_filter_key)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogFilterKeyBinding.bind(contentView)
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            val filterData = KeyManageFilterVo(
+                binding.keyCodeEt.text.toString(),
+                binding.keyNfcEt.text.toString(),
+                binding.keyMacEt.text.toString(),
+                if (binding.statusRg.checkedRadioButtonId == -1) {
+                    null
+                } else {
+                    binding.statusRg.checkedRadioButtonId == binding.activateRb.id
+                }
+            )
+            onConfirm(filterData)
+            dismiss()
+            binding.keyCodeEt.setText("")
+            binding.keyNfcEt.setText("")
+            binding.keyMacEt.setText("")
+        }
+        binding.cancel.setOnClickListener {
+            dismiss()
+        }
+    }
+
+    /**
+     * 设置确认事件
+     */
+    fun setOnConfirmListener(onConfirm: (KeyManageFilterVo) -> Unit) {
+        this.onConfirm = onConfirm
+    }
+}

+ 51 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/FilterLockDialog.kt

@@ -0,0 +1,51 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.vo.LockManageFilterVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogFilterLockBinding
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 筛选挂锁
+ */
+class FilterLockDialog(context: Context) : BasePopupWindow(context) {
+    private var onConfirm: (LockManageFilterVo) -> Unit = {}
+    private lateinit var binding: DialogFilterLockBinding
+
+    init {
+        setContentView(R.layout.dialog_filter_lock)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogFilterLockBinding.bind(contentView)
+
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            val filter = LockManageFilterVo(
+                lockCode = binding.lockCodeEt.text.toString().takeIf { it.isNotBlank() },
+                lockNfc  = binding.lockNfcEt.text.toString().takeIf { it.isNotBlank() },
+                status   = when (binding.statusRg.checkedRadioButtonId) {
+                    binding.activateRb.id   -> true
+                    binding.deactivateRb.id -> false
+                    else                    -> null
+                }
+            )
+            onConfirm(filter)
+            dismiss()
+            binding.lockCodeEt.setText("")
+            binding.lockNfcEt.setText("")
+            binding.statusRg.clearCheck()
+        }
+        binding.cancel.setOnClickListener { dismiss() }
+    }
+
+    /**
+     * 设置确认事件
+     */
+    fun setOnConfirmListener(listener: (LockManageFilterVo) -> Unit) {
+        this.onConfirm = listener
+    }
+}

+ 51 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/FilterRfidTokenDialog.kt

@@ -0,0 +1,51 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.vo.RfidTokenManageFilterVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogFilterRfidTokenBinding
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 筛选 RFID 标签
+ */
+class FilterRfidTokenDialog(context: Context) : BasePopupWindow(context) {
+    private var onConfirm: (RfidTokenManageFilterVo) -> Unit = {}
+    private lateinit var binding: DialogFilterRfidTokenBinding
+
+    init {
+        setContentView(R.layout.dialog_filter_rfid_token)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogFilterRfidTokenBinding.bind(contentView)
+
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            val filter = RfidTokenManageFilterVo(
+                rfidCode = binding.rfidCodeEt.text.toString().takeIf { it.isNotBlank() },
+                rfid     = binding.rfidEt.text.toString().takeIf { it.isNotBlank() },
+                status   = when (binding.statusRg.checkedRadioButtonId) {
+                    binding.activateRb.id   -> true
+                    binding.deactivateRb.id -> false
+                    else                    -> null
+                }
+            )
+            onConfirm(filter)
+            dismiss()
+            binding.rfidCodeEt.setText("")
+            binding.rfidEt.setText("")
+            binding.statusRg.clearCheck()
+        }
+        binding.cancel.setOnClickListener { dismiss() }
+    }
+
+    /**
+     * 设置确认事件
+     */
+    fun setOnConfirmListener(listener: (RfidTokenManageFilterVo) -> Unit) {
+        this.onConfirm = listener
+    }
+}

+ 81 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/UpdateCardDialog.kt

@@ -0,0 +1,81 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.dos.IsJobCard
+import com.grkj.data.model.vo.UpdateCardDataVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogUpdateCardBinding
+import com.grkj.ui_base.utils.extension.tipDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 更新卡片
+ */
+class UpdateCardDialog(context: Context) : BasePopupWindow(context) {
+    private lateinit var binding: DialogUpdateCardBinding
+    private var onConfirm: (UpdateCardDataVo) -> Unit = {}
+    private var cardId: Long = 0
+
+    init {
+        setContentView(R.layout.dialog_update_card)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogUpdateCardBinding.bind(contentView)
+        binding.cancel.setOnClickListener { dismiss() }
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            if (checkData()) {
+                val dto = UpdateCardDataVo(
+                    cardId,
+                    binding.cardNfcEt.text.toString(),
+                    binding.usernameEt.text.toString(),
+                    binding.statusRg.checkedRadioButtonId == binding.activateRb.id,
+                    binding.remarkEt.text.toString().takeIf { it.isNotBlank() }
+                )
+                onConfirm(dto)
+                clear()
+                dismiss()
+            }
+        }
+    }
+
+    private fun checkData(): Boolean {
+        if (binding.cardNfcEt.text.isBlank()) {
+            PopTip.build().tipDialog(R.string.please_input_card_nfc)
+            return false
+        }
+        if (binding.usernameEt.text.isBlank()) {
+            PopTip.build().tipDialog(R.string.please_input_card_nickname)
+            return false
+        }
+        if (binding.statusRg.checkedRadioButtonId == -1) {
+            PopTip.build().tipDialog(R.string.please_select_status)
+            return false
+        }
+        return true
+    }
+
+    private fun clear() {
+        binding.cardNfcEt.setText("")
+        binding.usernameEt.setText("")
+        binding.remarkEt.setText("")
+        binding.statusRg.clearCheck()
+    }
+
+    fun setOnConfirmListener(listener: (UpdateCardDataVo) -> Unit) {
+        this.onConfirm = listener
+    }
+
+    fun setCardData(card: IsJobCard) {
+        cardId = card.cardId
+        binding.cardNfcEt.setText(card.cardNfc)
+        binding.usernameEt.setText(card.userName)
+        binding.activateRb.isChecked = card.exStatus == "0"
+        binding.deactivateRb.isChecked = card.exStatus != "0"
+        binding.remarkEt.setText(card.exRemark)
+    }
+}

+ 91 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/UpdateKeyDialog.kt

@@ -0,0 +1,91 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.dos.IsKey
+import com.grkj.data.model.vo.UpdateKeyDataVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogUpdateKeyBinding
+import com.grkj.ui_base.utils.extension.tipDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 更新钥匙
+ */
+class UpdateKeyDialog(context: Context) : BasePopupWindow(context) {
+    private lateinit var binding: DialogUpdateKeyBinding
+    private var onConfirm: (UpdateKeyDataVo) -> Unit = {}
+    private var keyId: Long = 0
+
+    init {
+        setContentView(R.layout.dialog_update_key)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogUpdateKeyBinding.bind(contentView)
+        binding.cancel.setOnClickListener { dismiss() }
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            if (checkData()) {
+                val updateKeyData = UpdateKeyDataVo(
+                    keyId,
+                    binding.keyCodeEt.text.toString(),
+                    binding.keyNfcEt.text.toString(),
+                    binding.keyMacEt.text.toString(),
+                    binding.statusRg.checkedRadioButtonId == binding.activateRb.id,
+                    binding.remarkEt.text.toString(),
+                )
+                onConfirm(updateKeyData)
+                binding.keyCodeEt.setText("")
+                binding.keyNfcEt.setText("")
+                binding.remarkEt.setText("")
+                binding.statusRg.clearCheck()
+                dismiss()
+            }
+        }
+    }
+
+    /**
+     * 检查数据
+     */
+    private fun checkData(): Boolean {
+        if (binding.keyCodeEt.text.trim().toString().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_key_name)
+            return false
+        }
+        if (binding.keyNfcEt.text.trim().toString().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_key_nfc)
+            return false
+        }
+        if (binding.keyMacEt.text.trim().toString().isEmpty()) {
+            PopTip.build().tipDialog(R.string.please_input_key_mac)
+            return false
+        }
+        if (binding.statusRg.checkedRadioButtonId == -1) {
+            PopTip.build().tipDialog(R.string.please_select_status)
+            return false
+        }
+        return true
+    }
+
+    /**
+     * 设置确认监听
+     */
+    fun setOnConfirmListener(onConfirm: (UpdateKeyDataVo) -> Unit) {
+        this.onConfirm = onConfirm
+    }
+
+    /**
+     * 设置钥匙数据
+     */
+    fun setKeyData(isKey: IsKey) {
+        keyId = isKey.keyId
+        binding.keyCodeEt.setText(isKey.keyCode)
+        binding.keyNfcEt.setText(isKey.keyNfc)
+        binding.keyMacEt.setText(isKey.macAddress)
+        binding.activateRb.isChecked = isKey.exStatus == "0"
+        binding.deactivateRb.isChecked = isKey.exStatus != "0"
+    }
+}

+ 81 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/UpdateLockDialog.kt

@@ -0,0 +1,81 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.dos.IsLock
+import com.grkj.data.model.vo.UpdateLockDataVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogUpdateLockBinding
+import com.grkj.ui_base.utils.extension.tipDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 更新挂锁
+ */
+class UpdateLockDialog(context: Context) : BasePopupWindow(context) {
+    private lateinit var binding: DialogUpdateLockBinding
+    private var onConfirm: (UpdateLockDataVo) -> Unit = {}
+    private var lockId: Long = 0
+
+    init {
+        setContentView(R.layout.dialog_update_lock)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogUpdateLockBinding.bind(contentView)
+        binding.cancel.setOnClickListener { dismiss() }
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            if (checkData()) {
+                val dto = UpdateLockDataVo(
+                    lockId,
+                    binding.lockCodeEt.text.toString(),
+                    binding.lockNfcEt.text.toString(),
+                    binding.statusRg.checkedRadioButtonId == binding.activateRb.id,
+                    binding.remarkEt.text.toString().takeIf { it.isNotBlank() }
+                )
+                onConfirm(dto)
+                clear()
+                dismiss()
+            }
+        }
+    }
+
+    private fun checkData(): Boolean {
+        if (binding.lockCodeEt.text.isBlank()) {
+            PopTip.build().tipDialog(R.string.please_input_lock_code)
+            return false
+        }
+        if (binding.lockNfcEt.text.isBlank()) {
+            PopTip.build().tipDialog(R.string.please_input_lock_nfc)
+            return false
+        }
+        if (binding.statusRg.checkedRadioButtonId == -1) {
+            PopTip.build().tipDialog(R.string.please_select_status)
+            return false
+        }
+        return true
+    }
+
+    private fun clear() {
+        binding.lockCodeEt.setText("")
+        binding.lockNfcEt.setText("")
+        binding.remarkEt.setText("")
+        binding.statusRg.clearCheck()
+    }
+
+    fun setOnConfirmListener(listener: (UpdateLockDataVo) -> Unit) {
+        this.onConfirm = listener
+    }
+
+    fun setLockData(lock: IsLock) {
+        lockId = lock.lockId
+        binding.lockCodeEt.setText(lock.lockCode)
+        binding.lockNfcEt.setText(lock.lockNfc)
+        binding.activateRb.isChecked = lock.exStatus == "0"
+        binding.deactivateRb.isChecked = lock.exStatus != "0"
+        binding.remarkEt.setText(lock.exRemark)
+    }
+}

+ 81 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/hardware_manage/UpdateRfidTokenDialog.kt

@@ -0,0 +1,81 @@
+package com.grkj.iscs.features.main.dialog.hardware_manage
+
+import android.content.Context
+import android.view.View
+import com.grkj.data.model.dos.IsRfidToken
+import com.grkj.data.model.vo.UpdateRfidTokenDataVo
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.DialogUpdateRfidTokenBinding
+import com.grkj.ui_base.utils.extension.tipDialog
+import com.kongzue.dialogx.dialogs.PopTip
+import razerdp.basepopup.BasePopupWindow
+
+/**
+ * 更新 RFID 标签
+ */
+class UpdateRfidTokenDialog(context: Context) : BasePopupWindow(context) {
+    private lateinit var binding: DialogUpdateRfidTokenBinding
+    private var onConfirm: (UpdateRfidTokenDataVo) -> Unit = {}
+    private var rfidId: Long = 0
+
+    init {
+        setContentView(R.layout.dialog_update_rfid_token)
+    }
+
+    override fun onViewCreated(contentView: View) {
+        super.onViewCreated(contentView)
+        binding = DialogUpdateRfidTokenBinding.bind(contentView)
+        binding.cancel.setOnClickListener { dismiss() }
+        binding.closeIv.setOnClickListener { dismiss() }
+        binding.confirm.setOnClickListener {
+            if (checkData()) {
+                val dto = UpdateRfidTokenDataVo(
+                    rfidId,
+                    binding.rfidCodeEt.text.toString(),
+                    binding.rfidEt.text.toString(),
+                    binding.statusRg.checkedRadioButtonId == binding.activateRb.id,
+                    binding.remarkEt.text.toString().takeIf { it.isNotBlank() }
+                )
+                onConfirm(dto)
+                clear()
+                dismiss()
+            }
+        }
+    }
+
+    private fun checkData(): Boolean {
+        if (binding.rfidCodeEt.text.isBlank()) {
+            PopTip.build().tipDialog(R.string.please_input_rfid_code)
+            return false
+        }
+        if (binding.rfidEt.text.isBlank()) {
+            PopTip.build().tipDialog(R.string.please_input_rfid)
+            return false
+        }
+        if (binding.statusRg.checkedRadioButtonId == -1) {
+            PopTip.build().tipDialog(R.string.please_select_status)
+            return false
+        }
+        return true
+    }
+
+    private fun clear() {
+        binding.rfidCodeEt.setText("")
+        binding.rfidEt.setText("")
+        binding.remarkEt.setText("")
+        binding.statusRg.clearCheck()
+    }
+
+    fun setOnConfirmListener(listener: (UpdateRfidTokenDataVo) -> Unit) {
+        this.onConfirm = listener
+    }
+
+    fun setRfidTokenData(token: IsRfidToken) {
+        rfidId = token.rfidId
+        binding.rfidCodeEt.setText(token.rfidCode)
+        binding.rfidEt.setText(token.rfid)
+        binding.activateRb.isChecked = token.status == "0"
+        binding.deactivateRb.isChecked = token.status != "0"
+        binding.remarkEt.setText(token.remark)
+    }
+}

+ 161 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/CardManageFragment.kt

@@ -0,0 +1,161 @@
+package com.grkj.iscs.features.main.fragment.hardware_manage
+
+import android.graphics.Color
+import android.view.Gravity
+import androidx.annotation.StringRes
+import androidx.fragment.app.viewModels
+import com.drake.brv.BindingAdapter
+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.model.dos.IsJobCard
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentCardManageBinding
+import com.grkj.iscs.databinding.ItemCardManageBinding
+import com.grkj.iscs.features.main.dialog.hardware_manage.AddCardDialog
+import com.grkj.iscs.features.main.dialog.hardware_manage.FilterCardDialog
+import com.grkj.iscs.features.main.dialog.hardware_manage.UpdateCardDialog
+import com.grkj.iscs.features.main.viewmodel.hardware_manage.CardManageViewModel
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 卡片管理
+ */
+@AndroidEntryPoint
+class CardManageFragment : BaseFragment<FragmentCardManageBinding>() {
+    private val viewModel: CardManageViewModel by viewModels()
+    private lateinit var addCardDialog: AddCardDialog
+    private lateinit var filterCardDialog: FilterCardDialog
+    private lateinit var updateCardDialog: UpdateCardDialog
+
+    override fun getLayoutId() = R.layout.fragment_card_manage
+
+    override fun initView() {
+        addCardDialog = AddCardDialog(requireContext()).apply { popupGravity = Gravity.CENTER }
+        filterCardDialog = FilterCardDialog(requireContext()).apply { popupGravity = Gravity.CENTER }
+        updateCardDialog = UpdateCardDialog(requireContext()).apply { popupGravity = Gravity.CENTER }
+
+        filterCardDialog.setOnConfirmListener {
+            viewModel.cardFilterData = it
+            loadCards(reset = true)
+        }
+        // 添加卡片
+        addCardDialog.setOnConfirmListener { vo ->
+            viewModel.addCard(vo).observe(this) { ok ->
+                @StringRes val titleRes = if (ok) com.grkj.ui_base.R.string.action_succeed else com.grkj.ui_base.R.string.action_failed
+                @StringRes val msgRes   = if (ok) R.string.add_card_succeed   else R.string.add_card_failed
+
+                TipDialog.show(
+                    title         = CommonUtils.getStr(titleRes).toString(),
+                    dialogType    = if (ok) TipDialog.DialogType.SUCCESS else TipDialog.DialogType.ERROR,
+                    msg           = CommonUtils.getStr(msgRes).toString(),
+                    countDownTime = 10,
+                    showConfirm   = false,
+                    onCancelClick = { loadCards(reset = true) }
+                )
+            }
+        }
+        // 更新卡片
+        updateCardDialog.setOnConfirmListener { vo ->
+            viewModel.updateCard(vo).observe(this) { ok ->
+                @StringRes val titleRes = if (ok) com.grkj.ui_base.R.string.action_succeed else com.grkj.ui_base.R.string.action_failed
+                @StringRes val msgRes   = if (ok) R.string.update_card_succeed else R.string.update_card_failed
+
+                TipDialog.show(
+                    title         = CommonUtils.getStr(titleRes).toString(),
+                    dialogType    = if (ok) TipDialog.DialogType.SUCCESS else TipDialog.DialogType.ERROR,
+                    msg           = CommonUtils.getStr(msgRes).toString(),
+                    countDownTime = 10,
+                    showConfirm   = false,
+                    onCancelClick = { loadCards(reset = true) }
+                )
+            }
+        }
+
+        binding.back.setDebouncedClickListener { navController.popBackStack() }
+        binding.add.setDebouncedClickListener { addCardDialog.showPopupWindow() }
+        binding.filter.setDebouncedClickListener { filterCardDialog.showPopupWindow() }
+        binding.delete.setDebouncedClickListener { deleteSelectedCards() }
+        binding.refreshLayout.setOnRefreshListener {
+            viewModel.cardFilterData = null
+            loadCards(reset = true)
+        }
+        binding.refreshLayout.setOnLoadMoreListener { loadCards() }
+
+        binding.listRv.linear().divider {
+            setColor(Color.BLACK); orientation = DividerOrientation.VERTICAL
+        }.setup {
+            addType<IsJobCard>(R.layout.item_card_manage)
+            onBind { bindCardItem(this) }
+        }
+
+        binding.selectAll.setOnCheckedChangeListener { _, checked ->
+            viewModel.cardManageDataList.forEach { it.isSelected = checked }
+            binding.listRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    private fun bindCardItem(holder: BindingAdapter.BindingViewHolder) {
+        val bind = holder.getBinding<ItemCardManageBinding>()
+        val item = holder.getModel<IsJobCard>()
+        bind.cardCode.text   = item.cardCode
+        bind.cardNfc.text   = item.cardNfc
+        bind.cardNickname.text  = item.userName ?: ""
+        bind.select.apply {
+            setOnCheckedChangeListener(null)
+            isChecked = item.isSelected
+            setOnCheckedChangeListener { _, c ->
+                item.isSelected = c
+                binding.selectAll.isChecked = viewModel.cardManageDataList.all { it.isSelected }
+            }
+        }
+        bind.root.setOnClickListener {
+            updateCardDialog.setCardData(item)
+            updateCardDialog.showPopupWindow()
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        viewModel.cardFilterData = null
+        loadCards(reset = true)
+    }
+
+    private fun loadCards(reset: Boolean = false) {
+        if (reset) viewModel.cardManageDataList.clear()
+        viewModel.getCardData(viewModel.cardFilterData, !reset).observe(this) {
+            if (reset) binding.selectAll.isChecked = false
+            binding.refreshLayout.finishRefresh()
+            binding.refreshLayout.finishLoadMore()
+            binding.listRv.models = viewModel.cardManageDataList
+        }
+    }
+
+    private fun deleteSelectedCards() {
+        if (viewModel.cardManageDataList.none { it.isSelected }) {
+            PopTip.tip(R.string.please_select_card); return
+        }
+        TipDialog.show(
+            msg = CommonUtils.getStr(R.string.check_delete_card).toString(),
+            countDownTime = 10
+        ) {
+            val ids = viewModel.cardManageDataList.filter { it.isSelected }.map { it.cardId }
+            viewModel.deleteSelectedCard(ids).observe(this) { ok ->
+                TipDialog.show(
+                    dialogType = if (ok) TipDialog.DialogType.SUCCESS else TipDialog.DialogType.ERROR,
+                    msg = CommonUtils.getStr(if (ok) R.string.card_manage_delete_succeed else R.string.card_manage_delete_failed).toString(),
+                    showConfirm = false,
+                    countDownTime = 10
+                )
+                loadCards(reset = true)
+            }
+        }
+    }
+}

+ 6 - 2
app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/HardwareManageHomeFragment.kt

@@ -106,15 +106,19 @@ class HardwareManageHomeFragment : BaseFragment<FragmentHardwareManageHomeBindin
             }
 
             1 -> {
-
+                navController.navigate(R.id.action_hardwareManageHomeFragment_to_keyManageFragment)
             }
 
             2 -> {
-
+                navController.navigate(R.id.action_hardwareManageHomeFragment_to_lockManageFragment)
             }
 
             3 -> {
+                navController.navigate(R.id.action_hardwareManageHomeFragment_to_cardManageFragment)
+            }
 
+            4 -> {
+                navController.navigate(R.id.action_hardwareManageHomeFragment_to_rfidTokenManageFragment)
             }
         }
     }

+ 221 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/KeyManageFragment.kt

@@ -0,0 +1,221 @@
+package com.grkj.iscs.features.main.fragment.hardware_manage
+
+import android.graphics.Color
+import android.view.Gravity
+import androidx.fragment.app.viewModels
+import com.drake.brv.BindingAdapter
+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.model.dos.IsKey
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentKeyManageBinding
+import com.grkj.iscs.databinding.ItemKeyManageBinding
+import com.grkj.iscs.features.main.dialog.hardware_manage.AddKeyDialog
+import com.grkj.iscs.features.main.dialog.hardware_manage.FilterKeyDialog
+import com.grkj.iscs.features.main.dialog.hardware_manage.UpdateKeyDialog
+import com.grkj.iscs.features.main.viewmodel.hardware_manage.KeyManageViewModel
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 钥匙管理
+ */
+@AndroidEntryPoint
+class KeyManageFragment : BaseFragment<FragmentKeyManageBinding>() {
+    private val viewModel: KeyManageViewModel by viewModels()
+    private lateinit var addKeyDialog: AddKeyDialog
+    private lateinit var filterKeyDialog: FilterKeyDialog
+    private lateinit var updateKeyDialog: UpdateKeyDialog
+    override fun getLayoutId(): Int {
+        return R.layout.fragment_key_manage
+    }
+
+    override fun initView() {
+        addKeyDialog = AddKeyDialog(requireContext())
+        addKeyDialog.popupGravity = Gravity.CENTER
+        filterKeyDialog = FilterKeyDialog(requireContext())
+        filterKeyDialog.popupGravity = Gravity.CENTER
+        updateKeyDialog = UpdateKeyDialog(requireContext())
+        updateKeyDialog.popupGravity = Gravity.CENTER
+        filterKeyDialog.setOnConfirmListener {
+            viewModel.keyFilterData = it
+            getKeyData(nextPage = false)
+        }
+        addKeyDialog.setOnConfirmListener {
+            viewModel.addKey(it).observe(this) {
+                if (it) {
+                    TipDialog.show(
+                        title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_succeed)
+                            .toString(),
+                        dialogType = TipDialog.DialogType.SUCCESS,
+                        msg = CommonUtils.getStr(R.string.add_key_succeed).toString(),
+                        countDownTime = 10,
+                        showConfirm = false,
+                        onCancelClick = {
+                            getKeyData(false)
+                        }
+                    )
+                } else {
+                    TipDialog.show(
+                        title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_failed)
+                            .toString(),
+                        dialogType = TipDialog.DialogType.ERROR,
+                        msg = CommonUtils.getStr(R.string.add_key_failed).toString(),
+                        countDownTime = 10,
+                        showConfirm = false,
+                        onCancelClick = {
+                            getKeyData(false)
+                        }
+                    )
+                }
+            }
+        }
+        updateKeyDialog.setOnConfirmListener {
+            viewModel.updateKey(it).observe(this) {
+                if (it) {
+                    TipDialog.show(
+                        title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_succeed)
+                            .toString(),
+                        dialogType = TipDialog.DialogType.SUCCESS,
+                        msg = CommonUtils.getStr(R.string.update_key_succeed).toString(),
+                        countDownTime = 10,
+                        showConfirm = false,
+                        onCancelClick = {
+                            getKeyData(false)
+                        }
+                    )
+                } else {
+                    TipDialog.show(
+                        title = CommonUtils.getStr(com.grkj.ui_base.R.string.action_failed)
+                            .toString(),
+                        dialogType = TipDialog.DialogType.ERROR,
+                        msg = CommonUtils.getStr(R.string.update_key_failed).toString(),
+                        countDownTime = 10,
+                        showConfirm = false,
+                        onCancelClick = {
+                            getKeyData(false)
+                        }
+                    )
+                }
+            }
+        }
+        binding.back.setDebouncedClickListener {
+            navController.popBackStack()
+        }
+        binding.delete.setDebouncedClickListener {
+            deleteSelected()
+        }
+        binding.add.setDebouncedClickListener {
+            addKeyDialog.showPopupWindow()
+        }
+        binding.filter.setDebouncedClickListener {
+            filterKeyDialog.showPopupWindow()
+        }
+        binding.refreshLayout.setOnRefreshListener {
+            viewModel.keyFilterData = null
+            getKeyData(nextPage = false)
+        }
+        binding.refreshLayout.setOnLoadMoreListener {
+            getKeyData()
+        }
+        binding.listRv.linear().divider {
+            this.setColor(Color.BLACK)
+            this.startVisible = false
+            this.endVisible = true
+            this.orientation = DividerOrientation.VERTICAL
+        }.setup {
+            addType<IsKey>(R.layout.item_key_manage)
+            onBind {
+                onUserDataBinding(this)
+            }
+        }
+        setSelectAllListener()
+    }
+
+    private fun setSelectAllListener() {
+        binding.selectAll.setOnCheckedChangeListener { v, checked ->
+            viewModel.keyManageDataList.forEach { it.isSelected = checked }
+            binding.listRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    private fun BindingAdapter.BindingViewHolder.onUserDataBinding(holder: BindingAdapter.BindingViewHolder) {
+        val itemBinding = holder.getBinding<ItemKeyManageBinding>()
+        val item = holder.getModel<IsKey>()
+        itemBinding.keyCode.text = item.keyCode
+        itemBinding.keyNfc.text = item.keyNfc
+        itemBinding.keyMac.text = item.macAddress
+        itemBinding.select.setOnCheckedChangeListener(null)
+        itemBinding.select.isChecked = item.isSelected
+        itemBinding.select.setOnCheckedChangeListener { _, checked ->
+            item.isSelected = checked
+            binding.selectAll.setOnCheckedChangeListener(null)
+            binding.selectAll.isChecked = viewModel.keyManageDataList.all { it.isSelected }
+            setSelectAllListener()
+        }
+        itemBinding.root.setOnClickListener {
+            updateKeyDialog.setKeyData(item)
+            updateKeyDialog.showPopupWindow()
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        viewModel.keyFilterData = null
+        getKeyData(nextPage = false)
+    }
+
+    private fun getKeyData(nextPage: Boolean = true) {
+        viewModel.getKeyData(viewModel.keyFilterData, nextPage).observe(this) {
+            if (!nextPage) {
+                viewModel.keyFilterData = null
+                binding.selectAll.setOnCheckedChangeListener(null)
+                binding.selectAll.isChecked = false
+                setSelectAllListener()
+            }
+            binding.refreshLayout.finishRefresh()
+            binding.refreshLayout.finishLoadMore()
+            binding.listRv.models = viewModel.keyManageDataList
+        }
+    }
+
+    private fun deleteSelected() {
+        if (viewModel.keyManageDataList.none { it.isSelected }) {
+            PopTip.tip(R.string.please_select_key)
+            return
+        }
+        TipDialog.show(
+            msg = CommonUtils.getStr(R.string.check_delete_key).toString(),
+            countDownTime = 10,
+            onConfirmClick = {
+                viewModel.deleteSelectedKey(viewModel.keyManageDataList.filter { it.isSelected }
+                    .map { it.keyId }).observe(this) {
+                    if (it) {
+                        TipDialog.show(
+                            dialogType = TipDialog.DialogType.SUCCESS,
+                            msg = CommonUtils.getStr(R.string.key_manage_delete_succeed)
+                                .toString(),
+                            showConfirm = false,
+                            countDownTime = 10
+                        )
+                        getKeyData(false)
+                    } else {
+                        TipDialog.show(
+                            dialogType = TipDialog.DialogType.ERROR,
+                            msg = CommonUtils.getStr(R.string.key_manage_delete_failed)
+                                .toString(),
+                            showConfirm = false,
+                            countDownTime = 10
+                        )
+                    }
+                }
+            })
+    }
+}

+ 169 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/LockManageFragment.kt

@@ -0,0 +1,169 @@
+package com.grkj.iscs.features.main.fragment.hardware_manage
+
+import android.graphics.Color
+import android.view.Gravity
+import androidx.annotation.StringRes
+import androidx.fragment.app.viewModels
+import com.drake.brv.BindingAdapter
+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.model.dos.IsLock
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentLockManageBinding
+import com.grkj.iscs.databinding.ItemLockManageBinding
+import com.grkj.iscs.features.main.dialog.hardware_manage.AddLockDialog
+import com.grkj.iscs.features.main.dialog.hardware_manage.FilterLockDialog
+import com.grkj.iscs.features.main.dialog.hardware_manage.UpdateLockDialog
+import com.grkj.iscs.features.main.viewmodel.hardware_manage.LockManageViewModel
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+/**
+ * 挂锁管理
+ */
+@AndroidEntryPoint
+class LockManageFragment : BaseFragment<FragmentLockManageBinding>() {
+    private val viewModel: LockManageViewModel by viewModels()
+    private lateinit var addLockDialog: AddLockDialog
+    private lateinit var filterLockDialog: FilterLockDialog
+    private lateinit var updateLockDialog: UpdateLockDialog
+
+    override fun getLayoutId() = R.layout.fragment_lock_manage
+
+    override fun initView() {
+        addLockDialog = AddLockDialog(requireContext()).apply { popupGravity = Gravity.CENTER }
+        filterLockDialog =
+            FilterLockDialog(requireContext()).apply { popupGravity = Gravity.CENTER }
+        updateLockDialog =
+            UpdateLockDialog(requireContext()).apply { popupGravity = Gravity.CENTER }
+
+        filterLockDialog.setOnConfirmListener {
+            viewModel.lockFilterData = it
+            loadLocks(reset = true)
+        }
+        // 添加挂锁回调
+        addLockDialog.setOnConfirmListener { vo ->
+            viewModel.addLock(vo).observe(this) { ok ->
+                @StringRes val titleRes =
+                    if (ok) com.grkj.ui_base.R.string.action_succeed else com.grkj.ui_base.R.string.action_failed
+                @StringRes val msgRes =
+                    if (ok) R.string.add_lock_succeed else R.string.add_lock_failed
+
+                TipDialog.show(
+                    title = CommonUtils.getStr(titleRes).toString(),
+                    dialogType = if (ok) TipDialog.DialogType.SUCCESS else TipDialog.DialogType.ERROR,
+                    msg = CommonUtils.getStr(msgRes).toString(),
+                    countDownTime = 10,
+                    showConfirm = false,
+                    onCancelClick = { loadLocks(reset = true) }
+                )
+            }
+        }
+
+        // 更新挂锁回调
+        updateLockDialog.setOnConfirmListener { vo ->
+            viewModel.updateLock(vo).observe(this) { ok ->
+                @StringRes val titleRes =
+                    if (ok) com.grkj.ui_base.R.string.action_succeed else com.grkj.ui_base.R.string.action_failed
+                @StringRes val msgRes =
+                    if (ok) R.string.update_lock_succeed else R.string.update_lock_failed
+
+                TipDialog.show(
+                    title = CommonUtils.getStr(titleRes).toString(),
+                    dialogType = if (ok) TipDialog.DialogType.SUCCESS else TipDialog.DialogType.ERROR,
+                    msg = CommonUtils.getStr(msgRes).toString(),
+                    countDownTime = 10,
+                    showConfirm = false,
+                    onCancelClick = { loadLocks(reset = true) }
+                )
+            }
+        }
+
+
+        binding.back.setDebouncedClickListener { navController.popBackStack() }
+        binding.add.setDebouncedClickListener { addLockDialog.showPopupWindow() }
+        binding.filter.setDebouncedClickListener { filterLockDialog.showPopupWindow() }
+        binding.delete.setDebouncedClickListener { deleteSelectedLocks() }
+        binding.refreshLayout.setOnRefreshListener {
+            viewModel.lockFilterData = null
+            loadLocks(reset = true)
+        }
+        binding.refreshLayout.setOnLoadMoreListener { loadLocks() }
+
+        binding.listRv.linear().divider {
+            setColor(Color.BLACK); orientation = DividerOrientation.VERTICAL
+        }.setup {
+            addType<IsLock>(R.layout.item_lock_manage)
+            onBind { bindLockItem(this) }
+        }
+
+        binding.selectAll.setOnCheckedChangeListener { _, checked ->
+            viewModel.lockManageDataList.forEach { it.isSelected = checked }
+            binding.listRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    private fun bindLockItem(holder: BindingAdapter.BindingViewHolder) {
+        val bind = holder.getBinding<ItemLockManageBinding>()
+        val item = holder.getModel<IsLock>()
+        bind.lockCode.text = item.lockCode
+        bind.lockNfc.text = item.lockNfc
+        bind.status.text = if (item.exStatus == "0") "正常" else "异常"
+        bind.select.apply {
+            setOnCheckedChangeListener(null)
+            isChecked = item.isSelected
+            setOnCheckedChangeListener { _, c ->
+                item.isSelected = c
+                binding.selectAll.isChecked = viewModel.lockManageDataList.all { it.isSelected }
+            }
+        }
+        bind.root.setOnClickListener {
+            updateLockDialog.setLockData(item)
+            updateLockDialog.showPopupWindow()
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        viewModel.lockFilterData = null
+        loadLocks(reset = true)
+    }
+
+    private fun loadLocks(reset: Boolean = false) {
+        if (reset) viewModel.lockManageDataList.clear()
+        viewModel.getLockData(viewModel.lockFilterData, !reset).observe(this) {
+            if (reset) binding.selectAll.isChecked = false
+            binding.refreshLayout.finishRefresh()
+            binding.refreshLayout.finishLoadMore()
+            binding.listRv.models = viewModel.lockManageDataList
+        }
+    }
+
+    private fun deleteSelectedLocks() {
+        if (viewModel.lockManageDataList.none { it.isSelected }) {
+            PopTip.tip(R.string.please_select_lock); return
+        }
+        TipDialog.show(
+            msg = CommonUtils.getStr(R.string.check_delete_lock).toString(),
+            countDownTime = 10
+        ) {
+            val ids = viewModel.lockManageDataList.filter { it.isSelected }.map { it.lockId }
+            viewModel.deleteSelectedLock(ids).observe(this) { ok ->
+                TipDialog.show(
+                    dialogType = if (ok) TipDialog.DialogType.SUCCESS else TipDialog.DialogType.ERROR,
+                    msg = CommonUtils.getStr(if (ok) R.string.lock_manage_delete_succeed else R.string.lock_manage_delete_failed)
+                        .toString(),
+                    showConfirm = false, countDownTime = 10
+                )
+                loadLocks(reset = true)
+            }
+        }
+    }
+}

+ 166 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/hardware_manage/RfidTokenManageFragment.kt

@@ -0,0 +1,166 @@
+package com.grkj.iscs.features.main.fragment.hardware_manage
+
+import android.graphics.Color
+import android.view.Gravity
+import androidx.annotation.StringRes
+import androidx.fragment.app.viewModels
+import com.drake.brv.BindingAdapter
+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.model.dos.IsRfidToken
+import com.grkj.iscs.R
+import com.grkj.iscs.databinding.FragmentRfidTokenManageBinding
+import com.grkj.iscs.databinding.ItemRfidTokenManageBinding
+import com.grkj.iscs.features.main.dialog.hardware_manage.AddRfidTokenDialog
+import com.grkj.iscs.features.main.dialog.hardware_manage.FilterRfidTokenDialog
+import com.grkj.iscs.features.main.dialog.hardware_manage.UpdateRfidTokenDialog
+import com.grkj.iscs.features.main.viewmodel.hardware_manage.RfidTokenManageViewModel
+import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
+import com.grkj.ui_base.utils.CommonUtils
+import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.extension.setDebouncedClickListener
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class RfidTokenManageFragment : BaseFragment<FragmentRfidTokenManageBinding>() {
+    private val viewModel: RfidTokenManageViewModel by viewModels()
+    private lateinit var addDialog: AddRfidTokenDialog
+    private lateinit var filterDialog: FilterRfidTokenDialog
+    private lateinit var updateDialog: UpdateRfidTokenDialog
+
+    override fun getLayoutId() = R.layout.fragment_rfid_token_manage
+
+    override fun initView() {
+        addDialog = AddRfidTokenDialog(requireContext()).apply { popupGravity = Gravity.CENTER }
+        filterDialog =
+            FilterRfidTokenDialog(requireContext()).apply { popupGravity = Gravity.CENTER }
+        updateDialog =
+            UpdateRfidTokenDialog(requireContext()).apply { popupGravity = Gravity.CENTER }
+
+        filterDialog.setOnConfirmListener {
+            viewModel.rfidTokenFilterData = it
+            loadTokens(reset = true)
+        }
+        // 添加 RFID 标签
+        addDialog.setOnConfirmListener { vo ->
+            viewModel.addRfidToken(vo).observe(this) { ok ->
+                @StringRes val titleRes =
+                    if (ok) com.grkj.ui_base.R.string.action_succeed else com.grkj.ui_base.R.string.action_failed
+                @StringRes val msgRes =
+                    if (ok) R.string.add_rfid_token_succeed else R.string.add_rfid_token_failed
+
+                TipDialog.show(
+                    title = CommonUtils.getStr(titleRes).toString(),
+                    dialogType = if (ok) TipDialog.DialogType.SUCCESS else TipDialog.DialogType.ERROR,
+                    msg = CommonUtils.getStr(msgRes).toString(),
+                    countDownTime = 10,
+                    showConfirm = false,
+                    onCancelClick = { loadTokens(reset = true) }
+                )
+            }
+        }
+        // 更新 RFID 标签
+        updateDialog.setOnConfirmListener { vo ->
+            viewModel.updateRfidToken(vo).observe(this) { ok ->
+                @StringRes val titleRes =
+                    if (ok) com.grkj.ui_base.R.string.action_succeed else com.grkj.ui_base.R.string.action_failed
+                @StringRes val msgRes =
+                    if (ok) R.string.update_rfid_token_succeed else R.string.update_rfid_token_failed
+
+                TipDialog.show(
+                    title = CommonUtils.getStr(titleRes).toString(),
+                    dialogType = if (ok) TipDialog.DialogType.SUCCESS else TipDialog.DialogType.ERROR,
+                    msg = CommonUtils.getStr(msgRes).toString(),
+                    countDownTime = 10,
+                    showConfirm = false,
+                    onCancelClick = { loadTokens(reset = true) }
+                )
+            }
+        }
+
+        binding.back.setDebouncedClickListener { navController.popBackStack() }
+        binding.add.setDebouncedClickListener { addDialog.showPopupWindow() }
+        binding.filter.setDebouncedClickListener { filterDialog.showPopupWindow() }
+        binding.delete.setDebouncedClickListener { deleteSelectedTokens() }
+        binding.refreshLayout.setOnRefreshListener {
+            viewModel.rfidTokenFilterData = null
+            loadTokens(reset = true)
+        }
+        binding.refreshLayout.setOnLoadMoreListener { loadTokens() }
+
+        binding.listRv.linear().divider {
+            setColor(Color.BLACK); orientation = DividerOrientation.VERTICAL
+        }.setup {
+            addType<IsRfidToken>(R.layout.item_rfid_token_manage)
+            onBind { bindTokenItem(this) }
+        }
+
+        binding.selectAll.setOnCheckedChangeListener { _, checked ->
+            viewModel.rfidTokenManageDataList.forEach { it.isSelected = checked }
+            binding.listRv.adapter?.notifyDataSetChanged()
+        }
+    }
+
+    private fun bindTokenItem(holder: BindingAdapter.BindingViewHolder) {
+        val bind = holder.getBinding<ItemRfidTokenManageBinding>()
+        val item = holder.getModel<IsRfidToken>()
+        bind.rfidCode.text = item.rfidCode
+        bind.rfid.text = item.rfid
+        bind.status.text = if (item.status == "0") "正常" else "异常"
+        bind.select.apply {
+            setOnCheckedChangeListener(null)
+            isChecked = item.isSelected
+            setOnCheckedChangeListener { _, c ->
+                item.isSelected = c
+                binding.selectAll.isChecked =
+                    viewModel.rfidTokenManageDataList.all { it.isSelected }
+            }
+        }
+        bind.root.setOnClickListener {
+            updateDialog.setRfidTokenData(item)
+            updateDialog.showPopupWindow()
+        }
+    }
+
+    override fun initData() {
+        super.initData()
+        viewModel.rfidTokenFilterData = null
+        loadTokens(reset = true)
+    }
+
+    private fun loadTokens(reset: Boolean = false) {
+        if (reset) viewModel.rfidTokenManageDataList.clear()
+        viewModel.getRfidTokenData(viewModel.rfidTokenFilterData, !reset).observe(this) {
+            if (reset) binding.selectAll.isChecked = false
+            binding.refreshLayout.finishRefresh()
+            binding.refreshLayout.finishLoadMore()
+            binding.listRv.models = viewModel.rfidTokenManageDataList
+        }
+    }
+
+    private fun deleteSelectedTokens() {
+        if (viewModel.rfidTokenManageDataList.none { it.isSelected }) {
+            PopTip.tip(R.string.please_select_rfid_token); return
+        }
+        TipDialog.show(
+            msg = CommonUtils.getStr(R.string.check_delete_rfid_token).toString(),
+            countDownTime = 10
+        ) {
+            val ids = viewModel.rfidTokenManageDataList.filter { it.isSelected }.map { it.rfidId }
+            viewModel.deleteSelectedRfidToken(ids).observe(this) { ok ->
+                TipDialog.show(
+                    dialogType = if (ok) TipDialog.DialogType.SUCCESS else TipDialog.DialogType.ERROR,
+                    msg = CommonUtils.getStr(if (ok) R.string.rfid_token_manage_delete_succeed else R.string.rfid_token_manage_delete_failed)
+                        .toString(),
+                    showConfirm = false,
+                    countDownTime = 10
+                )
+                loadTokens(reset = true)
+            }
+        }
+    }
+}

+ 81 - 0
app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/CardManageViewModel.kt

@@ -0,0 +1,81 @@
+package com.grkj.iscs.features.main.viewmodel.hardware_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.model.dos.IsJobCard
+import com.grkj.data.model.dos.SysUserDo
+import com.grkj.data.model.vo.AddCardDataVo
+import com.grkj.data.model.vo.CardManageFilterVo
+import com.grkj.data.model.vo.UpdateCardDataVo
+import com.grkj.data.repository.IHardwareRepository
+import com.grkj.data.repository.IUserRepository
+import com.grkj.data.repository.impl.UserRepository
+import com.grkj.ui_base.base.BaseViewModel
+import com.sik.sikcore.data.BeanUtils
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+/**
+ * 卡片管理界面模型
+ */
+@HiltViewModel
+class CardManageViewModel @Inject constructor(
+    private val hardwareRepository: IHardwareRepository
+) : BaseViewModel() {
+    private var current: Int = 0
+    private val size: Int = 50
+    var cardManageDataList: MutableList<IsJobCard> = mutableListOf()
+    var cardFilterData: CardManageFilterVo? = null
+
+    /**
+     * 删除选中卡片
+     */
+    fun deleteSelectedCard(cardIds: List<Long>): LiveData<Boolean> =
+        liveData(Dispatchers.IO) {
+            hardwareRepository.deleteCardByCardIds(cardIds)
+            emit(true)
+        }
+
+    /**
+     * 获取卡片数据
+     */
+    fun getCardData(
+        filterData: CardManageFilterVo? = null,
+        nextPage: Boolean = true
+    ): LiveData<Boolean> {
+        if (nextPage) {
+            current += 1
+        } else {
+            current = 0
+            cardManageDataList.clear()
+        }
+        return liveData(Dispatchers.IO) {
+            val page = hardwareRepository.getCardInfoPage(filterData, size, size * current)
+            cardManageDataList.addAll(page)
+            emit(true)
+        }
+    }
+
+    /**
+     * 添加卡片
+     */
+    fun addCard(data: AddCardDataVo): LiveData<Boolean> =
+        liveData(Dispatchers.IO) {
+            var isCard = IsJobCard()
+            isCard = BeanUtils.copyData<IsJobCard>(data, isCard)
+            hardwareRepository.addCardInfo(isCard)
+            emit(true)
+        }
+
+    /**
+     * 更新卡片
+     */
+    fun updateCard(data: UpdateCardDataVo): LiveData<Boolean> =
+        liveData(Dispatchers.IO) {
+            var isCard = IsJobCard()
+            isCard = BeanUtils.copyData<IsJobCard>(data, isCard)
+            hardwareRepository.updateCardInfo(isCard)
+            emit(true)
+        }
+}

+ 83 - 0
app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/KeyManageViewModel.kt

@@ -0,0 +1,83 @@
+package com.grkj.iscs.features.main.viewmodel.hardware_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.model.dos.IsKey
+import com.grkj.data.model.vo.AddKeyDataVo
+import com.grkj.data.model.vo.KeyManageFilterVo
+import com.grkj.data.model.vo.UpdateKeyDataVo
+import com.grkj.data.model.vo.UpdateUserDataVo
+import com.grkj.data.repository.IHardwareRepository
+import com.grkj.ui_base.base.BaseViewModel
+import com.sik.sikcore.data.BeanUtils
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+/**
+ * 钥匙管理界面模型
+ */
+@HiltViewModel
+class KeyManageViewModel @Inject constructor(
+    val hardwareRepository: IHardwareRepository
+) : BaseViewModel() {
+    private var current: Int = 0
+    private var size: Int = 50
+    var keyManageDataList: MutableList<IsKey> = mutableListOf()
+    var keyFilterData: KeyManageFilterVo? = null
+
+    /**
+     * 删除选中钥匙
+     */
+    fun deleteSelectedKey(keyIds: List<Long>): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            hardwareRepository.deleteKeyByKeyIds(keyIds)
+            emit(true)
+        }
+    }
+
+    /**
+     * 获取钥匙数据
+     */
+    fun getKeyData(
+        filterData: KeyManageFilterVo? = null,
+        nextPage: Boolean = true
+    ): LiveData<Boolean> {
+        if (nextPage) {
+            current += 1
+        } else {
+            current = 0
+            keyManageDataList.clear()
+        }
+        return liveData(Dispatchers.IO) {
+            val keyManageDataPage =
+                hardwareRepository.getKeyInfoPage(filterData, size, size * current)
+            keyManageDataList.addAll(keyManageDataPage)
+            emit(true)
+        }
+    }
+
+    /**
+     * 添加钥匙
+     */
+    fun addKey(data: AddKeyDataVo): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            var isKey = IsKey()
+            isKey = BeanUtils.copyData<IsKey>(data, isKey)
+            hardwareRepository.addKeyInfo(isKey)
+            emit(true)
+        }
+    }
+
+    /**
+     * 更新钥匙
+     */
+    fun updateKey(data: UpdateKeyDataVo): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            var isKey = IsKey()
+            isKey = BeanUtils.copyData<IsKey>(data, isKey)
+            hardwareRepository.updateKeyInfo(isKey)
+            emit(true)
+        }
+    }
+}

+ 78 - 0
app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/LockManageViewModel.kt

@@ -0,0 +1,78 @@
+package com.grkj.iscs.features.main.viewmodel.hardware_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.model.dos.IsLock
+import com.grkj.data.model.vo.AddLockDataVo
+import com.grkj.data.model.vo.LockManageFilterVo
+import com.grkj.data.model.vo.UpdateLockDataVo
+import com.grkj.data.repository.IHardwareRepository
+import com.grkj.ui_base.base.BaseViewModel
+import com.sik.sikcore.data.BeanUtils
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+/**
+ * 挂锁管理界面模型
+ */
+@HiltViewModel
+class LockManageViewModel @Inject constructor(
+    private val hardwareRepository: IHardwareRepository
+) : BaseViewModel() {
+    private var current: Int = 0
+    private val size: Int = 50
+    var lockManageDataList: MutableList<IsLock> = mutableListOf()
+    var lockFilterData: LockManageFilterVo? = null
+
+    /**
+     * 删除选中挂锁
+     */
+    fun deleteSelectedLock(lockIds: List<Long>): LiveData<Boolean> =
+        liveData(Dispatchers.IO) {
+            hardwareRepository.deleteLockByLockIds(lockIds)
+            emit(true)
+        }
+
+    /**
+     * 获取挂锁数据
+     */
+    fun getLockData(
+        filterData: LockManageFilterVo? = null,
+        nextPage: Boolean = true
+    ): LiveData<Boolean> {
+        if (nextPage) {
+            current += 1
+        } else {
+            current = 0
+            lockManageDataList.clear()
+        }
+        return liveData(Dispatchers.IO) {
+            val page = hardwareRepository.getLockInfoPage(filterData, size, size * current)
+            lockManageDataList.addAll(page)
+            emit(true)
+        }
+    }
+
+    /**
+     * 添加挂锁
+     */
+    fun addLock(data: AddLockDataVo): LiveData<Boolean> =
+        liveData(Dispatchers.IO) {
+            var isLock = IsLock()
+            isLock = BeanUtils.copyData<IsLock>(data, isLock)
+            hardwareRepository.addLockInfo(isLock)
+            emit(true)
+        }
+
+    /**
+     * 更新挂锁
+     */
+    fun updateLock(data: UpdateLockDataVo): LiveData<Boolean> =
+        liveData(Dispatchers.IO) {
+            var isLock = IsLock()
+            isLock = BeanUtils.copyData<IsLock>(data, isLock)
+            hardwareRepository.updateLockInfo(isLock)
+            emit(true)
+        }
+}

+ 78 - 0
app/src/main/java/com/grkj/iscs/features/main/viewmodel/hardware_manage/RfidTokenManageViewModel.kt

@@ -0,0 +1,78 @@
+package com.grkj.iscs.features.main.viewmodel.hardware_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.model.dos.IsRfidToken
+import com.grkj.data.model.vo.AddRfidTokenDataVo
+import com.grkj.data.model.vo.RfidTokenManageFilterVo
+import com.grkj.data.model.vo.UpdateRfidTokenDataVo
+import com.grkj.data.repository.IHardwareRepository
+import com.grkj.ui_base.base.BaseViewModel
+import com.sik.sikcore.data.BeanUtils
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+/**
+ * RFID标签管理界面模型
+ */
+@HiltViewModel
+class RfidTokenManageViewModel @Inject constructor(
+    private val hardwareRepository: IHardwareRepository
+) : BaseViewModel() {
+    private var current: Int = 0
+    private val size: Int = 50
+    var rfidTokenManageDataList: MutableList<IsRfidToken> = mutableListOf()
+    var rfidTokenFilterData: RfidTokenManageFilterVo? = null
+
+    /**
+     * 删除选中RFID标签
+     */
+    fun deleteSelectedRfidToken(rfidIds: List<Long>): LiveData<Boolean> =
+        liveData(Dispatchers.IO) {
+            hardwareRepository.deleteRfidTokenByRfidTokenIds(rfidIds)
+            emit(true)
+        }
+
+    /**
+     * 获取RFID标签数据
+     */
+    fun getRfidTokenData(
+        filterData: RfidTokenManageFilterVo? = null,
+        nextPage: Boolean = true
+    ): LiveData<Boolean> {
+        if (nextPage) {
+            current += 1
+        } else {
+            current = 0
+            rfidTokenManageDataList.clear()
+        }
+        return liveData(Dispatchers.IO) {
+            val page = hardwareRepository.getRfidTokenInfoPage(filterData, size, size * current)
+            rfidTokenManageDataList.addAll(page)
+            emit(true)
+        }
+    }
+
+    /**
+     * 添加RFID标签
+     */
+    fun addRfidToken(data: AddRfidTokenDataVo): LiveData<Boolean> =
+        liveData(Dispatchers.IO) {
+            var token = IsRfidToken()
+            token = BeanUtils.copyData<IsRfidToken>(data, token)
+            hardwareRepository.addRfidTokenInfo(token)
+            emit(true)
+        }
+
+    /**
+     * 更新RFID标签
+     */
+    fun updateRfidToken(data: UpdateRfidTokenDataVo): LiveData<Boolean> =
+        liveData(Dispatchers.IO) {
+            var token = IsRfidToken()
+            token = BeanUtils.copyData<IsRfidToken>(data, token)
+            hardwareRepository.updateRfidTokenInfo(token)
+            emit(true)
+        }
+}

+ 6 - 0
app/src/main/res/drawable/common_btn_green_bg.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/common_radius_small" />
+    <solid android:color="@color/common_btn_green_bg" />
+</shape>

+ 6 - 0
app/src/main/res/drawable/common_btn_red_bg.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/common_radius_small" />
+    <solid android:color="@color/common_btn_red_bg" />
+</shape>

+ 8 - 0
app/src/main/res/drawable/common_status_circle.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="oval">
+    <size
+        android:width="@dimen/common_status_circle_small"
+        android:height="@dimen/common_status_circle_small" />
+    <solid android:color="@color/common_status_red" />
+</shape>

+ 21 - 0
app/src/main/res/drawable/dock_has_lock.xml

@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="76dp"
+    android:height="182dp"
+    android:viewportWidth="76"
+    android:viewportHeight="182">
+    <path
+        android:fillColor="#000000"
+        android:pathData="M10,0L66,0A10,10 0,0 1,76 10L76,172A10,10 0,0 1,66 182L10,182A10,10 0,0 1,0 172L0,10A10,10 0,0 1,10 0z" />
+    <path
+        android:fillColor="#D9D9D9"
+        android:pathData=" M28,31
+        H48
+        A10,10 0 0 1 58,41
+        V141
+        A10,10 0 0 1 48,151
+        H28
+        A10,10 0 0 1 18,141
+        V41
+        A10,10 0 0 1 28,31
+        Z" />
+</vector>

+ 5 - 0
app/src/main/res/drawable/dock_key_selector.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="false" android:drawable="@mipmap/dock_no_key" />
+    <item android:state_selected="true" android:drawable="@mipmap/dock_has_key" />
+</selector>

+ 5 - 0
app/src/main/res/drawable/dock_lock_selector.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_selected="false" android:drawable="@drawable/dock_no_lock" />
+    <item android:state_selected="true" android:drawable="@drawable/dock_has_lock" />
+</selector>

+ 9 - 0
app/src/main/res/drawable/dock_no_lock.xml

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="76dp"
+    android:height="182dp"
+    android:viewportWidth="76"
+    android:viewportHeight="182">
+  <path
+      android:pathData="M10,0L66,0A10,10 0,0 1,76 10L76,172A10,10 0,0 1,66 182L10,182A10,10 0,0 1,0 172L0,10A10,10 0,0 1,10 0z"
+      android:fillColor="#000000"/>
+</vector>

+ 203 - 0
app/src/main/res/layout/dialog_add_card.xml

@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <!-- 标题栏 -->
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/card_manage_new_card_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:padding="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <!-- 内容区 -->
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <!-- 卡片 NFC -->
+            <TextView
+                android:id="@+id/card_nfc_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/card_nfc"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/card_nfc_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_card_nfc"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/card_nfc_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/card_nfc_tv" />
+
+            <!-- 卡片昵称 -->
+            <TextView
+                android:id="@+id/username_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/card_nickname"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/card_nfc_tv"
+                app:layout_constraintTop_toBottomOf="@+id/card_nfc_tv" />
+
+            <EditText
+                android:id="@+id/username_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_card_nickname"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/username_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/username_tv" />
+
+            <!-- 状态 -->
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/username_tv"
+                app:layout_constraintTop_toBottomOf="@+id/username_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+
+            <!-- 备注 -->
+            <TextView
+                android:id="@+id/remark_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/remark"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="@+id/status_tv"
+                app:layout_constraintTop_toBottomOf="@+id/status_tv" />
+
+            <EditText
+                android:id="@+id/remark_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_remark"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/remark_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/remark_tv" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <!-- 底部按钮 -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 233 - 0
app/src/main/res/layout/dialog_add_key.xml

@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/key_manage_new_key_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <TextView
+                android:id="@+id/key_code_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/key_name"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/key_code_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_name"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/key_code_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/key_code_tv"
+                app:layout_constraintTop_toTopOf="@+id/key_code_tv" />
+
+
+            <TextView
+                android:id="@+id/key_nfc_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/key_nfc"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/key_code_tv"
+                app:layout_constraintTop_toBottomOf="@+id/key_code_tv" />
+
+            <EditText
+                android:id="@+id/key_nfc_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_nfc"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/key_nfc_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/key_nfc_tv"
+                app:layout_constraintTop_toTopOf="@+id/key_nfc_tv" />
+
+
+            <TextView
+                android:id="@+id/key_mac_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/key_mac"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/key_nfc_tv"
+                app:layout_constraintTop_toBottomOf="@+id/key_nfc_tv" />
+
+            <EditText
+                android:id="@+id/key_mac_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_mac"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/key_mac_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/key_mac_tv"
+                app:layout_constraintTop_toTopOf="@+id/key_mac_tv" />
+
+
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/key_mac_tv"
+                app:layout_constraintTop_toBottomOf="@+id/key_mac_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+
+
+            <TextView
+                android:id="@+id/remark_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/remark"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toBottomOf="@+id/status_tv" />
+
+            <EditText
+                android:id="@+id/remark_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_remark"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/remark_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/remark_tv"
+                app:layout_constraintTop_toTopOf="@+id/remark_tv" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 203 - 0
app/src/main/res/layout/dialog_add_lock.xml

@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <!-- 标题栏 -->
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/lock_manage_new_lock_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:padding="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <!-- 内容区 -->
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <!-- 挂锁编号 -->
+            <TextView
+                android:id="@+id/lock_code_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/lock_code"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/lock_code_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_nfc"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/lock_code_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/lock_code_tv" />
+
+            <!-- 挂锁 NFC -->
+            <TextView
+                android:id="@+id/lock_nfc_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/lock_nfc"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/lock_code_tv"
+                app:layout_constraintTop_toBottomOf="@+id/lock_code_tv" />
+
+            <EditText
+                android:id="@+id/lock_nfc_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_nfc"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/lock_nfc_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/lock_nfc_tv" />
+
+            <!-- 状态 -->
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/lock_nfc_tv"
+                app:layout_constraintTop_toBottomOf="@+id/lock_nfc_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+
+            <!-- 备注 -->
+            <TextView
+                android:id="@+id/remark_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/remark"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="@+id/status_tv"
+                app:layout_constraintTop_toBottomOf="@+id/status_tv" />
+
+            <EditText
+                android:id="@+id/remark_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_nfc"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/remark_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/remark_tv" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <!-- 底部按钮 -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 203 - 0
app/src/main/res/layout/dialog_add_rfid_token.xml

@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <!-- 标题栏 -->
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/rfid_token_manage_new_rfid_token_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:padding="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <!-- 内容区 -->
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <!-- RFID 编号 -->
+            <TextView
+                android:id="@+id/rfid_code_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/rfid_code"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/rfid_code_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_rfid_code"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/rfid_code_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/rfid_code_tv" />
+
+            <!-- RFID 值 -->
+            <TextView
+                android:id="@+id/rfid_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/rfid"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/rfid_code_tv"
+                app:layout_constraintTop_toBottomOf="@+id/rfid_code_tv" />
+
+            <EditText
+                android:id="@+id/rfid_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_rfid"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/rfid_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/rfid_tv" />
+
+            <!-- 状态 -->
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/rfid_tv"
+                app:layout_constraintTop_toBottomOf="@+id/rfid_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+
+            <!-- 备注 -->
+            <TextView
+                android:id="@+id/remark_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/remark"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="@+id/status_tv"
+                app:layout_constraintTop_toBottomOf="@+id/status_tv" />
+
+            <EditText
+                android:id="@+id/remark_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_remark"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/remark_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/remark_tv" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <!-- 底部按钮 -->
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="end"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 178 - 0
app/src/main/res/layout/dialog_filter_card.xml

@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_normal"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/user_manage_filter_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <!-- 卡片 NFC -->
+            <TextView
+                android:id="@+id/card_nfc_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/card_nfc"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/card_nfc_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_card_nfc"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/card_nfc_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/card_nfc_tv"
+                app:layout_constraintTop_toTopOf="@+id/card_nfc_tv" />
+
+            <!-- 卡片昵称 -->
+            <TextView
+                android:id="@+id/username_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/card_nickname"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/card_nfc_tv"
+                app:layout_constraintTop_toBottomOf="@+id/card_nfc_tv" />
+
+            <EditText
+                android:id="@+id/username_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_card_nickname"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/username_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/username_tv"
+                app:layout_constraintTop_toTopOf="@+id/username_tv" />
+
+            <!-- 状态 -->
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/username_tv"
+                app:layout_constraintTop_toBottomOf="@+id/username_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 205 - 0
app/src/main/res/layout/dialog_filter_key.xml

@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_normal"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/user_manage_filter_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <TextView
+                android:id="@+id/key_code_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/key_name"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/key_code_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_name"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/key_code_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/key_code_tv"
+                app:layout_constraintTop_toTopOf="@+id/key_code_tv" />
+
+
+            <TextView
+                android:id="@+id/key_nfc_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/key_nfc"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/key_code_tv"
+                app:layout_constraintTop_toBottomOf="@+id/key_code_tv" />
+
+            <EditText
+                android:id="@+id/key_nfc_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_nfc"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/key_nfc_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/key_nfc_tv"
+                app:layout_constraintTop_toTopOf="@+id/key_nfc_tv" />
+
+
+            <TextView
+                android:id="@+id/key_mac_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/key_mac"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/key_nfc_tv"
+                app:layout_constraintTop_toBottomOf="@+id/key_nfc_tv" />
+
+            <EditText
+                android:id="@+id/key_mac_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_mac"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/key_mac_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/key_mac_tv"
+                app:layout_constraintTop_toTopOf="@+id/key_mac_tv" />
+
+
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/key_mac_tv"
+                app:layout_constraintTop_toBottomOf="@+id/key_mac_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 178 - 0
app/src/main/res/layout/dialog_filter_lock.xml

@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_normal"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/user_manage_filter_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <!-- 挂锁编号 -->
+            <TextView
+                android:id="@+id/lock_code_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/lock_code"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/lock_code_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_lock_code"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/lock_code_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/lock_code_tv"
+                app:layout_constraintTop_toTopOf="@+id/lock_code_tv" />
+
+            <!-- 挂锁 NFC -->
+            <TextView
+                android:id="@+id/lock_nfc_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/lock_nfc"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/lock_code_tv"
+                app:layout_constraintTop_toBottomOf="@+id/lock_code_tv" />
+
+            <EditText
+                android:id="@+id/lock_nfc_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_lock_nfc"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/lock_nfc_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/lock_nfc_tv"
+                app:layout_constraintTop_toTopOf="@+id/lock_nfc_tv" />
+
+            <!-- 状态 -->
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/lock_nfc_tv"
+                app:layout_constraintTop_toBottomOf="@+id/lock_nfc_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 178 - 0
app/src/main/res/layout/dialog_filter_rfid_token.xml

@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_normal"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/user_manage_filter_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <!-- RFID 编号 -->
+            <TextView
+                android:id="@+id/rfid_code_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/rfid_code"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/rfid_code_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_rfid_code"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/rfid_code_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/rfid_code_tv"
+                app:layout_constraintTop_toTopOf="@+id/rfid_code_tv" />
+
+            <!-- RFID 值 -->
+            <TextView
+                android:id="@+id/rfid_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/rfid"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/rfid_code_tv"
+                app:layout_constraintTop_toBottomOf="@+id/rfid_code_tv" />
+
+            <EditText
+                android:id="@+id/rfid_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_rfid"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/rfid_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/rfid_tv"
+                app:layout_constraintTop_toTopOf="@+id/rfid_tv" />
+
+            <!-- 状态 -->
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/rfid_tv"
+                app:layout_constraintTop_toBottomOf="@+id/rfid_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 203 - 0
app/src/main/res/layout/dialog_update_card.xml

@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <!-- 标题栏 -->
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/card_manage_card_detail_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <!-- 内容区 -->
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <!-- 卡片 NFC -->
+            <TextView
+                android:id="@+id/card_nfc_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/card_nfc"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/card_nfc_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_card_nfc"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/card_nfc_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/card_nfc_tv" />
+
+            <!-- 卡片昵称 -->
+            <TextView
+                android:id="@+id/username_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/card_nickname"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/card_nfc_tv"
+                app:layout_constraintTop_toBottomOf="@+id/card_nfc_tv" />
+
+            <EditText
+                android:id="@+id/username_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_card_nickname"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/username_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/username_tv" />
+
+            <!-- 状态 -->
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/username_tv"
+                app:layout_constraintTop_toBottomOf="@+id/username_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+
+            <!-- 备注 -->
+            <TextView
+                android:id="@+id/remark_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/remark"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toBottomOf="@+id/status_tv" />
+
+            <EditText
+                android:id="@+id/remark_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_remark"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/remark_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/remark_tv" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 233 - 0
app/src/main/res/layout/dialog_update_key.xml

@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/key_manage_key_detail_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <TextView
+                android:id="@+id/key_code_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/key_name"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/key_code_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_name"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/key_code_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/key_code_tv"
+                app:layout_constraintTop_toTopOf="@+id/key_code_tv" />
+
+
+            <TextView
+                android:id="@+id/key_nfc_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/key_nfc"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/key_code_tv"
+                app:layout_constraintTop_toBottomOf="@+id/key_code_tv" />
+
+            <EditText
+                android:id="@+id/key_nfc_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_nfc"
+                android:maxLines="1"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/key_nfc_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/key_nfc_tv"
+                app:layout_constraintTop_toTopOf="@+id/key_nfc_tv" />
+
+
+            <TextView
+                android:id="@+id/key_mac_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/key_mac"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/key_nfc_tv"
+                app:layout_constraintTop_toBottomOf="@+id/key_nfc_tv" />
+
+            <EditText
+                android:id="@+id/key_mac_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_key_mac"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/key_mac_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/key_mac_tv"
+                app:layout_constraintTop_toTopOf="@+id/key_mac_tv" />
+
+
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/key_mac_tv"
+                app:layout_constraintTop_toBottomOf="@+id/key_mac_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+
+
+            <TextView
+                android:id="@+id/remark_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/remark"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toBottomOf="@+id/status_tv" />
+
+            <EditText
+                android:id="@+id/remark_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_remark"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBottom_toBottomOf="@+id/remark_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/remark_tv"
+                app:layout_constraintTop_toTopOf="@+id/remark_tv" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 203 - 0
app/src/main/res/layout/dialog_update_lock.xml

@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <!-- 标题栏 -->
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/lock_manage_lock_detail_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <!-- 内容区 -->
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <!-- 挂锁编号 -->
+            <TextView
+                android:id="@+id/lock_code_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/lock_code"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/lock_code_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_lock_code"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/lock_code_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/lock_code_tv" />
+
+            <!-- 挂锁 NFC -->
+            <TextView
+                android:id="@+id/lock_nfc_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/lock_nfc"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/lock_code_tv"
+                app:layout_constraintTop_toBottomOf="@+id/lock_code_tv" />
+
+            <EditText
+                android:id="@+id/lock_nfc_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_lock_nfc"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/lock_nfc_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/lock_nfc_tv" />
+
+            <!-- 状态 -->
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/lock_nfc_tv"
+                app:layout_constraintTop_toBottomOf="@+id/lock_nfc_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+
+            <!-- 备注 -->
+            <TextView
+                android:id="@+id/remark_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/remark"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toBottomOf="@+id/status_tv" />
+
+            <EditText
+                android:id="@+id/remark_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_remark"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/remark_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/remark_tv" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 203 - 0
app/src/main/res/layout/dialog_update_rfid_token.xml

@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <LinearLayout
+        android:layout_width="@dimen/dialog_common_root_width"
+        android:layout_height="@dimen/dialog_common_root_height_big"
+        android:background="@drawable/common_card_bg"
+        android:orientation="vertical">
+
+        <!-- 标题栏 -->
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:text="@string/rfid_token_manage_rfid_token_detail_title"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <ImageView
+                android:id="@+id/close_iv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:src="@drawable/icon_close" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <!-- 内容区 -->
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:paddingHorizontal="@dimen/dialog_content_normal_padding_horizontal">
+
+            <!-- RFID 编号 -->
+            <TextView
+                android:id="@+id/rfid_code_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/rfid_code"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <EditText
+                android:id="@+id/rfid_code_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_rfid_code"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/rfid_code_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/rfid_code_tv" />
+
+            <!-- RFID 值 -->
+            <TextView
+                android:id="@+id/rfid_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/rfid"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/rfid_code_tv"
+                app:layout_constraintTop_toBottomOf="@+id/rfid_code_tv" />
+
+            <EditText
+                android:id="@+id/rfid_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_rfid"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/rfid_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/rfid_tv" />
+
+            <!-- 状态 -->
+            <TextView
+                android:id="@+id/status_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/manage_filter_status"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/rfid_tv"
+                app:layout_constraintTop_toBottomOf="@+id/rfid_tv" />
+
+            <RadioGroup
+                android:id="@+id/status_rg"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintBottom_toBottomOf="@+id/status_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toTopOf="@+id/status_tv">
+
+                <RadioButton
+                    android:id="@+id/activate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/normal"
+                    android:textSize="@dimen/common_text_size" />
+
+                <RadioButton
+                    android:id="@+id/deactivate_rb"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="@dimen/common_spacing"
+                    android:text="@string/abnormal"
+                    android:textSize="@dimen/common_text_size" />
+            </RadioGroup>
+
+            <!-- 备注 -->
+            <TextView
+                android:id="@+id/remark_tv"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/common_spacing_2x"
+                android:text="@string/remark"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintEnd_toEndOf="@+id/status_tv"
+                app:layout_constraintTop_toBottomOf="@+id/status_tv" />
+
+            <EditText
+                android:id="@+id/remark_et"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/bg_common_input"
+                android:hint="@string/please_input_remark"
+                android:paddingHorizontal="@dimen/common_spacing"
+                android:paddingVertical="2dp"
+                android:singleLine="true"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_text_size"
+                app:layout_constraintBaseline_toBaselineOf="@+id/remark_tv"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toEndOf="@+id/remark_tv" />
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="right"
+            android:orientation="horizontal"
+            android:padding="@dimen/common_spacing">
+
+            <TextView
+                android:id="@+id/confirm"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/confirm"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/cancel"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>

+ 155 - 0
app/src/main/res/layout/fragment_card_manage.xml

@@ -0,0 +1,155 @@
+<?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="match_parent"
+        android:layout_margin="@dimen/common_spacing_2x"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <ImageView
+                android:layout_width="@dimen/common_spacing_2x"
+                android:layout_height="@dimen/common_spacing_2x"
+                android:src="@mipmap/icon_data_manage_menu_user_manage" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:layout_weight="1"
+                android:text="@string/card_manage_title"
+                android:textColor="@color/black"
+                android:textSize="24sp" />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/back"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing">
+
+
+            <TextView
+                android:id="@+id/add"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/insert"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+
+            <TextView
+                android:id="@+id/delete"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/delete"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+            <TextView
+                android:id="@+id/filter"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/filter"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/common_spacing_2x"
+            android:layout_marginTop="@dimen/common_spacing"
+            android:background="@drawable/common_card_bg"
+            android:divider="@drawable/divider_table"
+            android:showDividers="middle">
+
+            <CheckBox
+                android:id="@+id/select_all"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center"
+                android:layout_margin="@dimen/common_spacing" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/card_code"
+                android:textSize="@dimen/common_text_size" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/card_nfc"
+                android:textSize="@dimen/common_text_size" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/username"
+                android:textSize="@dimen/common_text_size" />
+        </LinearLayout>
+
+        <com.scwang.smart.refresh.layout.SmartRefreshLayout
+            android:id="@+id/refresh_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/common_spacing_2x"
+            android:layout_marginBottom="@dimen/common_spacing">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/list_rv"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/common_card_bg" />
+        </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+    </LinearLayout>
+</layout>

+ 155 - 0
app/src/main/res/layout/fragment_key_manage.xml

@@ -0,0 +1,155 @@
+<?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="match_parent"
+        android:layout_margin="@dimen/common_spacing_2x"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <ImageView
+                android:layout_width="@dimen/common_spacing_2x"
+                android:layout_height="@dimen/common_spacing_2x"
+                android:src="@mipmap/icon_data_manage_menu_user_manage" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:layout_weight="1"
+                android:text="@string/key_manage_title"
+                android:textColor="@color/black"
+                android:textSize="24sp" />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/back"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing">
+
+
+            <TextView
+                android:id="@+id/add"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/insert"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+
+            <TextView
+                android:id="@+id/delete"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/delete"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+            <TextView
+                android:id="@+id/filter"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/filter"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/common_spacing_2x"
+            android:layout_marginTop="@dimen/common_spacing"
+            android:background="@drawable/common_card_bg"
+            android:divider="@drawable/divider_table"
+            android:showDividers="middle">
+
+            <CheckBox
+                android:id="@+id/select_all"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center"
+                android:layout_margin="@dimen/common_spacing" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/key_name"
+                android:textSize="@dimen/common_text_size" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/key_nfc"
+                android:textSize="@dimen/common_text_size" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/key_mac"
+                android:textSize="@dimen/common_text_size" />
+        </LinearLayout>
+
+        <com.scwang.smart.refresh.layout.SmartRefreshLayout
+            android:id="@+id/refresh_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/common_spacing_2x"
+            android:layout_marginBottom="@dimen/common_spacing">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/list_rv"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/common_card_bg" />
+        </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+    </LinearLayout>
+</layout>

+ 155 - 0
app/src/main/res/layout/fragment_lock_manage.xml

@@ -0,0 +1,155 @@
+<?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="match_parent"
+        android:layout_margin="@dimen/common_spacing_2x"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <ImageView
+                android:layout_width="@dimen/common_spacing_2x"
+                android:layout_height="@dimen/common_spacing_2x"
+                android:src="@mipmap/icon_data_manage_menu_user_manage" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:layout_weight="1"
+                android:text="@string/key_manage_title"
+                android:textColor="@color/black"
+                android:textSize="24sp" />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/back"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing">
+
+
+            <TextView
+                android:id="@+id/add"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/insert"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+
+            <TextView
+                android:id="@+id/delete"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/delete"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+            <TextView
+                android:id="@+id/filter"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/filter"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/common_spacing_2x"
+            android:layout_marginTop="@dimen/common_spacing"
+            android:background="@drawable/common_card_bg"
+            android:divider="@drawable/divider_table"
+            android:showDividers="middle">
+
+            <CheckBox
+                android:id="@+id/select_all"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center"
+                android:layout_margin="@dimen/common_spacing" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/key_name"
+                android:textSize="@dimen/common_text_size" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/key_nfc"
+                android:textSize="@dimen/common_text_size" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/key_mac"
+                android:textSize="@dimen/common_text_size" />
+        </LinearLayout>
+
+        <com.scwang.smart.refresh.layout.SmartRefreshLayout
+            android:id="@+id/refresh_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/common_spacing_2x"
+            android:layout_marginBottom="@dimen/common_spacing">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/list_rv"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/common_card_bg" />
+        </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+    </LinearLayout>
+</layout>

+ 155 - 0
app/src/main/res/layout/fragment_rfid_token_manage.xml

@@ -0,0 +1,155 @@
+<?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="match_parent"
+        android:layout_margin="@dimen/common_spacing_2x"
+        android:background="@drawable/home_card_bg"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/title_layout"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_vertical"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing_small">
+
+            <ImageView
+                android:layout_width="@dimen/common_spacing_2x"
+                android:layout_height="@dimen/common_spacing_2x"
+                android:src="@mipmap/icon_data_manage_menu_user_manage" />
+
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:layout_weight="1"
+                android:text="@string/key_manage_title"
+                android:textColor="@color/black"
+                android:textSize="24sp" />
+
+            <TextView
+                android:id="@+id/back"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/back"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+
+        <View
+            android:layout_width="match_parent"
+            android:layout_height="@dimen/divider_line_space"
+            android:background="@color/black" />
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            android:paddingHorizontal="@dimen/common_spacing"
+            android:paddingVertical="@dimen/common_spacing">
+
+
+            <TextView
+                android:id="@+id/add"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/insert"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+
+            <TextView
+                android:id="@+id/delete"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/delete"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+
+
+            <View
+                android:layout_width="0dp"
+                android:layout_height="0dp"
+                android:layout_weight="1" />
+
+            <TextView
+                android:id="@+id/filter"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/common_spacing"
+                android:background="@drawable/common_btn"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:text="@string/filter"
+                android:textColor="@color/black"
+                android:textSize="@dimen/common_btn_text_size" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginHorizontal="@dimen/common_spacing_2x"
+            android:layout_marginTop="@dimen/common_spacing"
+            android:background="@drawable/common_card_bg"
+            android:divider="@drawable/divider_table"
+            android:showDividers="middle">
+
+            <CheckBox
+                android:id="@+id/select_all"
+                android:layout_width="30dp"
+                android:layout_height="30dp"
+                android:layout_gravity="center"
+                android:layout_margin="@dimen/common_spacing" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/key_name"
+                android:textSize="@dimen/common_text_size" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/key_nfc"
+                android:textSize="@dimen/common_text_size" />
+
+            <TextView
+                android:layout_width="0dp"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                android:gravity="center"
+                android:text="@string/key_mac"
+                android:textSize="@dimen/common_text_size" />
+        </LinearLayout>
+
+        <com.scwang.smart.refresh.layout.SmartRefreshLayout
+            android:id="@+id/refresh_layout"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_marginHorizontal="@dimen/common_spacing_2x"
+            android:layout_marginBottom="@dimen/common_spacing">
+
+            <androidx.recyclerview.widget.RecyclerView
+                android:id="@+id/list_rv"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:background="@drawable/common_card_bg" />
+        </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+    </LinearLayout>
+</layout>

+ 40 - 0
app/src/main/res/layout/item_card_manage.xml

@@ -0,0 +1,40 @@
+<?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="30dp"
+            android:layout_gravity="center"
+            android:layout_margin="@dimen/common_spacing" />
+
+        <TextView
+            android:id="@+id/card_code"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+
+        <TextView
+            android:id="@+id/card_nfc"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+
+        <TextView
+            android:id="@+id/card_nickname"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+    </LinearLayout>
+</layout>

+ 44 - 0
app/src/main/res/layout/item_device_registration_key.xml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <RelativeLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <ImageView
+            android:id="@+id/iv_key"
+            android:layout_width="50dp"
+            android:layout_height="35dp"
+            android:layout_centerHorizontal="true"
+            android:background="@drawable/dock_key_selector" />
+
+        <TextView
+            android:id="@+id/tv_new_device"
+            style="@style/CommonTextView"
+            android:layout_below="@+id/iv_key"
+            android:layout_alignLeft="@+id/iv_key"
+            android:layout_alignRight="@+id/iv_key"
+            android:layout_marginTop="5dp"
+            android:background="@drawable/common_btn_red_bg"
+            android:text="@string/new_device"
+            android:visibility="invisible" />
+
+        <TextView
+            android:id="@+id/tv_new_device_mac"
+            style="@style/CommonTextView"
+            android:layout_below="@+id/tv_new_device"
+            android:layout_alignLeft="@+id/iv_key"
+            android:layout_alignRight="@+id/iv_key"
+            android:layout_marginTop="5dp"
+            android:background="@drawable/common_btn_green_bg"
+            android:visibility="invisible" />
+
+        <View
+            android:id="@+id/v_buckle_status"
+            android:layout_width="@dimen/common_status_circle_small"
+            android:layout_height="@dimen/common_status_circle_small"
+            android:layout_toRightOf="@+id/iv_key"
+            android:background="@drawable/common_status_circle"
+            android:visibility="invisible" />
+    </RelativeLayout>
+</layout>

+ 8 - 0
app/src/main/res/layout/item_device_registration_key_layout.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_key_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+</layout>

+ 28 - 0
app/src/main/res/layout/item_device_registration_lock.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout  xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/common_spacing_small"
+        android:orientation="horizontal">
+
+        <FrameLayout
+            android:id="@+id/root"
+            android:layout_width="20dp"
+            android:layout_height="70dp"
+            android:background="@drawable/dock_lock_selector" />
+
+        <TextView
+            android:id="@+id/tv_new_device"
+            style="@style/CommonTextView"
+            android:layout_below="@+id/root"
+            android:layout_alignLeft="@+id/root"
+            android:layout_alignRight="@+id/root"
+            android:layout_marginTop="5dp"
+            android:background="@drawable/common_btn_red_bg"
+            android:text="@string/new_device"
+            android:visibility="invisible" />
+    </RelativeLayout>
+
+</layout>

+ 8 - 0
app/src/main/res/layout/item_device_registration_lock_layout.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_lock_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+</layout>

+ 8 - 0
app/src/main/res/layout/item_device_registration_portable_layout.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_portable_layout"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+</layout>

+ 40 - 0
app/src/main/res/layout/item_key_manage.xml

@@ -0,0 +1,40 @@
+<?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="30dp"
+            android:layout_gravity="center"
+            android:layout_margin="@dimen/common_spacing" />
+
+        <TextView
+            android:id="@+id/key_code"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+
+        <TextView
+            android:id="@+id/key_nfc"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+
+        <TextView
+            android:id="@+id/key_mac"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+    </LinearLayout>
+</layout>

+ 40 - 0
app/src/main/res/layout/item_lock_manage.xml

@@ -0,0 +1,40 @@
+<?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="30dp"
+            android:layout_gravity="center"
+            android:layout_margin="@dimen/common_spacing" />
+
+        <TextView
+            android:id="@+id/lock_code"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+
+        <TextView
+            android:id="@+id/lock_nfc"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+
+        <TextView
+            android:id="@+id/status"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+    </LinearLayout>
+</layout>

+ 40 - 0
app/src/main/res/layout/item_rfid_token_manage.xml

@@ -0,0 +1,40 @@
+<?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="30dp"
+            android:layout_gravity="center"
+            android:layout_margin="@dimen/common_spacing" />
+
+        <TextView
+            android:id="@+id/rfid_code"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+
+        <TextView
+            android:id="@+id/rfid"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+
+        <TextView
+            android:id="@+id/status"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="@dimen/common_text_size" />
+    </LinearLayout>
+</layout>

二进制
app/src/main/res/mipmap-hdpi/dock_has_key.png


二进制
app/src/main/res/mipmap-hdpi/dock_no_key.png


+ 30 - 1
app/src/main/res/navigation/nav_hardware_manage.xml

@@ -7,5 +7,34 @@
     <fragment
         android:id="@+id/hardwareManageHomeFragment"
         android:name="com.grkj.iscs.features.main.fragment.hardware_manage.HardwareManageHomeFragment"
-        android:label="HardwareManageHomeFragment" />
+        android:label="HardwareManageHomeFragment" >
+        <action
+            android:id="@+id/action_hardwareManageHomeFragment_to_rfidTokenManageFragment"
+            app:destination="@id/rfidTokenManageFragment" />
+        <action
+            android:id="@+id/action_hardwareManageHomeFragment_to_cardManageFragment"
+            app:destination="@id/cardManageFragment" />
+        <action
+            android:id="@+id/action_hardwareManageHomeFragment_to_lockManageFragment"
+            app:destination="@id/lockManageFragment" />
+        <action
+            android:id="@+id/action_hardwareManageHomeFragment_to_keyManageFragment"
+            app:destination="@id/keyManageFragment" />
+    </fragment>
+    <fragment
+        android:id="@+id/keyManageFragment"
+        android:name="com.grkj.iscs.features.main.fragment.hardware_manage.KeyManageFragment"
+        android:label="KeyManageFragment" />
+    <fragment
+        android:id="@+id/lockManageFragment"
+        android:name="com.grkj.iscs.features.main.fragment.hardware_manage.LockManageFragment"
+        android:label="LockManageFragment" />
+    <fragment
+        android:id="@+id/cardManageFragment"
+        android:name="com.grkj.iscs.features.main.fragment.hardware_manage.CardManageFragment"
+        android:label="CardManageFragment" />
+    <fragment
+        android:id="@+id/rfidTokenManageFragment"
+        android:name="com.grkj.iscs.features.main.fragment.hardware_manage.RfidTokenManageFragment"
+        android:label="RfidTokenManageFragment" />
 </navigation>

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

@@ -226,4 +226,93 @@
     <string name="recognized_point_rfid">Recognized point rfid</string>
     <string name="operation">Operation</string>
     <string name="recognized_card_rfid">Recognized card rfid</string>
+    <string name="new_device">New</string>
+    <string name="key_manage_title">Key manage</string>
+    <string name="key_name">Key code</string>
+    <string name="key_nfc">Key NFC</string>
+    <string name="key_mac">Key MAC</string>
+    <string name="add_key_succeed">Add key succeed</string>
+    <string name="add_key_failed">Add key failed</string>
+    <string name="update_key_succeed">Update key succeed</string>
+    <string name="update_key_failed">Update key failed</string>
+    <!-- lock -->
+    <string name="add_lock_succeed">Add lock succeed</string>
+    <string name="add_lock_failed">Add lock failed</string>
+    <string name="update_lock_succeed">Update lock succeed</string>
+    <string name="update_lock_failed">Update lock failed</string>
+
+    <!-- card -->
+    <string name="add_card_succeed">Add card succeed</string>
+    <string name="add_card_failed">Add card failed</string>
+    <string name="update_card_succeed">Update card succeed</string>
+    <string name="update_card_failed">Update card failed</string>
+
+    <!-- rfid_token -->
+    <string name="add_rfid_token_succeed">Add RFID token succeed</string>
+    <string name="add_rfid_token_failed">Add RFID token failed</string>
+    <string name="update_rfid_token_succeed">Update RFID token succeed</string>
+    <string name="update_rfid_token_failed">Update RFID token failed</string>
+    <string name="please_select_key">Please select key</string>
+    <string name="check_delete_key">Are you sure you want to delete the selected key</string>
+    <string name="key_manage_delete_succeed">Key deleted successfully</string>
+    <string name="key_manage_delete_failed">Key deletion failed</string>
+    <string name="key_manage_new_key_title">New key</string>
+    <string name="key_manage_key_detail_title">Key detail</string>
+
+    <!-- lock -->
+    <string name="please_select_lock">Please select lock</string>
+    <string name="check_delete_lock">Are you sure you want to delete the selected lock</string>
+    <string name="lock_manage_delete_succeed">Lock deleted successfully</string>
+    <string name="lock_manage_delete_failed">Lock deletion failed</string>
+    <string name="lock_manage_lock_detail_title">Lock detail</string>
+
+    <!-- card -->
+    <string name="please_select_card">Please select card</string>
+    <string name="check_delete_card">Are you sure you want to delete the selected card</string>
+    <string name="card_manage_delete_succeed">Card deleted successfully</string>
+    <string name="card_manage_delete_failed">Card deletion failed</string>
+    <string name="card_manage_card_detail_title">Card detail</string>
+
+    <!-- rfid_token -->
+    <string name="please_select_rfid_token">Please select RFID token</string>
+    <string name="check_delete_rfid_token">Are you sure you want to delete the selected RFID token</string>
+    <string name="rfid_token_manage_delete_succeed">RFID token deleted successfully</string>
+    <string name="rfid_token_manage_delete_failed">RFID token deletion failed</string>
+    <string name="rfid_token_manage_rfid_token_detail_title">RFID token detail</string>
+    <string name="please_input_key_name">Please input the key name</string>
+    <string name="please_input_key_nfc">Please input key nfc</string>
+    <string name="please_input_key_mac">Please input key mac</string>
+    <string name="normal">Normal</string>
+    <string name="abnormal">Abnormal</string>
+    <string name="remark">Remark</string>
+    <string name="please_input_remark">Please input remark</string>
+
+    <string name="confirm">Confirm</string>
+    <string name="cancel">Cancel</string>
+
+    <!-- 挂锁 -->
+    <string name="lock_manage_new_lock_title">New lock</string>
+    <string name="lock_code">Lock code</string>
+    <string name="please_input_lock_code">Please input lock code</string>
+    <string name="lock_nfc">Lock NFC</string>
+    <string name="please_input_lock_nfc">Please input lock NFC</string>
+
+    <!-- 卡片 -->
+    <string name="card_manage_new_card_title">New card</string>
+    <string name="card_nfc">Card NFC</string>
+    <string name="please_input_card_nfc">Please input card NFC</string>
+    <string name="card_nickname">Username</string>
+    <string name="please_input_card_nickname">Please input user username</string>
+
+    <!-- RFID 标签 -->
+    <string name="rfid_token_manage_new_rfid_token_title">New RFID token</string>
+    <string name="rfid_code">RFID code</string>
+    <string name="please_input_rfid_code">Please input RFID code</string>
+    <string name="rfid">RFID</string>
+    <string name="please_input_rfid">Please input RFID</string>
+    <string name="view">View</string>
+    <string name="card_code">Card code</string>
+    <string name="username">Username</string>
+    <string name="card_manage_title">Card Manage</string>
+
 </resources>

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

@@ -226,4 +226,93 @@
     <string name="recognized_point_rfid">已识别的点位RFID</string>
     <string name="operation">操作</string>
     <string name="recognized_card_rfid">已识别的卡片RFID</string>
+    <string name="new_device">New</string>
+    <string name="key_manage_title">钥匙管理</string>
+    <string name="key_name">钥匙名称</string>
+    <string name="key_nfc">钥匙NFC</string>
+    <string name="key_mac">钥匙MAC</string>
+    <string name="add_key_succeed">新增钥匙成功</string>
+    <string name="add_key_failed">新增钥匙失败</string>
+    <string name="update_key_succeed">更新钥匙成功</string>
+    <string name="update_key_failed">更新钥匙失败</string>
+    <!-- lock -->
+    <string name="add_lock_succeed">添加挂锁成功</string>
+    <string name="add_lock_failed">添加挂锁失败</string>
+    <string name="update_lock_succeed">更新挂锁成功</string>
+    <string name="update_lock_failed">更新挂锁失败</string>
+
+    <!-- card -->
+    <string name="add_card_succeed">添加卡片成功</string>
+    <string name="add_card_failed">添加卡片失败</string>
+    <string name="update_card_succeed">更新卡片成功</string>
+    <string name="update_card_failed">更新卡片失败</string>
+
+    <!-- rfid_token -->
+    <string name="add_rfid_token_succeed">添加RFID标签成功</string>
+    <string name="add_rfid_token_failed">添加RFID标签失败</string>
+    <string name="update_rfid_token_succeed">更新RFID标签成功</string>
+    <string name="update_rfid_token_failed">更新RFID标签失败</string>
+    <string name="please_select_key">请选择钥匙</string>
+    <string name="check_delete_key">您确定要删除选中的钥匙吗</string>
+    <string name="key_manage_delete_succeed">钥匙删除成功</string>
+    <string name="key_manage_delete_failed">钥匙删除失败</string>
+    <string name="key_manage_new_key_title">新增钥匙</string>
+    <string name="key_manage_key_detail_title">钥匙详情</string>
+
+    <!-- 挂锁 -->
+    <string name="please_select_lock">请选择挂锁</string>
+    <string name="check_delete_lock">确定要删除选中的挂锁吗?</string>
+    <string name="lock_manage_delete_succeed">挂锁删除成功</string>
+    <string name="lock_manage_delete_failed">挂锁删除失败</string>
+    <string name="lock_manage_lock_detail_title">挂锁详情</string>
+
+    <!-- 卡片 -->
+    <string name="please_select_card">请选择卡片</string>
+    <string name="check_delete_card">确定要删除选中的卡片吗?</string>
+    <string name="card_manage_delete_succeed">卡片删除成功</string>
+    <string name="card_manage_delete_failed">卡片删除失败</string>
+    <string name="card_manage_card_detail_title">卡片详情</string>
+
+    <!-- RFID标签 -->
+    <string name="please_select_rfid_token">请选择RFID标签</string>
+    <string name="check_delete_rfid_token">确定要删除选中的RFID标签吗?</string>
+    <string name="rfid_token_manage_delete_succeed">RFID标签删除成功</string>
+    <string name="rfid_token_manage_delete_failed">RFID标签删除失败</string>
+    <string name="rfid_token_manage_rfid_token_detail_title">RFID标签详情</string>
+    <string name="please_input_key_name">请输入钥匙名称</string>
+    <string name="please_input_key_nfc">请输入钥匙NFC</string>
+    <string name="please_input_key_mac">请输入钥匙MAC</string>
+    <string name="normal">正常</string>
+    <string name="abnormal">异常</string>
+    <string name="remark">备注</string>
+    <string name="please_input_remark">请输入备注</string>
+
+    <string name="confirm">确定</string>
+    <string name="cancel">取消</string>
+
+    <!-- 挂锁 -->
+    <string name="lock_manage_new_lock_title">新增挂锁</string>
+    <string name="lock_code">挂锁编号</string>
+    <string name="please_input_lock_code">请输入挂锁编号</string>
+    <string name="lock_nfc">挂锁 NFC</string>
+    <string name="please_input_lock_nfc">请输入挂锁 NFC</string>
+
+    <!-- 卡片 -->
+    <string name="card_manage_new_card_title">新增卡片</string>
+    <string name="card_nfc">卡片 NFC</string>
+    <string name="please_input_card_nfc">请输入卡片 NFC</string>
+    <string name="card_nickname">用户名称</string>
+    <string name="please_input_card_nickname">请输入用户名称</string>
+
+    <!-- RFID 标签 -->
+    <string name="rfid_token_manage_new_rfid_token_title">新增 RFID 标签</string>
+    <string name="rfid_code">RFID 编号</string>
+    <string name="please_input_rfid_code">请输入 RFID 编号</string>
+    <string name="rfid">RFID 标签</string>
+    <string name="please_input_rfid">请输入 RFID 标签</string>
+    <string name="view">查看</string>
+    <string name="card_code">卡片名称</string>
+    <string name="username">用户名称</string>
+    <string name="card_manage_title">卡片管理</string>
+
 </resources>

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

@@ -229,4 +229,93 @@
     <string name="recognized_point_rfid">已识别的点位RFID</string>
     <string name="operation">操作</string>
     <string name="recognized_card_rfid">已识别的卡片RFID</string>
+    <string name="new_device">New</string>
+    <string name="key_manage_title">钥匙管理</string>
+    <string name="key_name">钥匙名称</string>
+    <string name="key_nfc">钥匙NFC</string>
+    <string name="key_mac">钥匙MAC</string>
+    <string name="add_key_succeed">新增钥匙成功</string>
+    <string name="add_key_failed">新增钥匙失败</string>
+    <string name="update_key_succeed">更新钥匙成功</string>
+    <string name="update_key_failed">更新钥匙失败</string>
+    <!-- lock -->
+    <string name="add_lock_succeed">添加挂锁成功</string>
+    <string name="add_lock_failed">添加挂锁失败</string>
+    <string name="update_lock_succeed">更新挂锁成功</string>
+    <string name="update_lock_failed">更新挂锁失败</string>
+
+    <!-- card -->
+    <string name="add_card_succeed">添加卡片成功</string>
+    <string name="add_card_failed">添加卡片失败</string>
+    <string name="update_card_succeed">更新卡片成功</string>
+    <string name="update_card_failed">更新卡片失败</string>
+
+    <!-- rfid_token -->
+    <string name="add_rfid_token_succeed">添加RFID标签成功</string>
+    <string name="add_rfid_token_failed">添加RFID标签失败</string>
+    <string name="update_rfid_token_succeed">更新RFID标签成功</string>
+    <string name="update_rfid_token_failed">更新RFID标签失败</string>
+    <string name="please_select_key">请选择钥匙</string>
+    <string name="check_delete_key">您确定要删除选中的钥匙吗</string>
+    <string name="key_manage_delete_succeed">钥匙删除成功</string>
+    <string name="key_manage_delete_failed">钥匙删除失败</string>
+    <string name="key_manage_new_key_title">新增钥匙</string>
+    <string name="key_manage_key_detail_title">钥匙详情</string>
+
+    <!-- 挂锁 -->
+    <string name="please_select_lock">请选择挂锁</string>
+    <string name="check_delete_lock">确定要删除选中的挂锁吗?</string>
+    <string name="lock_manage_delete_succeed">挂锁删除成功</string>
+    <string name="lock_manage_delete_failed">挂锁删除失败</string>
+    <string name="lock_manage_lock_detail_title">挂锁详情</string>
+
+    <!-- 卡片 -->
+    <string name="please_select_card">请选择卡片</string>
+    <string name="check_delete_card">确定要删除选中的卡片吗?</string>
+    <string name="card_manage_delete_succeed">卡片删除成功</string>
+    <string name="card_manage_delete_failed">卡片删除失败</string>
+    <string name="card_manage_card_detail_title">卡片详情</string>
+
+    <!-- RFID标签 -->
+    <string name="please_select_rfid_token">请选择RFID标签</string>
+    <string name="check_delete_rfid_token">确定要删除选中的RFID标签吗?</string>
+    <string name="rfid_token_manage_delete_succeed">RFID标签删除成功</string>
+    <string name="rfid_token_manage_delete_failed">RFID标签删除失败</string>
+    <string name="rfid_token_manage_rfid_token_detail_title">RFID标签详情</string>
+    <string name="please_input_key_name">请输入钥匙名称</string>
+    <string name="please_input_key_nfc">请输入钥匙NFC</string>
+    <string name="please_input_key_mac">请输入钥匙MAC</string>
+    <string name="normal">正常</string>
+    <string name="abnormal">异常</string>
+    <string name="remark">备注</string>
+    <string name="please_input_remark">请输入备注</string>
+
+    <string name="confirm">确定</string>
+    <string name="cancel">取消</string>
+
+    <!-- 挂锁 -->
+    <string name="lock_manage_new_lock_title">新增挂锁</string>
+    <string name="lock_code">挂锁编号</string>
+    <string name="please_input_lock_code">请输入挂锁编号</string>
+    <string name="lock_nfc">挂锁 NFC</string>
+    <string name="please_input_lock_nfc">请输入挂锁 NFC</string>
+
+    <!-- 卡片 -->
+    <string name="card_manage_new_card_title">新增卡片</string>
+    <string name="card_nfc">卡片 NFC</string>
+    <string name="please_input_card_nfc">请输入卡片 NFC</string>
+    <string name="card_nickname">用户名称</string>
+    <string name="please_input_card_nickname">请输入用户名称</string>
+
+    <!-- RFID 标签 -->
+    <string name="rfid_token_manage_new_rfid_token_title">新增 RFID 标签</string>
+    <string name="rfid_code">RFID 编号</string>
+    <string name="please_input_rfid_code">请输入 RFID 编号</string>
+    <string name="rfid">RFID 标签</string>
+    <string name="please_input_rfid">请输入 RFID 标签</string>
+    <string name="view">查看</string>
+    <string name="card_code">卡片名称</string>
+    <string name="username">用户名称</string>
+    <string name="card_manage_title">卡片管理</string>
+
 </resources>

+ 186 - 14
data/src/main/java/com/grkj/data/dao/HardwareDao.kt

@@ -4,6 +4,7 @@ import androidx.room.Dao
 import androidx.room.Insert
 import androidx.room.OnConflictStrategy
 import androidx.room.Query
+import androidx.room.Update
 import com.grkj.data.model.dos.IsJobCard
 import com.grkj.data.model.dos.IsKey
 import com.grkj.data.model.dos.IsLock
@@ -29,6 +30,7 @@ interface HardwareDao {
      */
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     fun addCard(isJobCard: IsJobCard)
+
     /**
      * 批量添加卡片
      */
@@ -61,13 +63,7 @@ interface HardwareDao {
      * 更新点位上锁状态
      */
     @Query(
-        "update is_job_ticket_points " +
-                "set point_status = :status," +
-                "lock_id = :lockId," +
-                "locked_by_key_id = :keyId," +
-                "lock_time = :nowTime," +
-                "update_time = :nowTime " +
-                "where ticket_id = :ticketId and point_id = :pointId"
+        "update is_job_ticket_points " + "set point_status = :status," + "lock_id = :lockId," + "locked_by_key_id = :keyId," + "lock_time = :nowTime," + "update_time = :nowTime " + "where ticket_id = :ticketId and point_id = :pointId"
     )
     fun updatePointLockData(
         ticketId: Long,
@@ -84,13 +80,7 @@ interface HardwareDao {
      * 更新点位解锁锁状态
      */
     @Query(
-        "update is_job_ticket_points " +
-                "set point_status = :status," +
-                "lock_id = :lockId," +
-                "unlocked_by_key_id = :keyId," +
-                "unlock_time = :nowTime," +
-                "update_time = :nowTime " +
-                "where ticket_id = :ticketId and point_id = :pointId"
+        "update is_job_ticket_points " + "set point_status = :status," + "lock_id = :lockId," + "unlocked_by_key_id = :keyId," + "unlock_time = :nowTime," + "update_time = :nowTime " + "where ticket_id = :ticketId and point_id = :pointId"
     )
     fun updatePointUnLockData(
         ticketId: Long,
@@ -115,6 +105,30 @@ interface HardwareDao {
     @Query("select * from is_key")
     fun getAllKeyData(): List<IsKey>
 
+    /**
+     * 获取默认数据数量
+     */
+    @Query("select count(1) from is_key where key_code like '钥匙-%'")
+    fun getDefaultKeyNameCount(): Int
+
+    /**
+     * 获取默认数据数量
+     */
+    @Query("select count(1) from is_lock where lock_code like '挂锁-%'")
+    fun getDefaultLockNameCount(): Int
+
+    /**
+     * 获取默认数据数量
+     */
+    @Query("select count(1) from is_job_card where card_code like '工卡-%'")
+    fun getDefaultCardNameCount(): Int
+
+    /**
+     * 获取默认数据数量
+     */
+    @Query("select count(1) from is_rfid_token where rfid_code like 'RFID标签-%'")
+    fun getDefaultRfidTokenNameCount(): Int
+
     /**
      * 获取所有锁仓数据
      */
@@ -180,4 +194,162 @@ interface HardwareDao {
     @Insert(onConflict = OnConflictStrategy.REPLACE)
     fun addRfidToken(isRfidToken: List<IsRfidToken>)
 
+    /**
+     * 添加钥匙信息
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun addKeyInfo(isKey: IsKey)
+
+    /**
+     * 添加挂锁信息
+     */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    fun addLockInfo(isLock: IsLock)
+
+    /**
+     * 清除钥匙表
+     */
+    @Query("delete from is_key where 1=1")
+    fun clearIsKey()
+
+    /**
+     * 清除挂锁表
+     */
+    @Query("delete from is_lock where 1=1")
+    fun clearIsLock()
+
+    /**
+     * 根据钥匙id删除钥匙
+     */
+    @Query("delete from is_key where key_id in (:keyIds)")
+    fun deleteKeyByKeyIds(keyIds: List<Long>)
+
+    /**
+     * 根据挂锁id删除挂锁
+     */
+    @Query("delete from is_lock where lock_id in (:lockIds)")
+    fun deleteLockByLockIds(lockIds: List<Long>)
+
+    /**
+     * 根据卡片id删除卡片
+     */
+    @Query("delete from is_job_card where card_id in (:cardIds)")
+    fun deleteCardByCardIds(cardIds: List<Long>)
+
+    /**
+     * 根据rfid标签id删除rfid标签
+     */
+    @Query("delete from is_rfid_token where rfid_id in (:rfidTokenIds)")
+    fun deleteRfidTokenByRfidTokenIds(rfidTokenIds: List<Long>)
+
+    /**
+     * 更新钥匙信息
+     */
+    @Update
+    fun updateKeyInfo(isKey: IsKey)
+
+    /**
+     * 更新挂锁信息
+     */
+    @Update
+    fun updateLockInfo(isLock: IsLock)
+
+    /**
+     * 更新卡片信息
+     */
+    @Update
+    fun updateCardInfo(isJobCard: IsJobCard)
+
+    /**
+     * 更新rfid标签信息
+     */
+    @Update
+    fun updateRfidToken(rfidToken: IsRfidToken)
+
+    /**
+     * 获取钥匙分页
+     */
+    @Query(
+        """
+        select * from is_key
+        where del_flag = 0
+        and (:keyCode       IS NULL OR trim(:keyCode) = '' OR key_code       LIKE '%' || :keyCode       || '%')
+        and (:keyNfc       IS NULL OR trim(:keyNfc) = '' OR key_nfc       LIKE '%' || :keyNfc       || '%')
+        and (:keyMacAddress       IS NULL OR trim(:keyMacAddress) = '' OR mac_address       LIKE '%' || :keyMacAddress       || '%')
+        and (:exStatus       IS NULL OR trim(:exStatus) = '' OR ex_status       LIKE '%' || :exStatus       || '%')
+        limit :size offset :offset
+    """
+    )
+    fun getKeyInfoPage(
+        keyCode: String?,
+        keyNfc: String?,
+        keyMacAddress: String?,
+        exStatus: Int?,
+        size: Int,
+        offset: Int
+    ): List<IsKey>
+
+    /**
+     * 获取挂锁分页
+     */
+    @Query(
+        """
+        select * from is_lock
+        where del_flag = 0
+        and (:lockCode       IS NULL OR trim(:lockCode) = '' OR lock_code       LIKE '%' || :lockCode       || '%')
+        and (:lockNfc       IS NULL OR trim(:lockNfc) = '' OR lock_nfc       LIKE '%' || :lockNfc       || '%')
+        and (:exStatus       IS NULL OR trim(:exStatus) = '' OR ex_status       LIKE '%' || :exStatus       || '%')
+        limit :size offset :offset
+    """
+    )
+    fun getLockInfoPage(
+        lockCode: String?,
+        lockNfc: String?,
+        exStatus: Int?,
+        size: Int,
+        offset: Int
+    ): List<IsLock>
+
+    /**
+     * 获取卡片分页
+     */
+    @Query(
+        """
+        select ijc.* from is_job_card ijc
+        left join sys_user su on ijc.user_id = su.user_id
+        where ijc.del_flag = 0
+        and (:cardNfc       IS NULL OR trim(:cardNfc) = '' OR ijc.card_nfc       LIKE '%' || :cardNfc       || '%')
+        and (:nickname       IS NULL OR trim(:nickname) = '' OR su.nick_name       LIKE '%' || :nickname       || '%')
+        and (:exStatus       IS NULL OR trim(:exStatus) = '' OR ijc.ex_status       LIKE '%' || :exStatus       || '%')
+        limit :size offset :offset
+    """
+    )
+    fun getCardInfoPage(
+        cardNfc: String?,
+        nickname: String?,
+        exStatus: Int?,
+        size: Int,
+        offset: Int
+    ): List<IsJobCard>
+
+    /**
+     * 获取rfid分页
+     */
+    @Query(
+        """
+        select * from is_rfid_token
+        where del_flag = 0
+        and (:rfidTokenCode       IS NULL OR trim(:rfidTokenCode) = '' OR rfid_code       LIKE '%' || :rfidTokenCode       || '%')
+        and (:rfid       IS NULL OR trim(:rfid) = '' OR rfid       LIKE '%' || :rfid       || '%')
+        and (:status       IS NULL OR trim(:status) = '' OR status       LIKE '%' || :status       || '%')
+        limit :size offset :offset
+    """
+    )
+    fun getRfidTokenInfoPage(
+        rfidTokenCode: String?,
+        rfid: String?,
+        status: Int?,
+        size: Int,
+        offset: Int
+    ): List<IsRfidToken>
 }

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

@@ -164,4 +164,10 @@ interface UserDao {
             WHERE r.role_key = :roleKey;
     """)
     fun getUsersByRoleKey(roleKey: String): List<Long>
+
+    /**
+     * 获取所有未删除用户
+     */
+    @Query("select * from sys_user where del_flag = 0")
+    fun getAllUsers(): List<SysUserDo>
 }

+ 5 - 0
data/src/main/java/com/grkj/data/data/EventConstants.kt

@@ -41,6 +41,11 @@ object EventConstants {
      */
     const val EVENT_CARD_SWIPE = 100_000_007
 
+    /**
+     * 启动串口
+     */
+    const val EVENT_START_MODBUS = 100_000_008
+
     //---------------------------作业票------------------------
     const val EVENT_GET_TICKET_STATUS: Int = 100_001_001
 

+ 6 - 0
data/src/main/java/com/grkj/data/model/dos/BaseBean.kt

@@ -36,6 +36,12 @@ open class BaseBean : Serializable {
     @ColumnInfo("remark")
     var remark: String? = null
 
+    /**
+     * 是否选中
+     */
+    @Ignore
+    var isSelected: Boolean = false
+
     @Ignore
     var paramMap: MutableMap<String?, Any?>? = null
         get() {

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

@@ -1,7 +1,9 @@
 package com.grkj.data.model.dos
 
+import androidx.annotation.NonUiContext
 import androidx.room.ColumnInfo
 import androidx.room.Entity
+import androidx.room.Ignore
 import androidx.room.PrimaryKey
 
 /**

+ 11 - 0
data/src/main/java/com/grkj/data/model/vo/AddCardDataVo.kt

@@ -0,0 +1,11 @@
+package com.grkj.data.model.vo
+
+/**
+ * 添加卡片
+ */
+data class AddCardDataVo(
+    val cardNfc: String,
+    val username: String,
+    val exStatus: Boolean,
+    val exRemark: String?
+)

+ 12 - 0
data/src/main/java/com/grkj/data/model/vo/AddKeyDataVo.kt

@@ -0,0 +1,12 @@
+package com.grkj.data.model.vo
+
+/**
+ * 添加钥匙
+ */
+data class AddKeyDataVo(
+    val keyCode: String,
+    val keyNfc: String,
+    val macAddress: String,
+    val exStatus: Boolean,
+    val exRemark: String?
+)

+ 11 - 0
data/src/main/java/com/grkj/data/model/vo/AddLockDataVo.kt

@@ -0,0 +1,11 @@
+package com.grkj.data.model.vo
+
+/**
+ * 添加挂锁
+ */
+data class AddLockDataVo(
+    val lockCode: String,
+    val lockNfc: String,
+    val exStatus: Boolean,
+    val exRemark: String?
+)

+ 11 - 0
data/src/main/java/com/grkj/data/model/vo/AddRfidTokenDataVo.kt

@@ -0,0 +1,11 @@
+package com.grkj.data.model.vo
+
+/**
+ * 添加rfid标签
+ */
+data class AddRfidTokenDataVo(
+    val rfidCode: String,
+    val rfid: String,
+    val exStatus: Boolean,
+    val exRemark: String?
+)

+ 10 - 0
data/src/main/java/com/grkj/data/model/vo/CardManageFilterVo.kt

@@ -0,0 +1,10 @@
+package com.grkj.data.model.vo
+
+/**
+ * 筛选卡片
+ */
+data class CardManageFilterVo(
+    val cardNfc: String?,
+    val username: String?,
+    val status: Boolean?
+)

+ 11 - 0
data/src/main/java/com/grkj/data/model/vo/KeyManageFilterVo.kt

@@ -0,0 +1,11 @@
+package com.grkj.data.model.vo
+
+/**
+ * 筛选钥匙
+ */
+data class KeyManageFilterVo(
+    val keyCode: String?,
+    val keyNfc: String?,
+    val macAddress: String?,
+    val status: Boolean?
+)

+ 10 - 0
data/src/main/java/com/grkj/data/model/vo/LockManageFilterVo.kt

@@ -0,0 +1,10 @@
+package com.grkj.data.model.vo
+
+/**
+ * 筛选挂锁
+ */
+data class LockManageFilterVo(
+    val lockCode: String?,
+    val lockNfc: String?,
+    val status: Boolean?
+)

+ 10 - 0
data/src/main/java/com/grkj/data/model/vo/RfidTokenManageFilterVo.kt

@@ -0,0 +1,10 @@
+package com.grkj.data.model.vo
+
+/**
+ * 筛选rfid标签
+ */
+data class RfidTokenManageFilterVo(
+    val rfidCode: String?,
+    val rfid: String?,
+    val status: Boolean?
+)

+ 12 - 0
data/src/main/java/com/grkj/data/model/vo/UpdateCardDataVo.kt

@@ -0,0 +1,12 @@
+package com.grkj.data.model.vo
+
+/**
+ * 更新卡片
+ */
+data class UpdateCardDataVo(
+    val cardId: Long,
+    val cardNfc: String,
+    val nickname: String,
+    val exStatus: Boolean,
+    val exRemark: String?
+)

+ 13 - 0
data/src/main/java/com/grkj/data/model/vo/UpdateKeyDataVo.kt

@@ -0,0 +1,13 @@
+package com.grkj.data.model.vo
+
+/**
+ * 更新钥匙
+ */
+data class UpdateKeyDataVo(
+    val keyId: Long,
+    val keyCode: String,
+    val keyNfc: String,
+    val macAddress: String,
+    val exStatus: Boolean,
+    val exRemark: String?
+)

+ 12 - 0
data/src/main/java/com/grkj/data/model/vo/UpdateLockDataVo.kt

@@ -0,0 +1,12 @@
+package com.grkj.data.model.vo
+
+/**
+ * 更新挂锁
+ */
+data class UpdateLockDataVo(
+    val lockId: Long,
+    val lockCode: String,
+    val lockNfc: String,
+    val exStatus: Boolean,
+    val exRemark: String?
+)

+ 12 - 0
data/src/main/java/com/grkj/data/model/vo/UpdateRfidTokenDataVo.kt

@@ -0,0 +1,12 @@
+package com.grkj.data.model.vo
+
+/**
+ * 添加rfid标签
+ */
+data class UpdateRfidTokenDataVo(
+    val rfidId: Long,
+    val rfidCode: String,
+    val rfid: String,
+    val exStatus: Boolean,
+    val exRemark: String?
+)

+ 103 - 0
data/src/main/java/com/grkj/data/repository/IHardwareRepository.kt

@@ -1,5 +1,9 @@
 package com.grkj.data.repository
 
+import com.grkj.data.model.dos.IsJobCard
+import com.grkj.data.model.dos.IsKey
+import com.grkj.data.model.dos.IsLock
+import com.grkj.data.model.dos.IsRfidToken
 import com.grkj.data.model.local.LockData
 import com.grkj.data.model.local.PointData
 import com.grkj.data.model.req.LockPointUpdateReq
@@ -11,6 +15,10 @@ import com.grkj.data.model.res.KeyPageRes
 import com.grkj.data.model.res.LockInfoRes
 import com.grkj.data.model.res.LockPageRes
 import com.grkj.data.model.vo.AddUserDataVo
+import com.grkj.data.model.vo.CardManageFilterVo
+import com.grkj.data.model.vo.KeyManageFilterVo
+import com.grkj.data.model.vo.LockManageFilterVo
+import com.grkj.data.model.vo.RfidTokenManageFilterVo
 
 /**
  * 硬件相关仓储
@@ -93,4 +101,99 @@ interface IHardwareRepository {
      * 添加点位硬件
      */
     fun addRfidTokenHardware(pointRfid: List<String>)
+
+    /**
+     * 添加钥匙信息
+     */
+    fun saveKeyInfo(keyNfc: String, keyMacAddress: String)
+
+    /**
+     * 添加挂锁信息
+     */
+    fun saveLockInfo(lockNfc: String)
+
+    /**
+     * 清楚挂锁和钥匙表
+     */
+    fun clearKeyAndLock()
+
+    /**
+     * 根据钥匙id删除钥匙
+     */
+    fun deleteKeyByKeyIds(keyIds: List<Long>)
+
+    /**
+     * 根据挂锁id删除挂锁
+     */
+    fun deleteLockByLockIds(lockIds: List<Long>)
+
+    /**
+     * 根据卡片id删除卡片
+     */
+    fun deleteCardByCardIds(cardIds: List<Long>)
+
+    /**
+     * 根据rfidTokenId删除rfidToken
+     */
+    fun deleteRfidTokenByRfidTokenIds(rfidTokenIds: List<Long>)
+
+    /**
+     * 添加钥匙信息
+     */
+    fun addKeyInfo(isKey: IsKey)
+
+    /**
+     * 添加挂锁信息
+     */
+    fun addLockInfo(isLock: IsLock)
+
+    /**
+     * 添加卡片信息
+     */
+    fun addCardInfo(isJobCard: IsJobCard)
+
+    /**
+     * 添加rfid标签信息
+     */
+    fun addRfidTokenInfo(isRfidToken: IsRfidToken)
+
+    /**
+     * 更新钥匙信息
+     */
+    fun updateKeyInfo(isKey: IsKey)
+
+    /**
+     * 更新挂锁信息
+     */
+    fun updateLockInfo(isLock: IsLock)
+
+    /**
+     * 更新卡片信息
+     */
+    fun updateCardInfo(isJobCard: IsJobCard)
+
+    /**
+     * 更新rfid标签信息
+     */
+    fun updateRfidTokenInfo(isRfidToken: IsRfidToken)
+
+    /**
+     * 获取钥匙分页
+     */
+    fun getKeyInfoPage(filterVo: KeyManageFilterVo?, size: Int, offset: Int): List<IsKey>
+
+    /**
+     * 获取钥匙分页
+     */
+    fun getLockInfoPage(filterVo: LockManageFilterVo?, size: Int, offset: Int): List<IsLock>
+
+    /**
+     * 获取钥匙分页
+     */
+    fun getCardInfoPage(filterVo: CardManageFilterVo?, size: Int, offset: Int): List<IsJobCard>
+
+    /**
+     * 获取RfidToken分页
+     */
+    fun getRfidTokenInfoPage(filterVo: RfidTokenManageFilterVo?, size: Int, offset: Int): List<IsRfidToken>
 }

+ 5 - 0
data/src/main/java/com/grkj/data/repository/IUserRepository.kt

@@ -78,4 +78,9 @@ interface IUserRepository {
      * 添加管理员用户
      */
     fun addAdminUser(username: String, password: String)
+
+    /**
+     * 获取所有用户数据
+     */
+    fun getAllUsers(): List<SysUserDo>
 }

+ 142 - 6
data/src/main/java/com/grkj/data/repository/impl/HardwareRepository.kt

@@ -4,6 +4,8 @@ import com.grkj.data.dao.HardwareDao
 import com.grkj.data.dao.IsolationPointDao
 import com.grkj.data.enums.CommonDictDataEnum
 import com.grkj.data.model.dos.IsJobCard
+import com.grkj.data.model.dos.IsKey
+import com.grkj.data.model.dos.IsLock
 import com.grkj.data.model.dos.IsRfidToken
 import com.grkj.data.model.local.LockData
 import com.grkj.data.model.local.PointData
@@ -19,10 +21,13 @@ import com.grkj.data.model.res.KeyPageRes
 import com.grkj.data.model.res.LockInfoRes
 import com.grkj.data.model.res.LockPageItem
 import com.grkj.data.model.res.LockPageRes
+import com.grkj.data.model.vo.CardManageFilterVo
+import com.grkj.data.model.vo.KeyManageFilterVo
+import com.grkj.data.model.vo.LockManageFilterVo
+import com.grkj.data.model.vo.RfidTokenManageFilterVo
 import com.grkj.data.repository.BaseRepository
 import com.grkj.shared.utils.Pinyin4jUtil
 import com.sik.sikcore.data.BeanUtils
-import com.sik.sikcore.date.TimeUtils
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -166,10 +171,11 @@ class HardwareRepository @Inject constructor(
     }
 
     override fun addCardHardware(cardCode: List<String>) {
-        val isJobCard = cardCode.map {
+        var defaultCardCodeSize = hardwareDao.getDefaultCardNameCount()
+        val isJobCard = cardCode.mapIndexed { index, cardCode ->
             val isJobCard = IsJobCard()
-            isJobCard.cardCode = it
-            isJobCard.cardNfc = it
+            isJobCard.cardCode = "工卡_${defaultCardCodeSize + index + 1}"
+            isJobCard.cardNfc = cardCode
             isJobCard
         }
         hardwareDao.addCard(isJobCard)
@@ -188,11 +194,141 @@ class HardwareRepository @Inject constructor(
     }
 
     override fun addRfidTokenHardware(pointRfid: List<String>) {
-        val isRfidToken = pointRfid.map {
+        var defaultRfidTokenCodeSize = hardwareDao.getDefaultRfidTokenNameCount()
+        val isRfidToken = pointRfid.mapIndexed { index, rfid ->
             val isRfidToken = IsRfidToken()
-            isRfidToken.rfid = it
+            isRfidToken.rfidCode = "RFID标签_${defaultRfidTokenCodeSize + index + 1}"
+            isRfidToken.rfid = rfid
             isRfidToken
         }
         hardwareDao.addRfidToken(isRfidToken)
     }
+
+    override fun saveKeyInfo(keyNfc: String, keyMacAddress: String) {
+        val isKey = IsKey()
+        var defaultKeyCodeSize = hardwareDao.getDefaultKeyNameCount()
+        hardwareDao.getAllKeyData().size
+        isKey.keyCode = "钥匙_${defaultKeyCodeSize + 1}"
+        isKey.keyNfc = keyNfc
+        isKey.macAddress = keyMacAddress
+        hardwareDao.addKeyInfo(isKey)
+    }
+
+    override fun saveLockInfo(lockNfc: String) {
+        val isLock = IsLock()
+        var defaultLockCodeSize = hardwareDao.getDefaultLockNameCount()
+        isLock.lockCode = "挂锁_${defaultLockCodeSize + 1}"
+        isLock.lockNfc = lockNfc
+        hardwareDao.addLockInfo(isLock)
+    }
+
+    override fun clearKeyAndLock() {
+        hardwareDao.clearIsKey()
+        hardwareDao.clearIsLock()
+    }
+
+    override fun deleteKeyByKeyIds(keyIds: List<Long>) {
+        hardwareDao.deleteKeyByKeyIds(keyIds)
+    }
+
+    override fun deleteLockByLockIds(lockIds: List<Long>) {
+        hardwareDao.deleteKeyByKeyIds(lockIds)
+    }
+
+    override fun deleteCardByCardIds(cardIds: List<Long>) {
+        hardwareDao.deleteCardByCardIds(cardIds)
+    }
+
+    override fun deleteRfidTokenByRfidTokenIds(rfidTokenIds: List<Long>) {
+        hardwareDao.deleteRfidTokenByRfidTokenIds(rfidTokenIds)
+    }
+
+    override fun addKeyInfo(isKey: IsKey) {
+        hardwareDao.addKeyInfo(isKey)
+    }
+
+    override fun addLockInfo(isLock: IsLock) {
+        hardwareDao.addLockInfo(isLock)
+    }
+
+    override fun addCardInfo(isJobCard: IsJobCard) {
+        hardwareDao.addCard(isJobCard)
+    }
+
+    override fun addRfidTokenInfo(isRfidToken: IsRfidToken) {
+        hardwareDao.addRfidToken(listOf(isRfidToken))
+    }
+
+    override fun updateKeyInfo(isKey: IsKey) {
+        hardwareDao.updateKeyInfo(isKey)
+    }
+
+    override fun updateLockInfo(isLock: IsLock) {
+        hardwareDao.updateLockInfo(isLock)
+    }
+
+    override fun updateCardInfo(isJobCard: IsJobCard) {
+        hardwareDao.updateCardInfo(isJobCard)
+    }
+
+    override fun updateRfidTokenInfo(isRfidToken: IsRfidToken) {
+        hardwareDao.updateRfidToken(isRfidToken)
+    }
+
+    override fun getKeyInfoPage(
+        filterVo: KeyManageFilterVo?,
+        size: Int,
+        offset: Int
+    ): List<IsKey> {
+        return hardwareDao.getKeyInfoPage(
+            filterVo?.keyCode,
+            filterVo?.keyNfc,
+            filterVo?.macAddress,
+            filterVo?.status?.let { if (it) 0 else 2 } ?: null,
+            size,
+            offset
+        )
+    }
+
+    override fun getLockInfoPage(
+        filterVo: LockManageFilterVo?,
+        size: Int,
+        offset: Int
+    ): List<IsLock> {
+        return hardwareDao.getLockInfoPage(
+            filterVo?.lockCode,
+            filterVo?.lockNfc,
+            filterVo?.status?.let { if (it) 0 else 2 } ?: null,
+            size,
+            offset
+        )
+    }
+
+    override fun getCardInfoPage(
+        filterVo: CardManageFilterVo?,
+        size: Int,
+        offset: Int
+    ): List<IsJobCard> {
+        return hardwareDao.getCardInfoPage(
+            filterVo?.cardNfc,
+            filterVo?.username,
+            filterVo?.status?.let { if (it) 0 else 2 } ?: null,
+            size,
+            offset
+        )
+    }
+
+    override fun getRfidTokenInfoPage(
+        filterVo: RfidTokenManageFilterVo?,
+        size: Int,
+        offset: Int
+    ): List<IsRfidToken> {
+        return hardwareDao.getRfidTokenInfoPage(
+            filterVo?.rfidCode,
+            filterVo?.rfid,
+            filterVo?.status?.let { if (it) 0 else 2 } ?: null,
+            size,
+            offset
+        )
+    }
 }

+ 4 - 0
data/src/main/java/com/grkj/data/repository/impl/UserRepository.kt

@@ -221,4 +221,8 @@ class UserRepository @Inject constructor(
         userRole.roleId = roleId
         roleDao.insertUserRole(listOf(userRole))
     }
+
+    override fun getAllUsers(): List<SysUserDo> {
+        return userDao.getAllUsers()
+    }
 }

二进制
shared/libs/adh_series_sdk.jar


+ 1 - 0
ui-base/build.gradle.kts

@@ -65,6 +65,7 @@ dependencies {
     api("io.github.scwang90:refresh-layout-kernel:3.0.0-alpha")
     api("io.github.scwang90:refresh-header-classics:3.0.0-alpha")
     api("io.github.scwang90:refresh-footer-classics:3.0.0-alpha")
+    api("com.licheedev:android-serialport:2.1.5")
     implementation(project(":data"))
     implementation(project(":shared"))
     testImplementation(libs.junit)

+ 25 - 0
ui-base/src/main/java/com/grkj/ui_base/business/BleBusinessManager.kt

@@ -42,6 +42,7 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.async
 import kotlinx.coroutines.withContext
 import org.slf4j.LoggerFactory
+import kotlin.math.log
 
 /**
  * 蓝牙业务
@@ -878,4 +879,28 @@ object BleBusinessManager {
         logger.info("json : $jsonStr")
         return jsonStr
     }
+
+    /**
+     * 生成下空发工作票Json
+     *
+     * @param vo 工作票详情
+     */
+    fun generateEmptyTicketSendJson(): String {
+        // 构造一个所有字段都为空/默认值的 WorkTicketSendBO
+        val bo = WorkTicketSend(
+            cardNo = "",          // 空卡号
+            effectiveTime = 0,    // 默认有效时长
+            password = ""         // 空密码
+        ).apply {
+            // data 列表留空
+            data = mutableListOf()
+            // lockList 留空(如果字段非空,再设置为 emptyList())
+            lockList = mutableListOf()
+        }
+
+        // 转成 JSON 并返回
+        val jsonStr = Gson().toJson(bo)
+        logger.info("generateEmptyTicketJson: $jsonStr")
+        return jsonStr
+    }
 }

+ 176 - 2
ui-base/src/main/java/com/grkj/ui_base/utils/ble/BleConnectionManager.kt

@@ -5,6 +5,8 @@ import com.clj.fastble.BleManager
 import com.clj.fastble.data.BleDevice
 import com.clj.fastble.exception.BleException
 import com.grkj.ui_base.R
+import com.grkj.ui_base.business.BleBusinessManager
+import com.grkj.ui_base.business.DataBusiness
 import com.grkj.ui_base.config.ISCSConfig
 import com.grkj.ui_base.utils.modbus.ModBusController
 import com.grkj.ui_base.utils.CommonUtils
@@ -581,7 +583,7 @@ object BleConnectionManager {
      *   1. 先尝试不充电连接,若成功就返回 true;
      *   2. 否则开启“充电”,等 500ms,再尝试一次连接,连接成功后断电并返回 true;否则返回 false。
      */
-    suspend fun tryConnectWithOptionalCharge(mac: String): Boolean =
+    suspend fun tryConnectWithOptionalCharge(mac: String, withOpenCharge: Boolean = true): Boolean =
         withContext(Dispatchers.IO) {
             // -------- 第一次尝试 --------
             logger.info("蓝牙连接-第一次尝试")
@@ -607,6 +609,9 @@ object BleConnectionManager {
             if (firstTry) {
                 return@withContext true
             }
+            if (!withOpenCharge) {
+                return@withContext false
+            }
             // -------- 第二次尝试:先开电,再连 --------
             // 开电,并等待回调
             suspendCoroutine<Unit> { unitCont ->
@@ -619,7 +624,7 @@ object BleConnectionManager {
                     }
                 }
             }
-            logger.info("蓝牙连接-开启充电并等待500ms")
+            logger.debug("蓝牙连接-开启充电并等待500ms")
             // 等 500ms 保证硬件电源稳定
             delay(500)
 
@@ -640,6 +645,175 @@ object BleConnectionManager {
             return@withContext secondTry
         }
 
+    /**
+     * 扫描在线的蓝牙钥匙并发送指令关机
+     */
+    suspend fun scanOnlineKeyLockMacAndSwitchModeToClose(): Boolean {
+        return suspendCancellableCoroutine { parentCont ->
+            BleUtil.instance?.scan(object : CustomBleScanCallback() {
+                override fun onPrompt(promptStr: String?) {
+                    // 蓝牙未启动重试
+                    logger.debug("设备录入-参数:${promptStr}")
+                    BleManager.getInstance().enableBluetooth()
+                    ThreadUtils.runOnMainDelayed(300) {
+                        scanOnlineKeyLockMacAndSwitchModeToClose()
+                    }
+                }
+
+                override fun onScanStarted(success: Boolean) {
+                    logger.debug("设备录入-onScanStarted:${success}")
+                    if (!success) {
+                        ThreadUtils.runOnMainDelayed(300) {
+                            scanOnlineKeyLockMacAndSwitchModeToClose()
+                        }
+                    }
+                }
+
+                override fun onScanning(bleDevice: BleDevice?) {
+                    val mac = bleDevice?.mac ?: return
+                    logger.debug("设备录入-onScanning:$mac")
+                }
+
+                override fun onScanFinished(scanResultList: MutableList<BleDevice>?) {
+                    val devicesSnapshot = scanResultList?.toList().orEmpty()
+                    ThreadUtils.runOnIO {
+                        devicesSnapshot.forEach {
+                            val connected =
+                                tryConnectWithOptionalCharge(
+                                    it.mac,
+                                    false
+                                )
+                            if (connected) {
+                                val sendSuccess = sendEmptyTicketJson(it)
+                                logger.debug("设备录入-发送切换工作模式:${it.mac},${sendSuccess}")
+                            }
+                        }
+                        parentCont.resume(true)
+                    }
+                }
+            })
+        }
+    }
+
+    /**
+     * 发送空作业票
+     */
+    private suspend fun sendEmptyTicketJson(bleDevice: BleDevice): Boolean {
+        return suspendCancellableCoroutine<Boolean> { cont ->
+            BleCmdManager.sendWorkTicket(
+                BleBusinessManager.generateEmptyTicketSendJson(),
+                bleDevice = bleDevice,
+                callback = object : CustomBleWriteCallback() {
+                    override fun onWriteSuccess(
+                        current: Int,
+                        total: Int,
+                        justWrite: ByteArray?
+                    ) {
+                        ThreadUtils.runOnIO {
+                            delay(3000)
+                            cont.resume(switchWorkMode(bleDevice))
+                        }
+                    }
+
+                    override fun onWriteFailure(exception: BleException?) {
+                        ThreadUtils.runOnMainDelayed(300) {
+                            cont.resume(sendEmptyTicketJson(bleDevice))
+                        }
+                    }
+                })
+        }
+    }
+
+    /**
+     * 切换工作模式
+     */
+    private suspend fun switchWorkMode(bleDevice: BleDevice): Boolean {
+        return suspendCancellableCoroutine<Boolean> { cont ->
+            BleCmdManager.switchMode(
+                BleConst.STATUS_WORK,
+                bleDevice,
+                object : CustomBleWriteCallback() {
+                    override fun onWriteSuccess(
+                        current: Int,
+                        total: Int,
+                        justWrite: ByteArray?
+                    ) {
+                        BleManager.getInstance().disconnect(bleDevice)
+                        ThreadUtils.runOnIO {
+                            delay(800)
+                            cont.resume(true)
+                        }
+                        logger.debug("设备录入-切换模式发送成功 : ${bleDevice.mac}")
+                    }
+
+                    override fun onWriteFailure(exception: BleException?) {
+                        logger.debug("设备录入-切换模式发送失败 : ${bleDevice.mac}")
+                        ThreadUtils.runOnMainDelayed(300) {
+                            cont.resume(sendEmptyTicketJson(bleDevice))
+                        }
+                    }
+                })
+        }
+    }
+
+    /**
+     * 切换待机模式
+     */
+    fun switchReadyMode(bleDevice: BleDevice) {
+        BleCmdManager.switchMode(
+            BleConst.STATUS_READY,
+            bleDevice,
+            object : CustomBleWriteCallback() {
+                override fun onWriteSuccess(
+                    current: Int,
+                    total: Int,
+                    justWrite: ByteArray?
+                ) {
+                    BleManager.getInstance().disconnect(bleDevice)
+                    logger.debug("设备录入-切换模式发送成功 : ${bleDevice.mac}")
+                }
+
+                override fun onWriteFailure(exception: BleException?) {
+                    logger.debug("设备录入-切换模式发送失败 : ${bleDevice.mac}")
+                }
+            })
+    }
+
+    /**
+     * 扫描在线的蓝牙
+     */
+    fun scanOnlineKeyLockMac(callback: (List<BleDevice>?) -> Unit) {
+        BleUtil.instance?.scan(object : CustomBleScanCallback() {
+            override fun onPrompt(promptStr: String?) {
+                // 蓝牙未启动重试
+                logger.debug("设备录入-参数:${promptStr}")
+                BleManager.getInstance().enableBluetooth()
+                ThreadUtils.runOnMainDelayed(300) {
+                    scanOnlineKeyLockMac(callback)
+                }
+            }
+
+            override fun onScanStarted(success: Boolean) {
+                logger.debug("设备录入-onScanStarted:${success}")
+                if (!success) {
+                    ThreadUtils.runOnMainDelayed(300) {
+                        scanOnlineKeyLockMac(callback)
+                    }
+                }
+            }
+
+            override fun onScanning(bleDevice: BleDevice?) {
+                val mac = bleDevice?.mac ?: return
+                logger.debug("设备录入-onScanning:$mac")
+            }
+
+            override fun onScanFinished(scanResultList: MutableList<BleDevice>?) {
+                logger.debug("设备录入-扫描完成:$scanResultList")
+                callback(scanResultList?.toList())
+            }
+        })
+    }
+
 
     // 蓝牙连接准备监听
     data class ConnectListener(

+ 1 - 6
ui-base/src/main/java/com/grkj/ui_base/utils/ble/BleUtil.kt

@@ -21,12 +21,7 @@ class BleUtil private constructor() {
     private val logger: Logger = LoggerFactory.getLogger(BleUtil::class.java)
 
     companion object {
-        var instance: BleUtil? = null
-            get() {
-                if (field == null) field = BleUtil()
-                return field
-            }
-            private set
+        val instance: BleUtil by lazy { BleUtil() }
 
         const val OPERATE_TIMEOUT = 10 * 1000
     }

+ 24 - 0
ui-base/src/main/java/com/grkj/ui_base/utils/event/StartModbusEvent.kt

@@ -0,0 +1,24 @@
+package com.grkj.ui_base.utils.event
+
+import com.grkj.shared.model.EventBean
+import com.grkj.data.data.EventConstants
+
+/**
+ * 启动串口
+ */
+class StartModbusEvent() {
+
+    companion object {
+        /**
+         * 发送启动串口事件
+         */
+        @JvmStatic
+        fun sendStartModbusEvent() {
+            val startModbusEvent = StartModbusEvent()
+            val startModbusEventBean = EventBean<StartModbusEvent>(
+                EventConstants.EVENT_LOGOUT, startModbusEvent
+            )
+            EventHelper.sendEvent(startModbusEventBean)
+        }
+    }
+}

+ 14 - 0
ui-base/src/main/java/com/grkj/ui_base/utils/modbus/ModBusController.kt

@@ -720,6 +720,20 @@ object ModBusController {
         }
     }
 
+    /**
+     * 关闭锁并充电
+     */
+    fun controlKeyLockAndCharge(
+        isOpen: Boolean,
+        idx: Int,
+        slaveAddress: Byte?,
+        done: ((ByteArray) -> Unit)? = null
+    ) {
+        controlKeyBuckle(!isOpen, idx, slaveAddress) {
+            controlKeyCharge(isOpen, idx, slaveAddress, done)
+        }
+    }
+
     /**
      * 根据RFID找钥匙
      */

+ 8 - 4
ui-base/src/main/java/com/grkj/ui_base/utils/modbus/PortManager.kt

@@ -1,11 +1,14 @@
 package com.grkj.ui_base.utils.modbus
 
+import android.serialport.SerialPort
 import androidx.annotation.WorkerThread
-import com.epton.sdk.SerialPort
 import com.grkj.data.data.MMKVConstants
+import com.grkj.ui_base.utils.SPUtils
 import com.kongzue.dialogx.dialogs.PopTip
+import com.sik.sikcore.SIKCore
 import com.sik.sikcore.extension.getMMKVData
 import com.sik.sikcore.extension.saveMMKVData
+import com.sik.sikcore.extension.toJson
 import com.sik.sikcore.thread.ThreadUtils
 import org.slf4j.Logger
 import org.slf4j.LoggerFactory
@@ -103,7 +106,7 @@ class PortManager private constructor(
             try {
                 val file = File(if (usb) "/dev/ttyUSB${port}" else "/dev/ttyS${port}")
                 logger.info("连接 port file")
-                SerialPort(file, bps, 0).run {
+                SerialPort(file, bps).run {
                     blocked = false
                     logger.info("建立 SerialPort")
                     return PortManager(inputStream, outputStream)
@@ -138,7 +141,7 @@ class PortManager private constructor(
             try {
                 val file = File(port)
                 logger.info("连接 port file")
-                SerialPort(file, bps, 0).run {
+                SerialPort(file, bps).run {
                     blocked = false
                     logger.info("建立 SerialPort")
                     return PortManager(inputStream, outputStream)
@@ -164,7 +167,7 @@ class PortManager private constructor(
             devs.forEach { path ->
                 try {
                     dockConfig.clear()
-                    SerialPort(File(path), baudRate, 0).let { sp ->
+                    SerialPort(File(path), baudRate).let { sp ->
                         val input = sp.inputStream
                         val output = sp.outputStream
                         val slaveInfo = path
@@ -173,6 +176,7 @@ class PortManager private constructor(
                         }
                         checkSlave(0xA1, input, output, dockConfig)
                         if (slaveInfo.isNotEmpty() && dockConfig.isNotEmpty()) {
+                            SPUtils.saveDockConfig(SIKCore.getApplication(), dockConfig.toJson())
                             logger.info("扫描到设备:${slaveInfo},从机:${dockConfig}")
                             return slaveInfo
                         }