Explorar el Código

refactor(更新):
- 优化用户管理功能,修复密码更新逻辑,删除用户时同步解绑工卡
- 调整数据导出模块,将"workstation"表重命名为"area"
- 优化硬件模式枚举,简化硬件助手获取逻辑
- 统一作业管理、SOP管理、异常处理等模块的弹窗交互,移除不必要的取消按钮
- 修复蓝牙连接分发器在协程取消后依然尝试`resume`的问题
- 调整地图厂区图默认加载逻辑,确保首次加载时能正确显示
- 优化新增用户时用户名校验逻辑,增加正则校验
- 调整备份与还原页面"备份频率"文本为"备份时间"
- 优化开关状态颜色及国际化文本,将"打开/关闭"修改为"解锁/锁定"
- 移除蓝牙连接时"未找到钥匙信息"的多余提示
- 移除item_switch.xml中多余的布局约束

周文健 hace 2 meses
padre
commit
35590f087e
Se han modificado 29 ficheros con 96 adiciones y 62 borrados
  1. 12 12
      app/src/main/assets/i18n/en-US.json
  2. BIN
      app/src/main/assets/map_factory.png
  3. BIN
      app/src/main/assets/map_factory.png.png
  4. 7 0
      app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/AddUserDialog.kt
  5. 2 2
      app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/UpdateUserDialog.kt
  6. 5 7
      app/src/main/java/com/grkj/iscs/features/main/fragment/data_manage/SwitchLayoutFragment.kt
  7. 2 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/exception_manage/ExceptionDetailFragment.kt
  8. 3 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/exception_manage/ExceptionJobFragment.kt
  9. 2 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/CreateJobFragment.kt
  10. 1 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/CreateSopFragment.kt
  11. 4 2
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/CreateSopJobFragment.kt
  12. 2 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditJobFragment.kt
  13. 1 0
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditSopFragment.kt
  14. 4 1
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditSopJobFragment.kt
  15. 1 0
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/data_manage/UserManageViewModel.kt
  16. 1 1
      app/src/main/res/layout-land/fragment_backup_and_restore.xml
  17. 1 1
      app/src/main/res/layout/fragment_backup_and_restore.xml
  18. 0 1
      app/src/main/res/layout/item_switch.xml
  19. 10 2
      data/src/main/java/com/grkj/data/dao/HardwareDao.kt
  20. 1 1
      data/src/main/java/com/grkj/data/enums/DataExportTableEnum.kt
  21. 4 6
      data/src/main/java/com/grkj/data/enums/HardwareMode.kt
  22. 16 22
      data/src/main/java/com/grkj/data/hardware/modbus/ModBusController.kt
  23. 1 1
      data/src/main/java/com/grkj/data/hardware/modbus/PortManager.kt
  24. 5 0
      data/src/main/java/com/grkj/data/logic/IHardwareLogic.kt
  25. 4 0
      data/src/main/java/com/grkj/data/logic/impl/network/NetworkHardwareLogic.kt
  26. 4 0
      data/src/main/java/com/grkj/data/logic/impl/standard/HardwareLogic.kt
  27. 0 1
      ui-base/src/main/java/com/grkj/ui_base/business/BleBusinessManager.kt
  28. 2 1
      ui-base/src/main/java/com/grkj/ui_base/widget/CustomSwitchStationLayer.kt
  29. 1 1
      ui-base/src/main/res/values/theme.xml

+ 12 - 12
app/src/main/assets/i18n/en-US.json

@@ -117,12 +117,12 @@
   "add_workstation_failed": {
     "key": "add_workstation_failed",
     "type": "text",
-    "value": "Failed to Add Workstation"
+    "value": "Failed to Add Area"
   },
   "add_workstation_succeed": {
     "key": "add_workstation_succeed",
     "type": "text",
-    "value": "Workstation Added Successfully"
+    "value": "Area Added Successfully"
   },
   "admin_role_can_not_edit": {
     "key": "admin_role_can_not_edit",
@@ -1172,17 +1172,17 @@
   "job_card_login_failed": {
     "key": "job_card_login_failed",
     "type": "text",
-    "value": "Invalid work card"
+    "value": "Invalid job card"
   },
   "job_card_login_success": {
     "key": "job_card_login_success",
     "type": "text",
-    "value": "Work card recognized successfully"
+    "value": "Job card recognized successfully"
   },
   "job_card_not_set_tip": {
     "key": "job_card_not_set_tip",
     "type": "text",
-    "value": "You have not set up a work card"
+    "value": "You have not set up a job card"
   },
   "job_card_scan_tip": {
     "key": "job_card_scan_tip",
@@ -1192,7 +1192,7 @@
   "job_card_set_tip": {
     "key": "job_card_set_tip",
     "type": "text",
-    "value": "Work card data has been set"
+    "value": "Job card data has been set"
   },
   "job_create_and_execute_failed": {
     "key": "job_create_and_execute_failed",
@@ -1727,7 +1727,7 @@
   "please_input_card_code": {
     "key": "please_input_card_code",
     "type": "text",
-    "value": "Please enter work card number"
+    "value": "Please enter job card number"
   },
   "please_input_card_nfc": {
     "key": "please_input_card_nfc",
@@ -2652,7 +2652,7 @@
   "sop_manage_workstation": {
     "key": "sop_manage_workstation",
     "type": "text",
-    "value": "Workstation"
+    "value": "Area"
   },
   "sop_save_failed": {
     "key": "sop_save_failed",
@@ -3802,12 +3802,12 @@
   "switch_open": {
     "key": "switch_open",
     "type": "text",
-    "value": "Open"
+    "value": "Unlocked"
   },
   "switch_close": {
     "key": "switch_close",
     "type": "text",
-    "value": "Close"
+    "value": "Locked"
   },
   "switch_alarm": {
     "key": "switch_alarm",
@@ -3882,7 +3882,7 @@
   "workstation_info": {
     "key": "workstation_info",
     "type": "text",
-    "value": "Workstation info"
+    "value": "Area info"
   },
   "year": {
     "key": "year",
@@ -4091,7 +4091,7 @@
   "workstation": {
     "key": "workstation",
     "type": "text",
-    "value": "Workstation"
+    "value": "Area"
   },
   "point": {
     "key": "point",

BIN
app/src/main/assets/map_factory.png


BIN
app/src/main/assets/map_factory.png.png


+ 7 - 0
app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/AddUserDialog.kt

@@ -7,6 +7,7 @@ import com.grkj.iscs.R
 import com.grkj.iscs.databinding.DialogAddUserBinding
 import com.grkj.iscs.features.main.dialog.TextDropDownDialog
 import com.grkj.data.config.ISCSConfig
+import com.grkj.data.data.CommonConstants
 import com.grkj.shared.utils.BCryptUtils
 import com.grkj.ui_base.utils.CommonUtils
 import com.grkj.ui_base.utils.extension.tip
@@ -14,6 +15,7 @@ import com.kongzue.dialogx.dialogs.CustomDialog
 import com.kongzue.dialogx.dialogs.PopTip
 import com.kongzue.dialogx.interfaces.OnBindView
 import com.sik.sikcore.extension.setDebouncedClickListener
+import com.sik.sikcore.string.RegexUtils
 
 /**
  * 新增用户对话框,基于 DialogX,支持多选角色和岗位
@@ -91,6 +93,11 @@ class AddUserDialog(
             PopTip.build().tip(CommonUtils.getStr("please_input_username"))
             return false
         }
+        val username = binding.usernameEt.text.toString()
+        if (!RegexUtils.isMatch(username, CommonConstants.REGEX_USERNAME)){
+            PopTip.build().tip(CommonUtils.getStr("username_regex_tip"))
+            return false
+        }
         if (binding.nicknameEt.text.isNullOrBlank()) {
             PopTip.build().tip(CommonUtils.getStr("please_input_nickname"))
             return false

+ 2 - 2
app/src/main/java/com/grkj/iscs/features/main/dialog/data_manage/UpdateUserDialog.kt

@@ -107,15 +107,15 @@ class UpdateUserDialog(
                 .tip(CommonUtils.getStr("please_select_role"))
             if (ISCSConfig.isWorkstationOn && selectedWorkstations.isEmpty()) return@setDebouncedClickListener PopTip.build()
                 .tip(CommonUtils.getStr("please_select_area"))
-            if (!password.isNotEmpty() && password.length !in 6..20) {
+            if (password.isNotEmpty() && password.length !in 6..20) {
                 PopTip.build().tip(CommonUtils.getStr("password_hint"))
                 return@setDebouncedClickListener
             }
             val isActive = binding.statusRg.checkedRadioButtonId == binding.activateRb.id
             val updateVo = UpdateUserDataVo(
                 userVo.userId,
-                if (password.isNotEmpty()) BCryptUtils.encryptPassword(password) else userVo.password,
                 username,
+                if (password.isNotEmpty()) BCryptUtils.encryptPassword(password) else userVo.password,
                 name,
                 card,
                 selectedRoles.mapNotNull { it.getId() },

+ 5 - 7
app/src/main/java/com/grkj/iscs/features/main/fragment/data_manage/SwitchLayoutFragment.kt

@@ -66,15 +66,13 @@ class SwitchLayoutFragment : BaseFragment<FragmentSwitchLayoutBinding>() {
             .getData<Long>(DataTransferConstants.KEY_LOCKED_POINT_WORKSTATION_ID) ?: 0L
         logger.info("需要定位的点位id:${positionPointId}")
         val localMapUrl = MMKVConstants.KEY_MAP_LOCAL.getMMKVData("")
-        if (localMapUrl.isEmpty()) {
-            MMKVConstants.KEY_MAP_LOCAL.saveMMKVData(Constants.DEFAULT_MAP_LOCAL_URL)
-            val defaultFile = File(Constants.DEFAULT_MAP_LOCAL_URL).apply {
-                if (!parentFile.exists()) {
-                    parentFile.mkdirs()
-                }
+        MMKVConstants.KEY_MAP_LOCAL.saveMMKVData(Constants.DEFAULT_MAP_LOCAL_URL)
+        val defaultFile = File(Constants.DEFAULT_MAP_LOCAL_URL).apply {
+            if (!parentFile.exists()) {
+                parentFile.mkdirs()
             }
-            AssetsCopyUtil.copyToFile(assetName = "map_factory.png", outFile = defaultFile)
         }
+        AssetsCopyUtil.copyToFile(assetName = "map_factory.png", outFile = defaultFile)
         binding.back.setDebouncedClickListener {
             navController.popBackStack()
         }

+ 2 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/exception_manage/ExceptionDetailFragment.kt

@@ -150,6 +150,7 @@ class ExceptionDetailFragment : BaseFragment<FragmentExceptionDetailBinding>() {
             title = CommonUtils.getStr("warn"),
             msg = CommonUtils.getStr("handle_exception_will_release_all_colock"),
             dialogType = TipDialog.DialogType.ERROR,
+            showCancel = false,
             onConfirmClick = onConfirm
         )
     }
@@ -162,6 +163,7 @@ class ExceptionDetailFragment : BaseFragment<FragmentExceptionDetailBinding>() {
             title = CommonUtils.getStr("warn"),
             msg = CommonUtils.getStr("current_job_has_cross_job"),
             dialogType = TipDialog.DialogType.ERROR,
+            showCancel = false,
             onConfirmClick = onConfirm
         )
     }

+ 3 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/exception_manage/ExceptionJobFragment.kt

@@ -93,12 +93,14 @@ class ExceptionJobFragment : BaseFragment<FragmentExceptionJobBinding>() {
                             title = CommonUtils.getStr("warn"),
                             msg = CommonUtils.getStr("handle_exception_will_release_all_colock"),
                             dialogType = TipDialog.DialogType.ERROR,
+                            showCancel = false,
                             onConfirmClick = {
                                 if (it.second) {
                                     TipDialog.show(
                                         title = CommonUtils.getStr("warn"),
                                         msg = CommonUtils.getStr("current_job_has_cross_job"),
                                         dialogType = TipDialog.DialogType.ERROR,
+                                        showCancel = false,
                                         onConfirmClick = {
                                             handleExceptionCheck()
                                         })
@@ -112,6 +114,7 @@ class ExceptionJobFragment : BaseFragment<FragmentExceptionJobBinding>() {
                                 title = CommonUtils.getStr("warn"),
                                 msg = CommonUtils.getStr("current_job_has_cross_job"),
                                 dialogType = TipDialog.DialogType.ERROR,
+                                showCancel = false,
                                 onConfirmClick = {
                                     handleExceptionCheck()
                                 })

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

@@ -408,6 +408,7 @@ class CreateJobFragment : BaseFormFragment<FragmentCreateJobBinding>() {
                                 title = CommonUtils.getStr("action_succeed"),
                                 msg = CommonUtils.getStr("job_create_and_execute_succeed"),
                                 dialogType = TipDialog.DialogType.SUCCESS,
+                                showCancel = false,
                                 onConfirmClick = {
                                     navController.popBackStack()
                                 },)
@@ -425,6 +426,7 @@ class CreateJobFragment : BaseFormFragment<FragmentCreateJobBinding>() {
                         title = CommonUtils.getStr("action_succeed"),
                         msg = CommonUtils.getStr("job_create_succeed"),
                         dialogType = TipDialog.DialogType.SUCCESS,
+                        showCancel = false,
                         onConfirmClick = {
                             navController.popBackStack()
                         })

+ 1 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/CreateSopFragment.kt

@@ -350,6 +350,7 @@ class CreateSopFragment : BaseFormFragment<FragmentCreateSopBinding>() {
                     title = CommonUtils.getStr("action_succeed").toString(),
                     msg = CommonUtils.getStr("sop_create_succeed").toString(),
                     dialogType = TipDialog.DialogType.SUCCESS,
+                    showCancel = false,
                     onConfirmClick = {
                         clearData()
                         setDefaultData()

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

@@ -269,6 +269,7 @@ class CreateSopJobFragment : BaseFormFragment<FragmentCreateSopJobBinding>() {
                                 msg = CommonUtils.getStr("sop_job_save_and_execute_succeed")
                                     .toString(),
                                 dialogType = TipDialog.DialogType.SUCCESS,
+                                showCancel = false,
                                 onConfirmClick = {
                                     navController.popBackStack()
                                 }
@@ -280,6 +281,7 @@ class CreateSopJobFragment : BaseFormFragment<FragmentCreateSopJobBinding>() {
                                 msg = CommonUtils.getStr("sop_job_save_and_execute_failed")
                                     .toString(),
                                 dialogType = TipDialog.DialogType.SUCCESS,
+                                showCancel = false,
                             )
                         }
                     }
@@ -289,6 +291,7 @@ class CreateSopJobFragment : BaseFormFragment<FragmentCreateSopJobBinding>() {
                             .toString(),
                         msg = CommonUtils.getStr("sop_job_save_succeed").toString(),
                         dialogType = TipDialog.DialogType.SUCCESS,
+                        showCancel = false,
                         onConfirmClick = {
                             navController.popBackStack()
                         }
@@ -299,8 +302,7 @@ class CreateSopJobFragment : BaseFormFragment<FragmentCreateSopJobBinding>() {
                     title = CommonUtils.getStr("action_failed").toString(),
                     msg = CommonUtils.getStr("sop_job_save_failed").toString(),
                     dialogType = TipDialog.DialogType.ERROR,
-                    showCancel = false,
-                    countDownTime = 10
+                    showCancel = false
                 )
             }
         }

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

@@ -280,6 +280,7 @@ class EditJobFragment : BaseFormFragment<FragmentEditJobBinding>() {
                                 msg = CommonUtils.getStr("job_create_and_execute_succeed")
                                     .toString(),
                                 dialogType = TipDialog.DialogType.SUCCESS,
+                                showCancel = false,
                                 onConfirmClick = {
                                     navController.popBackStack()
                                 },)
@@ -300,6 +301,7 @@ class EditJobFragment : BaseFormFragment<FragmentEditJobBinding>() {
                             .toString(),
                         msg = CommonUtils.getStr("job_create_succeed").toString(),
                         dialogType = TipDialog.DialogType.SUCCESS,
+                        showCancel = false,
                         onConfirmClick = {
                             navController.popBackStack()
                         },)

+ 1 - 0
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditSopFragment.kt

@@ -270,6 +270,7 @@ class EditSopFragment : BaseFormFragment<FragmentEditSopBinding>() {
                     title = CommonUtils.getStr("action_succeed").toString(),
                     msg = CommonUtils.getStr("sop_save_succeed").toString(),
                     dialogType = TipDialog.DialogType.SUCCESS,
+                    showCancel = false,
                     onConfirmClick = {
                         navController.popBackStack()
                     }

+ 4 - 1
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/EditSopJobFragment.kt

@@ -268,6 +268,7 @@ class EditSopJobFragment : BaseFormFragment<FragmentEditSopJobBinding>() {
                                 msg = CommonUtils.getStr("sop_job_save_and_execute_succeed")
                                     .toString(),
                                 dialogType = TipDialog.DialogType.SUCCESS,
+                                showCancel = false,
                                 onConfirmClick = {
                                     navController.popBackStack()
                                 }
@@ -278,7 +279,8 @@ class EditSopJobFragment : BaseFormFragment<FragmentEditSopJobBinding>() {
                                     .toString(),
                                 msg = CommonUtils.getStr("sop_job_save_and_execute_failed")
                                     .toString(),
-                                dialogType = TipDialog.DialogType.SUCCESS
+                                dialogType = TipDialog.DialogType.SUCCESS,
+                                showCancel = false,
                             )
                         }
                     }
@@ -288,6 +290,7 @@ class EditSopJobFragment : BaseFormFragment<FragmentEditSopJobBinding>() {
                             .toString(),
                         msg = CommonUtils.getStr("sop_job_save_succeed").toString(),
                         dialogType = TipDialog.DialogType.SUCCESS,
+                        showCancel = false,
                         onConfirmClick = {
                             navController.popBackStack()
                         }

+ 1 - 0
app/src/main/java/com/grkj/iscs/features/main/viewmodel/data_manage/UserManageViewModel.kt

@@ -46,6 +46,7 @@ class UserManageViewModel @Inject constructor(
             userLogic.deleteUserById(userIds)
             roleRepository.deleteUserRoleByUserId(userIds)
             workstationRepository.deleteUserWorkstationByUserIds(userIds)
+            hardwareRepository.removeCardBindByUserIds(userIds)
             emit(true)
         }
     }

+ 1 - 1
app/src/main/res/layout-land/fragment_backup_and_restore.xml

@@ -268,7 +268,7 @@
                         android:layout_marginTop="@dimen/iscs_space_4"
                         android:textColor="?attr/colorTextPrimary"
                         android:textSize="@dimen/iscs_text_md"
-                        app:i18nKey='@{"backup_frequency"}'
+                        app:i18nKey='@{"backup_time"}'
                         app:layout_constraintEnd_toEndOf="@+id/end_line"
                         app:layout_constraintTop_toBottomOf="@+id/backup_frequency_tv" />
 

+ 1 - 1
app/src/main/res/layout/fragment_backup_and_restore.xml

@@ -266,7 +266,7 @@
                         android:layout_marginTop="@dimen/iscs_space_4"
                         android:textColor="?attr/colorTextPrimary"
                         android:textSize="@dimen/iscs_text_md"
-                        app:i18nKey='@{"backup_frequency"}'
+                        app:i18nKey='@{"backup_time"}'
                         app:layout_constraintEnd_toEndOf="@+id/end_line"
                         app:layout_constraintTop_toBottomOf="@+id/backup_frequency_tv" />
 

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

@@ -40,7 +40,6 @@
             android:layout_height="match_parent"
             android:layout_below="@+id/switch_id"
             android:layout_alignLeft="@+id/switch_id"
-            android:layout_toLeftOf="@+id/add_or_update_to_map"
             android:orientation="horizontal">
 
             <TextView

+ 10 - 2
data/src/main/java/com/grkj/data/dao/HardwareDao.kt

@@ -103,9 +103,11 @@ interface HardwareDao {
     /**
      * 获取默认数据数量
      */
-    @Query("""
+    @Query(
+        """
         SELECT seq FROM sqlite_sequence WHERE name = 'is_key' 
-    """)
+    """
+    )
     fun getLastKeyId(): Int
 
     /**
@@ -577,4 +579,10 @@ interface HardwareDao {
     @Query("update is_map_point set show_in_map = :showInMap where entity_id = :pointId")
     fun changeShowInMap(pointId: Long, showInMap: Boolean)
 
+    /**
+     * 根据用户id删除卡片
+     */
+    @Query("update is_job_card set user_id = null,user_name = null where user_id in (:userIds)")
+    fun removeCardBindByUserIds(userIds: List<Long>)
+
 }

+ 1 - 1
data/src/main/java/com/grkj/data/enums/DataExportTableEnum.kt

@@ -27,7 +27,7 @@ enum class DataExportTableEnum(val tableKey: String) {
     /**
      * 区域表
      */
-    WORKSTATION("workstation"),
+    WORKSTATION("area"),
 
     /**
      * 点位表

+ 4 - 6
data/src/main/java/com/grkj/data/enums/HardwareMode.kt

@@ -9,9 +9,10 @@ import com.sik.sikcore.extension.getMMKVData
 /**
  * 硬件模式
  */
-enum class HardwareMode {
+enum class HardwareMode(val iHardwareHelper: IHardwareHelper) {
 
-    MODBUS, CAN;
+    MODBUS(ModBusHardwareHelper()),
+    CAN(CanHardwareHelper());
 
     companion object {
         /**
@@ -20,10 +21,7 @@ enum class HardwareMode {
         fun getCurrentHardwareMode(): IHardwareHelper {
             val currentHardwareMode =
                 valueOf(MMKVConstants.KEY_HARDWARE_MODE.getMMKVData(MODBUS.name))
-            return when (currentHardwareMode) {
-                MODBUS -> ModBusHardwareHelper()
-                CAN -> CanHardwareHelper()
-            }
+            return currentHardwareMode.iHardwareHelper
         }
     }
 }

+ 16 - 22
data/src/main/java/com/grkj/data/hardware/modbus/ModBusController.kt

@@ -999,7 +999,7 @@ object ModBusController {
         var keyList = keyDockList.flatMap { it.deviceList }.apply {
             logger.info("keyStatus:${this}")
         }.filterIsInstance<DockBean.KeyBean>()
-            //RFID不为空,RFID不在异常列表,mac不在异常列表,mac不为空,存在,不在归还连接列表,不在归还连接中列表
+            // RFID不为空,RFID不在异常列表,mac不在异常列表,mac不为空,存在,不在归还连接列表,不在归还连接中列表
             .filterIndexed { idx, _ -> (idx + 1) !in slotCols }.filter { kb ->
                 !kb.rfid.isNullOrEmpty() && kb.rfid !in exceptionKeysRfid && kb.mac !in exceptionKeysMac && !kb.mac.isNullOrEmpty() && kb.isExist && !BleReturnDispatcher.isConnected(
                     kb.mac ?: ""
@@ -1011,7 +1011,7 @@ object ModBusController {
             return null
         }
         val sendConnectingAndConnected = BleSendDispatcher.getConnectedAndConnecting()
-        //如果已连接和连接中的设备不在钥匙列表中,直接断开连接让路
+        // 如果已连接和连接中的设备不在钥匙列表中,直接断开连接让路
         sendConnectingAndConnected.filter { it !in keyList.map { it.mac } }.forEach {
             BleSendDispatcher.scheduleDisconnect(it)
         }
@@ -1035,20 +1035,20 @@ object ModBusController {
         }
         val connectingKey = keyList.find { BleSendDispatcher.isConnecting(it.mac ?: "") }
         if (connectingKey != null) {
-            val result = suspendCoroutine { cont ->
+            val result = suspendCoroutine<Pair<Any, String?>?> { cont ->
+                val once = java.util.concurrent.atomic.AtomicBoolean(false)
+                fun resumeOnce(v: Pair<Any, String?>?) {
+                    if (once.compareAndSet(false, true)) cont.resume(v)
+                }
                 BleSendDispatcher.submit(connectingKey.mac ?: "") { connected ->
                     if (connected) {
                         val addr =
                             keyDockList.firstOrNull {
                                 it.getKeyList().any { it.rfid == connectingKey.rfid }
                             }?.addr
-                        if (addr != null) {
-                            cont.resume(addr to connectingKey.rfid)
-                        } else {
-                            cont.resume(null)
-                        }
+                        resumeOnce(if (addr != null) addr to connectingKey.rfid else null)
                     } else {
-                        cont.resume(null)
+                        resumeOnce(null)
                     }
                 }
             }
@@ -1058,7 +1058,11 @@ object ModBusController {
         }
         for (kb in keyList) {
             val mac = kb.mac ?: continue
-            val result = suspendCoroutine { cont ->
+            val result = suspendCoroutine<Pair<Any, String?>?> { cont ->
+                val once = java.util.concurrent.atomic.AtomicBoolean(false)
+                fun resumeOnce(v: Pair<Any, String?>?) {
+                    if (once.compareAndSet(false, true)) cont.resume(v)
+                }
                 BleSendDispatcher.submit(mac) { connected ->
                     if (connected) {
                         logger.info("蓝牙连接完成 :${mac}")
@@ -1068,19 +1072,9 @@ object ModBusController {
                                 it.getKeyList().any { it.rfid == kb.rfid }
                             }?.addr
                         logger.info("蓝牙连接-找到的底座地址 :${addr}")
-                        if (addr != null) {
-                            if (cont.context.isActive) {
-                                cont.resume(addr to kb.rfid)
-                            }
-                        } else {
-                            if (cont.context.isActive) {
-                                cont.resume(null)
-                            }
-                        }
+                        resumeOnce(if (addr != null) addr to kb.rfid else null)
                     } else {
-                        if (cont.context.isActive) {
-                            cont.resume(null)
-                        }
+                        resumeOnce(null)
                     }
                 }
             }

+ 1 - 1
data/src/main/java/com/grkj/data/hardware/modbus/PortManager.kt

@@ -387,7 +387,7 @@ class PortManager private constructor(
                 logger.info("扫描到新主控板串口: $newPort")
                 return null
             } else {
-                ToastEvent.sendToastEvent("未找到从机,请检查硬件连接")
+                logger.info("未找到从机,请检查硬件连接")
                 return null
             }
         }

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

@@ -419,4 +419,9 @@ interface IHardwareLogic {
      * 修改是否显示在地图上
      */
     fun changeShowInMap(pointId: Long, showInMap: Boolean)
+
+    /**
+     * 解绑工卡
+     */
+    fun removeCardBindByUserIds(userIds: List<Long>)
 }

+ 4 - 0
data/src/main/java/com/grkj/data/logic/impl/network/NetworkHardwareLogic.kt

@@ -300,6 +300,10 @@ class NetworkHardwareLogic  @Inject constructor() : BaseLogic(), IHardwareLogic
         TODO("Not yet implemented")
     }
 
+    override fun removeCardBindByUserIds(userIds: List<Long>) {
+        TODO("Not yet implemented")
+    }
+
     override fun updateMapPoint(pointToMapVo: PointToMapVo) {
         TODO("Not yet implemented")
     }

+ 4 - 0
data/src/main/java/com/grkj/data/logic/impl/standard/HardwareLogic.kt

@@ -227,6 +227,10 @@ class HardwareLogic @Inject constructor(
         hardwareDao.changeShowInMap(pointId, showInMap)
     }
 
+    override fun removeCardBindByUserIds(userIds: List<Long>) {
+        hardwareDao.removeCardBindByUserIds(userIds)
+    }
+
     override fun removePointFromMap(pointId: Long) {
         hardwareDao.deletePointFromMap(pointId)
     }

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

@@ -132,7 +132,6 @@ object BleBusinessManager {
                     val keyBean =
                         HardwareMode.getCurrentHardwareMode().getKeyBeanByMac(bleBean.bleDevice.mac)
                     if (keyBean == null) {
-                        LoadingEvent.sendLoadingEvent("未找到钥匙信息", true)
                         PopTip.build().tip(CommonUtils.getStr("key_not_exists"))
                     } else {
                         LoadingEvent.sendLoadingEvent(

+ 2 - 1
ui-base/src/main/java/com/grkj/ui_base/widget/CustomSwitchStationLayer.kt

@@ -477,7 +477,8 @@ class CustomSwitchStationLayer @JvmOverloads constructor(
                         STATUS_ON -> {
                             val t = pulsePhase
                             paint.color = colOn  // 保持颜色为 colOn(开关打开的颜色)
-                            paint.alpha = (255 * abs(sin(2f * Math.PI.toFloat() * t))).toInt()  // 控制闪烁的透明度
+                            paint.alpha = 255
+//                            paint.alpha = (255 * abs(sin(2f * Math.PI.toFloat() * t))).toInt()  // 控制闪烁的透明度
                             canvas.drawCircle(c.x, c.y, switchSize, paint)  // 绘制闪烁效果
                         }
 

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

@@ -87,7 +87,7 @@
         <item name="colorHomeBlockOngoing">@color/palette_yellow_bright</item>
         <item name="colorHomeBlockLocked">@color/palette_red_bright</item>
         <item name="colorHomeBlockUseHardware">@color/palette_navy</item>
-        <item name="colorSwitchOn">@color/palette_green_bright</item>
+        <item name="colorSwitchOn">@color/palette_dark_gray_bright</item>
         <item name="colorSwitchOff">@color/palette_red_bright</item>
         <item name="colorMapBase">@color/palette_dark_gray</item>
         <item name="colorJobExecuteStepDone">@color/chip_done_tint</item>