ソースを参照

fix(作业管理):
- 修复作业取消和结束权限判断问题
- 修复作业执行过程中权限判断问题
- 修复刷卡时权限判断问题

周文健 3 ヶ月 前
コミット
fb8a33f0f0
34 ファイル変更998 行追加251 行削除
  1. 12 1
      app/src/main/java/com/grkj/iscs/features/main/activity/MainActivity.kt
  2. 1 1
      app/src/main/java/com/grkj/iscs/features/main/entity/QuickEntranceMenuItemEntity.kt
  3. 44 34
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobExecuteFragment.kt
  4. 1 1
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/JobManageHomeFragment.kt
  5. 108 48
      app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/MyTodoListFragment.kt
  6. 7 11
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobExecuteViewModel.kt
  7. 0 14
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobManageViewModel.kt
  8. 157 0
      app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/MyTodoViewModel.kt
  9. 6 0
      app/src/main/res/drawable/bg_status_chip.xml
  10. 21 45
      app/src/main/res/layout/fragment_my_todo_list.xml
  11. 0 1
      app/src/main/res/layout/item_job_execute_point.xml
  12. 85 0
      app/src/main/res/layout/item_my_todo.xml
  13. BIN
      app/src/main/res/mipmap-xhdpi/icon_my_todo.png
  14. 13 0
      app/src/main/res/values-en/strings.xml
  15. 13 0
      app/src/main/res/values-zh/strings.xml
  16. 16 0
      app/src/main/res/values/colors.xml
  17. 13 0
      app/src/main/res/values/strings.xml
  18. 5 0
      app/src/main/res/values/styles.xml
  19. 74 37
      data/src/main/java/com/grkj/data/dao/JobTicketDao.kt
  20. 6 0
      data/src/main/java/com/grkj/data/dao/WorkflowStepDao.kt
  21. 5 0
      data/src/main/java/com/grkj/data/data/DictConstants.kt
  22. 6 0
      data/src/main/java/com/grkj/data/enums/CommonDictDataEnum.kt
  23. 41 0
      data/src/main/java/com/grkj/data/enums/OperationTypeEnum.kt
  24. 1 1
      data/src/main/java/com/grkj/data/model/dos/IsJobTicketKey.kt
  25. 5 0
      data/src/main/java/com/grkj/data/model/dos/IsJobTicketUser.kt
  26. 66 0
      data/src/main/java/com/grkj/data/model/local/TodoStepJoin.kt
  27. 94 0
      data/src/main/java/com/grkj/data/model/vo/TodoItemVo.kt
  28. 3 1
      data/src/main/java/com/grkj/data/repository/IJobTicketRepository.kt
  29. 2 4
      data/src/main/java/com/grkj/data/repository/impl/network/NetworkJobTicketRepository.kt
  30. 178 35
      data/src/main/java/com/grkj/data/repository/impl/standard/JobTicketRepository.kt
  31. 1 1
      gradle/libs.versions.toml
  32. 11 6
      ui-base/src/main/java/com/grkj/ui_base/business/BleBusinessManager.kt
  33. 3 3
      ui-base/src/main/java/com/grkj/ui_base/utils/event/FlashTipEvent.kt
  34. 0 7
      ui-base/src/main/java/com/grkj/ui_base/widget/FlashingTipTextView.kt

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

@@ -26,6 +26,7 @@ import com.grkj.shared.utils.extension.toHexStrings
 import com.grkj.ui_base.utils.event.FlashTipEvent
 import com.grkj.ui_base.utils.event.RFIDCardReadEvent
 import com.sik.sikcore.extension.file
+import com.sik.sikcore.extension.toJson
 import com.sik.sikimage.ImageConvertUtils
 import dagger.hilt.android.AndroidEntryPoint
 
@@ -134,7 +135,17 @@ class MainActivity() : BaseActivity<ActivityMainBinding>() {
 
             EventConstants.EVENT_FLASH_TIP_CODE -> {
                 (event.data as FlashTipEvent).apply {
-                    binding.flashTipTv.isVisible = isShow
+                    if (isShow) {
+                        binding.flashTipTv.isVisible = true
+                        if (!binding.flashTipTv.isFlashing()) {
+                            binding.flashTipTv.startFlashing()
+                        }
+                    } else {
+                        if (binding.flashTipTv.isFlashing()) {
+                            binding.flashTipTv.stopFlashing()
+                        }
+                        binding.flashTipTv.isVisible = false
+                    }
                     binding.flashTipTv.text = msg
                 }
             }

+ 1 - 1
app/src/main/java/com/grkj/iscs/features/main/entity/QuickEntranceMenuItemEntity.kt

@@ -31,7 +31,7 @@ data class QuickEntranceMenuItemEntity(
                 RoleFunctionalPermissionsEnum.ROLE_MANAGE -> R.mipmap.icon_data_manage_menu_role_manage
                 RoleFunctionalPermissionsEnum.WORKSTATION_MANAGE -> R.mipmap.icon_data_manage_menu_area_manage
                 RoleFunctionalPermissionsEnum.POINT_MANAGE -> R.mipmap.icon_data_manage_menu_point_manage
-                RoleFunctionalPermissionsEnum.TODO_LIST -> R.mipmap.icon_job_manage_in_progress_job
+                RoleFunctionalPermissionsEnum.TODO_LIST -> R.mipmap.icon_my_todo
                 RoleFunctionalPermissionsEnum.IN_PROGRESS_JOB -> R.mipmap.icon_job_manage_in_progress_job
                 RoleFunctionalPermissionsEnum.CREATE_SOP -> R.mipmap.icon_job_manage_create_sop
                 RoleFunctionalPermissionsEnum.SOP_MANAGE -> R.mipmap.icon_job_manage_sop_manage

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

@@ -85,22 +85,26 @@ class JobExecuteFragment : BaseFragment<FragmentJobExecuteBinding>() {
             }
         }
         binding.cancelJob.setDebouncedClickListener {
-            viewModel.cancelJob().observe(this) {
-                navController.popBackStack()
-            }
+            TipDialog.showInfo(getString(R.string.cancel_job_tip), onConfirmClick = {
+                viewModel.cancelJob().observe(this) {
+                    navController.popBackStack()
+                }
+            })
         }
         binding.finishJob.setDebouncedClickListener {
-            viewModel.finishJob().observe(this) {
-                navController.popBackStack()
-            }
+            TipDialog.showInfo(getString(R.string.finish_job_tip), onConfirmClick = {
+                viewModel.finishJob().observe(this) {
+                    navController.popBackStack()
+                }
+            })
         }
         binding.toLock.setDebouncedClickListener {
             if (viewModel.groupInfo.isEmpty()) {
                 showToast(CommonUtils.getStr(R.string.not_group_can_lock).toString())
                 return@setDebouncedClickListener
             }
-            BottomMenu.build()
-                .setMenuList(viewModel.groupInfo.map { it.groupName })
+            BottomMenu.show(viewModel.groupInfo.map { it.groupName })
+                .setTitle(CommonUtils.getStr(R.string.please_select_group))
                 .setOnMenuItemClickListener { popMenu, itemText, position ->
                     popMenu.dismiss()
                     toLock(viewModel.groupInfo[position].groupId)
@@ -113,8 +117,8 @@ class JobExecuteFragment : BaseFragment<FragmentJobExecuteBinding>() {
                 showToast(CommonUtils.getStr(R.string.not_group_can_unlock).toString())
                 return@setDebouncedClickListener
             }
-            BottomMenu.build()
-                .setMenuList(viewModel.groupInfo.map { it.groupName })
+            BottomMenu.show(viewModel.groupInfo.map { it.groupName })
+                .setTitle(CommonUtils.getStr(R.string.please_select_group))
                 .setOnMenuItemClickListener { popMenu, itemText, position ->
                     popMenu.dismiss()
                     toUnLock(viewModel.groupInfo[position].groupId)
@@ -420,33 +424,39 @@ class JobExecuteFragment : BaseFragment<FragmentJobExecuteBinding>() {
         itemBinding.pointGroup.text = item.groupName
         itemBinding.pointFunction.text = item.pointFunction
         itemBinding.lockStatusLayout.isVisible = item.pointStatus != "0"
-        if (item.pointStatus == "1") {
-            itemBinding.lockStatusTv.setTextColor(
-                ContextCompat.getColor(
-                    requireContext(),
-                    R.color.white
+        when (item.pointStatus) {
+            "1" -> {
+                itemBinding.lockStatusTv.setTextColor(
+                    ContextCompat.getColor(
+                        requireContext(),
+                        R.color.white
+                    )
                 )
-            )
-            itemBinding.lockStatusLayout.setBackgroundResource(R.drawable.bg_btn_job_execute_go_locking)
-            itemBinding.lockStatusIv.setImageResource(R.drawable.icon_ticket_lock)
-        } else if (item.pointStatus == "2") {
-            itemBinding.lockStatusTv.setTextColor(
-                ContextCompat.getColor(
-                    requireContext(),
-                    R.color.white
+                itemBinding.lockStatusLayout.setBackgroundResource(R.drawable.bg_btn_job_execute_go_locking)
+                itemBinding.lockStatusIv.setImageResource(R.drawable.icon_ticket_lock)
+            }
+
+            "2" -> {
+                itemBinding.lockStatusTv.setTextColor(
+                    ContextCompat.getColor(
+                        requireContext(),
+                        R.color.white
+                    )
                 )
-            )
-            itemBinding.lockStatusLayout.setBackgroundResource(R.drawable.bg_btn_job_execute_go_unlocking)
-            itemBinding.lockStatusIv.setImageResource(R.drawable.icon_ticket_unlock)
-        } else {
-            itemBinding.lockStatusTv.setTextColor(
-                ContextCompat.getColor(
-                    requireContext(),
-                    R.color.black
+                itemBinding.lockStatusLayout.setBackgroundResource(R.drawable.bg_btn_job_execute_go_unlocking)
+                itemBinding.lockStatusIv.setImageResource(R.drawable.icon_ticket_unlock)
+            }
+
+            else -> {
+                itemBinding.lockStatusTv.setTextColor(
+                    ContextCompat.getColor(
+                        requireContext(),
+                        R.color.black
+                    )
                 )
-            )
-            itemBinding.lockStatusLayout.background = null
-            itemBinding.lockStatusIv.isVisible = false
+                itemBinding.lockStatusLayout.setBackgroundColor(requireContext().getColor(R.color.white))
+                itemBinding.lockStatusIv.setImageResource(0)
+            }
         }
         itemBinding.lockStatusTv.text = item.lockerName
     }

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

@@ -28,7 +28,7 @@ class JobManageHomeFragment : BaseFragment<FragmentJobManageHomeBinding>() {
     private var menuData: MutableList<MenuItemEntity> = mutableListOf(
         MenuItemEntity(
             0,
-            R.mipmap.icon_job_manage_in_progress_job,
+            R.mipmap.icon_my_todo,
             RoleFunctionalPermissionsEnum.TODO_LIST.description,
             RoleFunctionalPermissionsEnum.TODO_LIST.functionalPermission
         ),

+ 108 - 48
app/src/main/java/com/grkj/iscs/features/main/fragment/job_manage/MyTodoListFragment.kt

@@ -1,34 +1,33 @@
 package com.grkj.iscs.features.main.fragment.job_manage
 
-import android.graphics.Color
 import androidx.core.view.isVisible
 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.grid
 import com.drake.brv.utils.linear
 import com.drake.brv.utils.models
 import com.drake.brv.utils.setup
-import com.grkj.data.enums.JobTicketStatusEnum
-import com.grkj.data.model.vo.JobTicketManageVo
+import com.google.android.material.tabs.TabLayout
+import com.grkj.data.enums.OperationTypeEnum
+import com.grkj.data.model.vo.TodoItemVo
 import com.grkj.iscs.R
 import com.grkj.iscs.common.DataTransferConstants
-import com.grkj.iscs.databinding.FragmentInProgressJobManageBinding
 import com.grkj.iscs.databinding.FragmentMyTodoListBinding
-import com.grkj.iscs.databinding.ItemJobManageBinding
-import com.grkj.iscs.features.main.viewmodel.job_manage.JobManageViewModel
+import com.grkj.iscs.databinding.ItemMyTodoBinding
+import com.grkj.iscs.features.main.viewmodel.job_manage.MyTodoViewModel
 import com.grkj.ui_base.base.BaseFragment
+import com.grkj.ui_base.dialog.TipDialog
 import com.sik.sikcore.data.GlobalDataTempStore
 import com.sik.sikcore.extension.setDebouncedClickListener
 import dagger.hilt.android.AndroidEntryPoint
-import kotlin.getValue
 
 /**
  * 我的待办
  */
 @AndroidEntryPoint
 class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
-    private val viewModel: JobManageViewModel by viewModels()
+    private val viewModel: MyTodoViewModel by viewModels()
+
     override fun getLayoutId(): Int {
         return R.layout.fragment_my_todo_list
     }
@@ -37,63 +36,124 @@ class MyTodoListFragment : BaseFragment<FragmentMyTodoListBinding>() {
         binding.back.setDebouncedClickListener {
             navController.popBackStack()
         }
-        binding.refreshLayout.setOnRefreshListener {
-            getData(nextPage = false)
-        }
-        binding.refreshLayout.setOnLoadMoreListener {
-            getData()
-        }
-        binding.listRv.linear().divider {
-            this.setColor(Color.BLACK)
-            this.startVisible = false
-            this.endVisible = true
-            this.orientation = DividerOrientation.VERTICAL
+        binding.listRv.apply {
+            if (isLandscape()) {
+                grid(2)
+            } else {
+                linear()
+            }
         }.setup {
-            addType<JobTicketManageVo>(R.layout.item_job_manage)
+            addType<TodoItemVo>(R.layout.item_my_todo)
             onBind {
                 onListDataBinding(this)
             }
         }
+        setupTabs()
     }
 
+    private fun setupTabs() {
+        val tabTitles = listOf(
+            getString(R.string.wait_header),
+            getString(R.string.todo_header),
+            getString(R.string.done_header)
+        )
+        tabTitles.forEach { title ->
+            binding.tabLayout.addTab(
+                binding.tabLayout.newTab().setText(title)
+            )
+        }
+
+        binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
+            override fun onTabSelected(tab: TabLayout.Tab) {
+                filterAndUpdate(tab)
+            }
+
+            override fun onTabUnselected(tab: TabLayout.Tab) {}
+            override fun onTabReselected(tab: TabLayout.Tab) {}
+        })
+    }
+
+    /**
+     * 过滤和更新数据
+     */
+    private fun filterAndUpdate(tab: TabLayout.Tab? = null) {
+        val (data, index) = when {
+            tab != null -> {
+                val list = when (tab.position) {
+                    0 -> viewModel.waitData
+                    1 -> viewModel.todoData
+                    2 -> viewModel.doneData
+                    else -> mutableListOf()
+                }
+                list to tab.position
+            }
+
+            else -> {
+                // 优先级判断:todo > wait > done
+                when {
+                    viewModel.todoData.isNotEmpty() -> viewModel.todoData to 1
+                    viewModel.waitData.isNotEmpty() -> viewModel.waitData to 0
+                    else -> viewModel.doneData to 2
+                }
+            }
+        }
+
+        // UI 渲染逻辑
+        if (data.isEmpty()) {
+            binding.state.showEmpty()
+        } else {
+            binding.state.showContent()
+        }
+
+        // 自动切换 tab 并更新 RecyclerView 数据
+        binding.tabLayout.selectTab(binding.tabLayout.getTabAt(index))
+        binding.listRv.models = data
+    }
+
+
     private fun onListDataBinding(holder: BindingAdapter.BindingViewHolder) {
-        val itemBinding = holder.getBinding<ItemJobManageBinding>()
-        val item = holder.getModel<JobTicketManageVo>()
+        val itemBinding = holder.getBinding<ItemMyTodoBinding>()
+        val item = holder.getModel<TodoItemVo>()
         itemBinding.jobName.text = item.ticketName
-        itemBinding.status.text = JobTicketStatusEnum.getTicketStatusStr(item.ticketStatus)
-        itemBinding.select.isVisible = false
-        itemBinding.view.setDebouncedClickListener {
+        itemBinding.todoTitle.text = getString(R.string.todo_title, item.todoTitle)
+        itemBinding.todoContent.text = getString(R.string.todo_content, item.todoType.desc)
+        itemBinding.btnHandle.isVisible =
+            item.isCurrentStep && item.todoType != OperationTypeEnum.LOCK_RETURN_KEY && item.todoType != OperationTypeEnum.UNLOCK_RETURN_KEY
+        itemBinding.btnHandle.setDebouncedClickListener {
+            if (item.todoType== OperationTypeEnum.UNKNOWN){
+                TipDialog.showError(getString(R.string.handle_unknown))
+                return@setDebouncedClickListener
+            }
+            TipDialog.showInfo(viewModel.getHandleTip(item), onConfirmClick = {
+                viewModel.handleTodo(item).observe(this) {
+
+                }
+            })
+        }
+        itemBinding.cardRoot.setDebouncedClickListener {
             GlobalDataTempStore.getInstance()
                 .saveData(DataTransferConstants.KEY_JOB_TICKET_ID, item.ticketId)
-            if (item.exStatus == viewModel.jobTicketStatus.find { it.dictLabel == "异常" }?.dictValue?.toInt()) {
-                viewModel.getExceptionIdByTicketId(item.ticketId)
-                    .observe(this@MyTodoListFragment) {
-                        GlobalDataTempStore.getInstance()
-                            .saveData(DataTransferConstants.KEY_EXCEPTION_ID, it)
-                        navController.navigate(R.id.action_myTodoListFragment_to_nav_exception_job_manage)
-                    }
-            } else {
-                navController.navigate(R.id.action_myTodoListFragment_to_jobExecuteFragment)
-            }
+            navController.navigate(R.id.action_myTodoListFragment_to_jobExecuteFragment)
         }
     }
 
     override fun onResume() {
         super.onResume()
-        getData(nextPage = false)
+        getData()
     }
 
-    private fun getData(nextPage: Boolean = true) {
-        viewModel.getMyTodoList(nextPage).observe(this) {
-            binding.refreshLayout.finishRefresh()
-            binding.refreshLayout.finishLoadMore()
-            val data = viewModel.jobManageDataList
-            if (data.isEmpty()) {
-                binding.state.showEmpty()
-            } else {
-                binding.state.showContent()
+    private fun getData() {
+        viewModel.getMyTodoList().observe(this) {
+            for (tabPosition in 0 until binding.tabLayout.tabCount) {
+                binding.tabLayout.getTabAt(tabPosition)?.badge?.text = "${
+                    when (tabPosition) {
+                        0 -> viewModel.waitData.size
+                        1 -> viewModel.todoData.size
+                        else -> viewModel.doneData.size
+                    }
+                }"
             }
-            binding.listRv.models = data
+            filterAndUpdate()
         }
     }
 }

+ 7 - 11
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/JobExecuteViewModel.kt

@@ -199,7 +199,7 @@ class JobExecuteViewModel @Inject constructor(
             val role =
                 ticketDetail?.ticketUserVOList?.filter { it.userRole == RoleEnum.JTLOCKER.roleKey }
                     ?.find {
-                        it.userId == MainDomainData.userInfo?.userId && workflowStep?.currentUserCanConfirm() == true && it.groupId == groupId
+                        it.userId == MainDomainData.userInfo?.userId && it.groupId == groupId
                     }
             if (role == null) {
                 ThreadUtils.runOnMain {
@@ -320,7 +320,7 @@ class JobExecuteViewModel @Inject constructor(
             val role =
                 ticketDetail?.ticketUserVOList?.filter { it.userRole == RoleEnum.JTLOCKER.roleKey }
                     ?.find {
-                        it.userId == MainDomainData.userInfo?.userId && workflowStep?.currentUserCanConfirm() == true && it.groupId == groupId
+                        it.userId == MainDomainData.userInfo?.userId && it.groupId == groupId
                     }
             if (role == null) {
                 ThreadUtils.runOnMain {
@@ -640,7 +640,7 @@ class JobExecuteViewModel @Inject constructor(
                         tip = CommonUtils.getStr(com.grkj.ui_base.R.string.please_go_locking)
                             .toString()
                         index = 1
-                    } else if (ticketPoints.any { it.pointStatus != "1" } && ticketKey.filter { it.ticketType == 0L }
+                    } else if (ticketPoints.any { it.pointStatus != "1" } && ticketKey.filter { it.ticketType == 0 }
                             .any { it.collectTime.isNullOrEmpty() }) {
                         tip =
                             CommonUtils.getStr(com.grkj.ui_base.R.string.please_return_key_after_locking)
@@ -665,7 +665,7 @@ class JobExecuteViewModel @Inject constructor(
                         tip = CommonUtils.getStr(com.grkj.ui_base.R.string.please_go_unlocking)
                             .toString()
                         index = 1
-                    } else if (ticketPoints.any { it.pointStatus != "2" } && ticketKey.filter { it.ticketType == 1L }
+                    } else if (ticketPoints.any { it.pointStatus != "2" } && ticketKey.filter { it.ticketType == 1 }
                             .any { it.collectTime.isNullOrEmpty() }) {
                         tip =
                             CommonUtils.getStr(com.grkj.ui_base.R.string.please_return_key_after_unlocking)
@@ -707,7 +707,7 @@ class JobExecuteViewModel @Inject constructor(
                     if (ticketPoints.any { it.pointStatus != "1" } && ticketKey.isEmpty()) {
                         tip = CommonUtils.getStr(com.grkj.ui_base.R.string.please_go_locking)
                             .toString()
-                    } else if (ticketPoints.any { it.pointStatus != "1" } && ticketKey.filter { it.ticketType == 0L }
+                    } else if (ticketPoints.any { it.pointStatus != "1" } && ticketKey.filter { it.ticketType == 0 }
                             .any { it.collectTime.isNullOrEmpty() }) {
                         tip =
                             CommonUtils.getStr(com.grkj.ui_base.R.string.please_return_key_after_locking)
@@ -728,7 +728,7 @@ class JobExecuteViewModel @Inject constructor(
                     if (ticketPoints.any { it.pointStatus != "2" }) {
                         tip = CommonUtils.getStr(com.grkj.ui_base.R.string.please_go_unlocking)
                             .toString()
-                    } else if (ticketPoints.any { it.pointStatus != "2" } && ticketKey.filter { it.ticketType == 1L }
+                    } else if (ticketPoints.any { it.pointStatus != "2" } && ticketKey.filter { it.ticketType == 1 }
                             .any { it.collectTime.isNullOrEmpty() }) {
                         tip =
                             CommonUtils.getStr(com.grkj.ui_base.R.string.please_return_key_after_unlocking)
@@ -747,12 +747,8 @@ class JobExecuteViewModel @Inject constructor(
      */
     fun canCheckStep(): LiveData<Boolean> {
         return liveData(Dispatchers.IO) {
-            val ticketDetail = RepositoryManager.jobTicketRepo.getTicketDetail(ticketId)
             val workflowStep = workflowSteps.find { it.stepId == currentStepData?.workflowStepId }
-            val role = ticketDetail?.ticketUserVOList?.find {
-                it.userId == MainDomainData.userInfo?.userId && workflowStep?.currentUserCanConfirm() == true
-            }
-            if (role == null) {
+            if (ticketData?.createBy != MainDomainData.userInfo?.userName || workflowStep?.currentUserCanConfirm() == true) {
                 ThreadUtils.runOnMain {
                     PopTip.build()
                         .tip(CommonUtils.getStr(com.grkj.ui_base.R.string.no_permission_to_handle))

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

@@ -52,20 +52,6 @@ class JobManageViewModel @Inject constructor(
         }
     }
 
-    fun getMyTodoList(nextPage: Boolean): LiveData<Boolean> {
-        if (nextPage) {
-            current += 1
-        } else {
-            current = 0
-            jobManageDataList.clear()
-        }
-        return liveData(Dispatchers.IO) {
-            jobTicketStatus = DataBusiness.fetchDict(DictConstants.KEY_JOB_TICKET_STATUS)
-            jobManageDataList.addAll(jobTicketRepository.getMyTodoList(current, size))
-            emit(true)
-        }
-    }
-
     /**
      * 根据作业id获取异常id
      */

+ 157 - 0
app/src/main/java/com/grkj/iscs/features/main/viewmodel/job_manage/MyTodoViewModel.kt

@@ -0,0 +1,157 @@
+package com.grkj.iscs.features.main.viewmodel.job_manage
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.liveData
+import com.grkj.data.data.DictConstants
+import com.grkj.data.data.MainDomainData
+import com.grkj.data.enums.OperationTypeEnum
+import com.grkj.data.model.local.TodoStepJoin
+import com.grkj.data.model.res.CommonDictRes
+import com.grkj.data.model.vo.TodoItemVo
+import com.grkj.data.repository.IJobTicketRepository
+import com.grkj.iscs.R
+import com.grkj.ui_base.base.BaseViewModel
+import com.grkj.ui_base.business.DataBusiness
+import com.grkj.ui_base.utils.CommonUtils
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import javax.inject.Inject
+
+/**
+ * 作业管理
+ */
+@HiltViewModel
+class MyTodoViewModel @Inject constructor(
+    val jobTicketRepository: IJobTicketRepository,
+) : BaseViewModel() {
+    var jobTicketStatus: List<CommonDictRes> = mutableListOf()
+    var todoItemVos: MutableList<TodoItemVo> = mutableListOf()
+    var waitData: MutableList<TodoItemVo> = mutableListOf()
+    var todoData: MutableList<TodoItemVo> = mutableListOf()
+    var doneData: MutableList<TodoItemVo> = mutableListOf()
+    var todoStatus: List<CommonDictRes> = mutableListOf()
+
+    val tabKeys by lazy {
+        listOf(
+            CommonUtils.getStr(R.string.wait_header).toString(),
+            CommonUtils.getStr(R.string.todo_header).toString(),
+            CommonUtils.getStr(R.string.done_header).toString()
+        )
+    }
+
+    /**
+     * 获取待办列表
+     */
+    fun getMyTodoList(): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+            todoItemVos.clear()
+            todoStatus = DataBusiness.fetchDict(DictConstants.KEY_TODO_STATUS)
+            jobTicketStatus = DataBusiness.fetchDict(DictConstants.KEY_JOB_TICKET_STATUS)
+            val todoItemData = jobTicketRepository.getMyTodoList()
+            todoItemVos =
+                todoItemData.map { it.toTodoVo(todoItemData.filter { temp -> it.ticketId == temp.ticketId }) }
+                    .toMutableList()
+            splitTodoSteps(todoItemVos, 1)
+            emit(true)
+        }
+    }
+
+    private fun splitTodoSteps(
+        all: List<TodoItemVo>,
+        waitLimit: Int = Int.MAX_VALUE // 支持限制 waitData 条数
+    ) {
+        // 初始化容器
+        val tempTodo = mutableListOf<TodoItemVo>()
+        val tempWait = mutableListOf<TodoItemVo>()
+        val tempDone = mutableListOf<TodoItemVo>()
+
+        // 1. 先按 ticketId 分组
+        val grouped = all.groupBy { it.ticketId }
+
+        grouped.forEach { (_, steps) ->
+            val doneSteps = steps.filter { it.stepStatus == "1" }
+            val pendingSteps = steps.filter { it.stepStatus == "0" }
+
+            // 当前步骤 = 最小 stepIndex 的 pending
+            val todoStep = pendingSteps.minByOrNull { it.stepIndex }
+            todoStep?.let { tempTodo.add(it) }
+
+            // 剩下的就是 wait
+            pendingSteps.filter { it != todoStep }.let { tempWait.addAll(it) }
+
+            tempDone.addAll(doneSteps)
+        }
+
+        // 应用 limit 到 waitData
+        waitData = tempWait.take(waitLimit).toMutableList()
+        todoData = tempTodo
+        doneData = tempDone
+    }
+
+
+    fun TodoStepJoin.toTodoVo(sameTicketStepJoinData: List<TodoStepJoin>): TodoItemVo {
+        val isCreator = createBy == MainDomainData.userInfo?.userName
+        val type = OperationTypeEnum.fromJoin(this, isCreator)
+        val temp = this
+        return TodoItemVo().apply {
+            this.todoId = temp.stepId
+            this.ticketId = temp.ticketId
+            this.ticketName = temp.ticketName
+            this.stepId = temp.stepId
+            this.workflowStepId = temp.workflowStepId
+            this.stepIndex = temp.stepIndex
+            this.isCurrentStep =
+                sameTicketStepJoinData.filter { it.stepStatus == "0" }
+                    .minByOrNull { it.stepIndex }?.stepIndex == temp.stepIndex
+            this.todoStatus = when {
+                temp.stepStatus == "0" && this.isCurrentStep -> CommonUtils.getStr(R.string.todo_header)
+                temp.stepStatus == "0" && !this.isCurrentStep -> CommonUtils.getStr(R.string.wait_header)
+                temp.stepStatus == "1" -> CommonUtils.getStr(R.string.done_header)
+                else -> ""
+            }.toString()
+            this.todoTitle = temp.stepTitleShort ?: temp.stepTitle
+            this.todoContent = temp.stepDescription
+            this.todoType = type
+            this.groupId = temp.groupId
+            this.stepStatus = temp.stepStatus
+            this.createTime = temp.ticketStartTime
+        }
+    }
+
+    /**
+     * 处理上锁取钥匙
+     */
+    fun handleLockTakeKey(todoItemVo: TodoItemVo) {
+
+    }
+
+    /**
+     * 获取处理提醒
+     */
+    fun getHandleTip(item: TodoItemVo): String {
+        return when (item.todoType) {
+            OperationTypeEnum.LOCK_TAKE_KEY -> CommonUtils.getStr(R.string.handle_lock_take_key)
+                .toString()
+
+            OperationTypeEnum.UNLOCK_TAKE_KEY -> CommonUtils.getStr(R.string.handle_unlock_take_key)
+                .toString()
+
+            OperationTypeEnum.CONFIRM -> CommonUtils.getStr(
+                R.string.handle_step_confirm,
+                listOf(item.todoTitle)
+            ).toString()
+
+            OperationTypeEnum.END -> CommonUtils.getStr(R.string.finish_job_tip).toString()
+            else -> ""
+        }
+    }
+
+    /**
+     * 处理待办
+     */
+    fun handleTodo(item: TodoItemVo): LiveData<Boolean> {
+        return liveData(Dispatchers.IO) {
+
+        }
+    }
+}

+ 6 - 0
app/src/main/res/drawable/bg_status_chip.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/tag_lock" />
+</shape>

+ 21 - 45
app/src/main/res/layout/fragment_my_todo_list.xml

@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
-<layout xmlns:android="http://schemas.android.com/apk/res/android">
+<layout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
 
     <LinearLayout
         android:layout_width="match_parent"
@@ -19,14 +20,14 @@
             <ImageView
                 android:layout_width="@dimen/title_icon_size"
                 android:layout_height="@dimen/title_icon_size"
-                android:src="@mipmap/icon_data_manage_menu_user_manage" />
+                android:src="@mipmap/icon_my_todo" />
 
             <TextView
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:layout_marginLeft="@dimen/common_spacing"
                 android:layout_weight="1"
-                android:text="@string/in_progress_job_manage_title"
+                android:text="@string/my_todo_title"
                 android:textColor="@color/black"
                 android:textSize="@dimen/normal_text_size_25"
                 android:textStyle="bold" />
@@ -55,58 +56,33 @@
 
         <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">
+            android:layout_height="match_parent"
+            android:layout_margin="@dimen/common_spacing_2x"
+            android:orientation="vertical">
 
-            <TextView
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"
-                android:gravity="center"
+            <com.google.android.material.tabs.TabLayout
+                android:id="@+id/tabLayout"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:background="@drawable/card_white_bg"
+                android:elevation="4dp"
                 android:paddingVertical="@dimen/common_spacing"
-                android:text="@string/job_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/status"
-                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/detail"
-                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">
+                app:tabGravity="fill"
+                app:tabMaxWidth="0dp"
+                app:tabMode="fixed"
+                app:tabTextAppearance="@style/TabTextStyle" />
 
             <com.drake.statelayout.StateLayout
                 android:id="@+id/state"
                 android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:background="@drawable/common_card_bg">
+                android:layout_height="0dp"
+                android:layout_weight="1">
 
                 <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" />
+                    android:layout_height="match_parent" />
             </com.drake.statelayout.StateLayout>
-        </com.scwang.smart.refresh.layout.SmartRefreshLayout>
+        </LinearLayout>
     </LinearLayout>
 </layout>

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

@@ -66,7 +66,6 @@
                     android:layout_height="wrap_content"
                     android:layout_marginLeft="@dimen/common_spacing_small"
                     android:text="@string/has_locked"
-                    android:textColor="@color/white"
                     android:textSize="@dimen/common_text_size" />
             </LinearLayout>
         </FrameLayout>

+ 85 - 0
app/src/main/res/layout/item_my_todo.xml

@@ -0,0 +1,85 @@
+<?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">
+
+    <com.google.android.material.card.MaterialCardView
+        android:id="@+id/cardRoot"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="16dp"
+        android:layout_marginVertical="8dp"
+        app:cardCornerRadius="4dp"
+        app:cardElevation="2dp"
+        app:strokeColor="@color/border_light"
+        app:strokeWidth="1dp">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="@color/white"
+            android:padding="12dp">
+
+            <TextView
+                android:id="@+id/job_name"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="@dimen/common_spacing"
+                android:ellipsize="end"
+                android:maxLines="1"
+                android:paddingVertical="@dimen/common_spacing"
+                android:textColor="@color/text_primary"
+                android:textSize="@dimen/common_text_size"
+                android:textStyle="bold"
+                app:layout_constraintEnd_toStartOf="@id/btn_handle"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toTopOf="parent" />
+
+            <!-- 蓝色圆角确认按钮 -->
+            <TextView
+                android:id="@+id/btn_handle"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@drawable/bg_status_chip"
+                android:paddingHorizontal="@dimen/common_spacing_2x"
+                android:paddingVertical="@dimen/common_spacing"
+                android:text="@string/handle"
+                android:textColor="@color/white"
+                android:textSize="@dimen/common_text_size_small"
+                app:layout_constraintBottom_toBottomOf="@+id/job_name"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintTop_toTopOf="@+id/job_name"
+                app:rippleColor="@color/brand_blue_dark" />
+
+            <TextView
+                android:id="@+id/todo_title"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="6dp"
+                android:ellipsize="end"
+                android:maxLines="2"
+                android:paddingVertical="@dimen/common_spacing"
+                android:textColor="@color/text_secondary"
+                android:textSize="@dimen/common_text_size_small"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/job_name" />
+
+            <TextView
+                android:id="@+id/todo_content"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="6dp"
+                android:ellipsize="end"
+                android:maxLines="2"
+                android:paddingVertical="@dimen/common_spacing"
+                android:textColor="@color/text_secondary"
+                android:textSize="@dimen/common_text_size_small"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/todo_title" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+    </com.google.android.material.card.MaterialCardView>
+
+
+</layout>

BIN
app/src/main/res/mipmap-xhdpi/icon_my_todo.png


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

@@ -483,5 +483,18 @@
     <string name="admin_role_can_not_edit">Admin role can not edit</string>
     <string name="change_to_standard">Switch to Standard Version</string>
     <string name="restart_app_after_set">The app will restart after the settings are completed</string>
+    <string name="todo_title">To Do Steps:%s</string>
+    <string name="todo_content">To Do Content:%s</string>
+    <string name="todo_header">Todo</string>
+    <string name="done_header">Done</string>
+    <string name="wait_header">Waiting</string>
+    <string name="my_todo_title">My TODO</string>
+    <string name="handle">Handle</string>
+    <string name="cancel_job_tip">Are you sure to cancel the current job</string>
+    <string name="finish_job_tip">Are you sure to end the current job</string>
+    <string name="handle_unknown">The current processing type is unknown and cannot be processed</string>
+    <string name="handle_step_confirm">Please confirm if you have completed [%s]</string>
+    <string name="handle_lock_take_key">Are you sure to obtain the key for locking?</string>
+    <string name="handle_unlock_take_key">Are you sure to obtain the key for unlocking?</string>
 
 </resources>

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

@@ -483,5 +483,18 @@
     <string name="admin_role_can_not_edit">管理员角色无法编辑</string>
     <string name="change_to_standard">切换标准版</string>
     <string name="restart_app_after_set">App将在设置完成后重启</string>
+    <string name="todo_title">待办步骤:%s</string>
+    <string name="todo_content">待办内容:%s</string>
+    <string name="todo_header">处理中</string>
+    <string name="done_header">已处理</string>
+    <string name="wait_header">等待中</string>
+    <string name="my_todo_title">我的待办</string>
+    <string name="handle">处理</string>
+    <string name="cancel_job_tip">是否确认取消当前作业</string>
+    <string name="finish_job_tip">是否确认结束当前作业</string>
+    <string name="handle_unknown">当前处理类型未知,无法处理</string>
+    <string name="handle_step_confirm">请确认是否完成[%s]</string>
+    <string name="handle_lock_take_key">确认获取钥匙进行上锁吗?</string>
+    <string name="handle_unlock_take_key">确认获取钥匙进行解锁吗?</string>
 
 </resources>

+ 16 - 0
app/src/main/res/values/colors.xml

@@ -19,4 +19,20 @@
     <color name="color_black50">#80000000</color>
     <color name="color_black80">#cc000000</color>
     <color name="common_status_red_title">#e03131</color>
+    <!-- Brand / Action -->
+    <color name="brand_blue">#1890FF</color>        <!-- 企业微信主色调 -->
+    <color name="brand_blue_dark">#1276E5</color>
+
+    <!-- Text -->
+    <color name="text_primary">#1F1F1F</color>
+    <color name="text_secondary">#8C8C8C</color>
+
+    <!-- Border / Background -->
+    <color name="border_light">#E6E6E6</color>
+
+    <!-- 其他状态色可自定义 -->
+    <color name="tag_lock">#3B82F6</color>
+    <color name="tag_error">#FF4D4F</color>
+    <color name="tag_unlock">#10B981</color>
+    <color name="tag_confirm">#F59E0B</color>
 </resources>

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

@@ -486,5 +486,18 @@
     <string name="admin_role_can_not_edit">管理员角色无法编辑</string>
     <string name="change_to_standard">切换标准版</string>
     <string name="restart_app_after_set">App将在设置完成后重启</string>
+    <string name="todo_title">待办步骤:%s</string>
+    <string name="todo_content">待办内容:%s</string>
+    <string name="todo_header">处理中</string>
+    <string name="done_header">已处理</string>
+    <string name="wait_header">等待中</string>
+    <string name="my_todo_title">我的待办</string>
+    <string name="handle">处理</string>
+    <string name="cancel_job_tip">是否确认取消当前作业</string>
+    <string name="finish_job_tip">是否确认结束当前作业</string>
+    <string name="handle_unknown">当前处理类型未知,无法处理</string>
+    <string name="handle_step_confirm">请确认是否完成[%s]</string>
+    <string name="handle_lock_take_key">确认获取钥匙进行上锁吗?</string>
+    <string name="handle_unlock_take_key">确认获取钥匙进行解锁吗?</string>
 
 </resources>

+ 5 - 0
app/src/main/res/values/styles.xml

@@ -11,6 +11,11 @@
         <item name="android:background">@drawable/table_row_border</item>
     </style>
 
+    <style name="TabTextStyle" parent="TextAppearance.Design.Tab">
+        <item name="android:textSize">@dimen/common_text_size_small</item>
+        <item name="android:textStyle">bold</item>
+    </style>
+
     <style name="TableCellValue">
         <item name="android:padding">@dimen/common_spacing</item>
         <item name="android:textColor">@color/black</item>

+ 74 - 37
data/src/main/java/com/grkj/data/dao/JobTicketDao.kt

@@ -15,6 +15,7 @@ import com.grkj.data.model.dos.IsJobTicketLock
 import com.grkj.data.model.dos.IsJobTicketPoints
 import com.grkj.data.model.dos.IsJobTicketStep
 import com.grkj.data.model.dos.IsJobTicketUser
+import com.grkj.data.model.local.TodoStepJoin
 import com.grkj.data.model.vo.IsJobTicketDataVo
 import com.grkj.data.model.vo.IsJobTicketKeyDataVo
 import com.grkj.data.model.vo.IsJobTicketLockDataVo
@@ -26,6 +27,7 @@ import com.grkj.data.model.vo.JobPointVo
 import com.grkj.data.model.vo.JobTicketManageVo
 import com.grkj.data.model.vo.JobUserVo
 import com.grkj.data.model.vo.LockedPointVo
+import com.grkj.data.model.vo.TodoItemVo
 import com.sik.sikcore.date.TimeUtils
 
 /**
@@ -365,21 +367,6 @@ interface JobTicketDao {
         exStatusList: List<String>
     ): Int
 
-    /**
-     * 获取我的待办的作业数量
-     */
-    @Query(
-        """select count(DISTINCT ijt.ticket_id) from is_job_ticket ijt 
-            left join is_job_ticket_user ijtu on ijt.ticket_id = ijtu.ticket_id
-            where ijt.ticket_status in (1,2,3,4,7) and (:isAdmin = 1 OR  ex_status is NUll or ex_status NOT IN (:exStatusList)) and ijtu.user_id = :userId
-        """
-    )
-    fun getMyTodoJobSize(
-        isAdmin: Boolean,
-        exStatusList: List<String>,
-        userId: Long
-    ): Int
-
     /**
      * 获取所有作业数量
      */
@@ -674,26 +661,76 @@ interface JobTicketDao {
     /**
      * 我的待办列表
      */
-    @Query("""
-        select ijt.ticket_id as ticketId,
-        ijt.ticket_name as ticketName,
-        ijt.ticket_status as ticketStatus,
-        ijt.workstation_id as workstationId,
-        ijt.sop_id as sopId,
-        ijt.mode_id,
-        ijt.ex_status,
-        ijt.remark
-        from is_job_ticket ijt
-        left join is_job_ticket_user ijtu on ijt.ticket_id = ijtu.ticket_id
-        where ijt.ticket_status in (1,2,3,4,7) and (:isAdmin = 1 OR  ex_status is NUll or ex_status NOT IN (:exStatusList)) and ijtu.user_id = :userId
-        group by ijt.ticket_id
-        limit :size offset :offset
-    """)
-    fun getMyTodoJob(
-        isAdmin: Boolean,
-        exStatusList: List<String>,
-        userId: Long,
-        size: Int,
-        offset: Int
-    ): List<JobTicketManageVo>
+    @Query(
+        """
+            SELECT
+            js.step_id,
+            js.ticket_id,
+            js.step_index,
+            js.step_status,
+            js.workflow_step_id,
+            jt.ticket_name,
+            jt.create_by,
+            jt.ticket_start_time,
+            ws.enable_set_locker,
+            ws.enable_set_colocker,
+            ws.enable_lock,
+            ws.enable_colock,
+            ws.enable_release_colock,
+            ws.enable_unlock,
+            ws.enable_end_job,
+            ws.confirm_user,
+            ws.confirm_role_code,
+            ws.step_title,
+            ws.step_title_short,
+            ws.step_description,
+            ju.user_role,
+            ju.job_status
+        FROM is_job_ticket_step js
+        JOIN is_job_ticket jt  ON js.ticket_id = jt.ticket_id
+        JOIN is_workflow_step ws ON js.workflow_step_id = ws.step_id
+        LEFT JOIN is_job_ticket_user ju ON js.ticket_id = ju.ticket_id
+                                       AND ju.user_id  = :currentUserId
+                                       AND ju.del_flag = '0'
+        WHERE js.del_flag   = '0'
+          AND jt.del_flag   = '0'
+    """
+    )
+    fun getMyTodoList(currentUserId: Long): List<TodoStepJoin>
+
+    /**
+     * 获取所有用户参与的作业票
+     */
+    @Query(
+        """
+        SELECT DISTINCT ijt.*
+        FROM is_job_ticket ijt
+        LEFT JOIN sys_user su ON ijt.create_by = su.user_name
+        LEFT JOIN is_job_ticket_user ijtu ON ijtu.ticket_id = ijt.ticket_id
+        WHERE ijt.ticket_status IN ('1','2','3','4','7')
+          AND (
+                su.user_id = :userId         -- 是创建者
+             OR ijtu.user_id = :userId       -- 是参与人
+          )
+    """
+    )
+    fun getTicketThatUserJoin(userId: Long): List<IsJobTicket>
+
+    /**
+     * 根据作业票id获取步骤
+     */
+    @Query("select * from is_job_ticket_step where ticket_id in (:ticketIds)")
+    fun getStepsByTicketIds(ticketIds: List<Long>): List<IsJobTicketStep>
+
+    /**
+     * 根据作业票id获取钥匙
+     */
+    @Query("select * from is_job_ticket_key where ticket_id in (:ticketIds)")
+    fun getKeysByTicketIds(ticketIds: List<Long>): List<IsJobTicketKey>
+
+    /**
+     * 根据作业票id获取分组
+     */
+    @Query("select * from is_job_ticket_group where ticket_id in (:ticketId)")
+    fun getGroupsByTicketIds(ticketId: List<Long>): List<IsJobTicketGroup>
 }

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

@@ -28,6 +28,12 @@ interface WorkflowStepDao {
     @Query("SELECT * FROM is_workflow_step WHERE mode_id = :modeId ORDER BY step_index ASC")
     fun getStepsByMode(modeId: Long): List<WorkflowStep>
 
+    /**
+     * 根据流程模式id列表查询步骤列表
+     */
+    @Query("SELECT * FROM is_workflow_step WHERE mode_id IN (:modeIds)")
+    fun getStepsByModes(modeIds: List<Long>): List<WorkflowStep>
+
     /**
      * 获取下一个可用步骤序号
      */

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

@@ -92,4 +92,9 @@ object DictConstants {
      * 处理申请(作业)
      */
     const val KEY_EXCEPTION_PROCESS_APPLICATION_JOB = "exception_process_application_job"
+
+    /**
+     * 待办状态
+     */
+    const val KEY_TODO_STATUS: String = "todo_status"
 }

+ 6 - 0
data/src/main/java/com/grkj/data/enums/CommonDictDataEnum.kt

@@ -124,6 +124,12 @@ enum class CommonDictDataEnum(val dictKey: String, val commonDictRes: List<Commo
             SimpleCommonDictRes("申请结束作业", "1"),
         )
     ),
+    TODO_STATUS(
+        DictConstants.KEY_TODO_STATUS, listOf(
+            SimpleCommonDictRes("未处理", "0"),
+            SimpleCommonDictRes("已处理", "1"),
+        )
+    ),
     ;
 
     companion object {

+ 41 - 0
data/src/main/java/com/grkj/data/enums/OperationTypeEnum.kt

@@ -0,0 +1,41 @@
+package com.grkj.data.enums
+
+import com.grkj.data.model.local.TodoStepJoin
+import com.grkj.data.model.local.hasAnyHardwareOperationFunction
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+/**
+ * 操作类型枚举(用于标记作业票当前步骤所需的用户行为)
+ */
+enum class OperationTypeEnum(val code: String, val desc: String, val todoTitle: String) {
+    LOCK_TAKE_KEY("LOCK_TAKE_KEY", "取钥匙上锁", "启动上锁"),
+    LOCK_RETURN_KEY("LOCK_RETURN_KEY", "上锁还钥匙", "完成上锁"),
+    UNLOCK_TAKE_KEY("UNLOCK_TAKE_KEY", "取钥匙解锁", "启动解锁"),
+    UNLOCK_RETURN_KEY("UNLOCK_RETURN_KEY", "解锁还钥匙", "完成解锁"),
+    COLOCK("COLOCK", "待添加共锁", "添加共锁"),
+    RELEASE_COLOCK("RELEASE_COLOCK", "待解除共锁", "解除共锁"),
+    CONFIRM("CONFIRM", "步骤待确认", "执行确认"),
+    END("END", "作业结束待确认", "结束作业"),
+    UNKNOWN("UNKNOWN", "未知", "未知待办");
+
+    companion object {
+        /**
+         * 根据 WorkflowStep 和 IsJobTicketUser 构建 OperationTypeEnum
+         */
+        fun fromJoin(j: TodoStepJoin, isCreator: Boolean): OperationTypeEnum {
+            return when {
+                (j.enableSetLocker || j.enableSetColocker) && isCreator -> CONFIRM
+                j.enableLock && j.userRole == RoleEnum.JTLOCKER.roleKey && j.keyStatus == "0" -> LOCK_TAKE_KEY
+                j.enableLock && j.userRole == RoleEnum.JTLOCKER.roleKey && j.keyStatus == "1" -> LOCK_RETURN_KEY
+                j.enableUnlock && j.userRole == RoleEnum.JTLOCKER.roleKey && j.keyStatus == "0" -> UNLOCK_TAKE_KEY
+                j.enableUnlock && j.userRole == RoleEnum.JTLOCKER.roleKey && j.keyStatus == "1" -> UNLOCK_RETURN_KEY
+                j.enableColock && isCreator -> COLOCK
+                j.enableReleaseColock && isCreator -> RELEASE_COLOCK
+                j.enableEndJob && isCreator -> END
+                !j.hasAnyHardwareOperationFunction() -> CONFIRM
+                else -> UNKNOWN
+            }
+        }
+    }
+}

+ 1 - 1
data/src/main/java/com/grkj/data/model/dos/IsJobTicketKey.kt

@@ -35,7 +35,7 @@ open class IsJobTicketKey : BaseBean() {
     var keyStatus: String? = "0"
 	
 	@ColumnInfo("ticket_type")
-    var ticketType: Long? = null
+    var ticketType: Int? = null
 
     @ColumnInfo("del_flag")
     var delFlag: String? = "0"

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

@@ -31,6 +31,11 @@ open class IsJobTicketUser : BaseBean() {
     @ColumnInfo("user_role")
     var userRole: String? = null
 
+    /**
+     * 根据userRole是JTLOCKER还是JTCOLOCKER分开判断
+     * JTLOCKER: 0-待上锁 1-已上锁 2-已解锁
+     * JTCOLOCKER: 0-待共锁 1-已共锁 2-已解锁
+     */
     @ColumnInfo("job_status")
     var jobStatus: String? = "0"
 

+ 66 - 0
data/src/main/java/com/grkj/data/model/local/TodoStepJoin.kt

@@ -0,0 +1,66 @@
+package com.grkj.data.model.local
+
+import androidx.room.ColumnInfo
+import com.grkj.data.enums.RoleEnum
+
+data class TodoStepJoin(
+    // ----- 基础定位字段 -----
+    @ColumnInfo(name = "step_id") val stepId: Long,
+    @ColumnInfo(name = "ticket_id") val ticketId: Long,
+    @ColumnInfo(name = "step_index") val stepIndex: Int,
+    @ColumnInfo(name = "step_status") val stepStatus: String,
+    @ColumnInfo(name = "workflow_step_id") val workflowStepId: Long,
+
+    // ----- Ticket 信息 -----
+    @ColumnInfo(name = "ticket_name") val ticketName: String?,
+    @ColumnInfo(name = "create_by") val createBy: String?,
+    @ColumnInfo(name = "ticket_start_time") val ticketStartTime: String?,
+
+    // ----- WorkflowStep 信息(判定动作) -----
+    @ColumnInfo(name = "enable_set_locker") val enableSetLocker: Boolean,
+    @ColumnInfo(name = "enable_set_colocker") val enableSetColocker: Boolean,
+    @ColumnInfo(name = "enable_lock") val enableLock: Boolean,
+    @ColumnInfo(name = "enable_colock") val enableColock: Boolean,
+    @ColumnInfo(name = "enable_release_colock") val enableReleaseColock: Boolean,
+    @ColumnInfo(name = "enable_unlock") val enableUnlock: Boolean,
+    @ColumnInfo(name = "enable_end_job") val enableEndJob: Boolean,
+    @ColumnInfo(name = "confirm_user") val confirmUser: Long?,
+    @ColumnInfo(name = "confirm_role_code") val confirmRoleCode: String?,
+    @ColumnInfo(name = "step_title") val stepTitle: String?,
+    @ColumnInfo(name = "step_title_short") val stepTitleShort: String?,
+    @ColumnInfo(name = "step_description") val stepDescription: String?,
+
+    // ----- User 表(锁/共锁人员) -----
+    @ColumnInfo(name = "user_role") val userRole: String?,   // JTLOCKER/JTCOLOCKER
+    @ColumnInfo(name = "job_status") val jobStatus: String?,   // 0/1/2
+    @ColumnInfo(name = "key_status") val keyStatus: String?,   // 0/1/2 未取 已取 已还
+
+    @ColumnInfo(name = "group_id") var groupId: Long? = null,
+    @ColumnInfo(name = "group_name") var groupName: String? = null
+)
+
+// ============== 4️⃣ 权责判断 ==============
+fun TodoStepJoin.isMyTodo(currentUserId: Long, currentUserName: String): Boolean {
+    val isCreator = createBy == currentUserName
+
+    // ---- 判定锁/解锁 ----
+    if (enableLock && userRole == RoleEnum.JTLOCKER.roleKey && jobStatus == "0") return true
+    if (enableUnlock && userRole == RoleEnum.JTLOCKER.roleKey && jobStatus == "1") return true
+
+    // ---- 判定共锁/解除共锁(由创建者) ----
+    if (enableColock && isCreator) return true
+    if (enableReleaseColock && isCreator) return true
+
+    // ---- 判定结束作业(由创建者) ----
+    if (enableEndJob && isCreator) return true
+
+    // ---- 普通步骤确认 ----
+    val confirmToCurrentUser =
+        (confirmUser == currentUserId) || (confirmUser == null && confirmRoleCode == null && isCreator)
+    return !hasAnyHardwareOperationFunction() && confirmToCurrentUser
+}
+
+// WorkflowStep 扩展:判定是否包含任何硬件相关动作
+fun TodoStepJoin.hasAnyHardwareOperationFunction(): Boolean =
+    enableLock || enableColock || enableReleaseColock || enableUnlock
+

+ 94 - 0
data/src/main/java/com/grkj/data/model/vo/TodoItemVo.kt

@@ -0,0 +1,94 @@
+package com.grkj.data.model.vo
+
+import com.grkj.data.enums.OperationTypeEnum
+
+/**
+ * 作业票待办项实体类
+ */
+class TodoItemVo {
+
+    /**
+     * 待办项唯一 ID(主键)
+     */
+    var todoId: Long = 0L
+
+    /**
+     * 作业票 ID,对应 is_job_ticket.ticket_id
+     */
+    var ticketId: Long = 0L
+
+    /**
+     * 作业票名称
+     */
+    var ticketName: String? = ""
+
+    /**
+     * 当前步骤 ID,对应 is_job_ticket_step.step_id
+     */
+    var stepId: Long = 0L
+
+    /**
+     * 当前工作流步骤 ID(关联 is_workflow_step.step_id)
+     */
+    var workflowStepId: Long = 0L
+
+    /**
+     * 步骤索引
+     */
+    var stepIndex: Int = 0
+
+    /**
+     * 是否是当前步骤
+     */
+    var isCurrentStep: Boolean = false
+
+    /**
+     * 待办标题(推荐使用短标题)
+     */
+    var todoTitle: String? = ""
+
+    /**
+     * 待办内容说明(比如步骤说明/确认人要求)
+     */
+    var todoContent: String? = ""
+
+    /**
+     * 待办类型(如:LOCK、COLOCK、RELEASE_COLOCK、UNLOCK、CONFIRM、END)
+     */
+    var todoType: OperationTypeEnum = OperationTypeEnum.UNKNOWN
+
+    /**
+     * 当前步骤状态(0未执行 1已完成)
+     */
+    var stepStatus: String = "0"
+
+    /**
+     * 分组id
+     */
+    var groupId: Long? = null
+
+    /**
+     * 待办状态(待处理 待执行 已处理)
+     */
+    var todoStatus: String = ""
+
+    /**
+     * 是否已读
+     */
+    var isRead: Boolean = false
+
+    /**
+     * 是否置顶
+     */
+    var isPinned: Boolean = false
+
+    /**
+     * 创建时间(用于排序)
+     */
+    var createTime: String? = ""
+
+    /**
+     * 截止时间(当前业务暂不用,预留字段)
+     */
+    var dueTime: Long? = null
+}

+ 3 - 1
data/src/main/java/com/grkj/data/repository/IJobTicketRepository.kt

@@ -3,6 +3,7 @@ package com.grkj.data.repository
 import com.grkj.data.enums.NextJobPrompt
 import com.grkj.data.model.dos.IsJobTicket
 import com.grkj.data.model.dos.IsJobTicketStep
+import com.grkj.data.model.local.TodoStepJoin
 import com.grkj.data.model.req.LockPointUpdateReq
 import com.grkj.data.model.res.StepDetailRes
 import com.grkj.data.model.res.TicketDetailRes
@@ -17,6 +18,7 @@ import com.grkj.data.model.vo.JobTicketGroupDataVo
 import com.grkj.data.model.vo.JobTicketManageVo
 import com.grkj.data.model.vo.JobUserVo
 import com.grkj.data.model.vo.LockedPointVo
+import com.grkj.data.model.vo.TodoItemVo
 
 /**
  * 作业票仓储
@@ -312,5 +314,5 @@ interface IJobTicketRepository {
     /**
      * 获取我的待办列表
      */
-    fun getMyTodoList(current: Int, size: Int): Collection<JobTicketManageVo>
+    fun getMyTodoList(): List<TodoStepJoin>
 }

+ 2 - 4
data/src/main/java/com/grkj/data/repository/impl/network/NetworkJobTicketRepository.kt

@@ -3,6 +3,7 @@ package com.grkj.data.repository.impl.network
 import com.grkj.data.enums.NextJobPrompt
 import com.grkj.data.model.dos.IsJobTicket
 import com.grkj.data.model.dos.IsJobTicketStep
+import com.grkj.data.model.local.TodoStepJoin
 import com.grkj.data.model.req.LockPointUpdateReq
 import com.grkj.data.model.res.StepDetailRes
 import com.grkj.data.model.res.TicketDetailRes
@@ -254,10 +255,7 @@ class NetworkJobTicketRepository  @Inject constructor() : BaseRepository(), IJob
         TODO("Not yet implemented")
     }
 
-    override fun getMyTodoList(
-        current: Int,
-        size: Int
-    ): Collection<JobTicketManageVo> {
+    override fun getMyTodoList(): List<TodoStepJoin> {
         TODO("Not yet implemented")
     }
 

+ 178 - 35
data/src/main/java/com/grkj/data/repository/impl/standard/JobTicketRepository.kt

@@ -19,6 +19,9 @@ import com.grkj.data.model.dos.IsJobTicketLock
 import com.grkj.data.model.dos.IsJobTicketPoints
 import com.grkj.data.model.dos.IsJobTicketStep
 import com.grkj.data.model.dos.IsJobTicketUser
+import com.grkj.data.model.dos.WorkflowStep
+import com.grkj.data.model.local.TodoStepJoin
+import com.grkj.data.model.local.isMyTodo
 import com.grkj.data.model.req.LockPointUpdateReq
 import com.grkj.data.model.res.StepDetailRes
 import com.grkj.data.model.res.TicketDetailRes
@@ -231,43 +234,183 @@ class JobTicketRepository @Inject constructor(
         jobTicketDao.saveIsJobTicketStep(ticketStep)
     }
 
-    override fun getMyTodoList(
-        current: Int,
-        size: Int
-    ): List<JobTicketManageVo> {
-        val isAdmin = MainDomainData.roleKeys.orEmpty()
-            .contains(RoleEnum.ADMIN.roleKey)
-
-        // 普通用户要排除 "异常" 状态
-        val exStatusList = CommonDictDataEnum.JOB_TICKET_STATUS
-            .commonDictRes
-            .filter { it.dictLabel == "异常" }
-            .mapNotNull { it.dictValue }
-            .ifEmpty { listOf("") }  // 管理员也能传,但不会被用到
-        return jobTicketDao.getMyTodoJob(
-            isAdmin = isAdmin,
-            exStatusList = exStatusList,
-            userId = MainDomainData.userInfo?.userId ?: 0,
-            size = size,
-            offset = current * size
+    override fun getMyTodoList(): List<TodoStepJoin> {
+        val userId = MainDomainData.userInfo?.userId ?: return emptyList()
+        val userName = MainDomainData.userInfo?.userName ?: return emptyList()
+        //1.获取所有用户参与的作业票
+        val tickets = jobTicketDao.getTicketThatUserJoin(userId)
+        if (tickets.isEmpty()) return emptyList()
+        // 2. 获取票对应的流程步骤定义(模板)
+        val modeIdToSteps = workflowStepDao.getStepsByModes(tickets.map { it.modeId }.distinct())
+            .groupBy { it.modeId }
+        // 3. 获取作业票步骤实例
+        val stepInstances = jobTicketDao.getStepsByTicketIds(tickets.map { it.ticketId })
+        // 4. 获取钥匙记录(上锁/解锁 取/还钥匙逻辑)
+        val keyRecords = jobTicketDao.getKeysByTicketIds(tickets.map { it.ticketId })
+        // 5. 获取 ticket 所属分组
+        val groups = jobTicketDao.getGroupsByTicketIds(tickets.map { it.ticketId })
+        // 6. 构建待办项 TodoStepJoin
+        val todoList = assembleTodoStepJoins(
+            tickets, modeIdToSteps, stepInstances, keyRecords, groups, userId, userName
         )
+        // 7. 当前用户真正需要处理的
+        return todoList.filter { it.isMyTodo(userId, userName) }
     }
 
-    override fun getMyToDoListJobCount(): Int {
-        val isAdmin = MainDomainData.roleKeys.orEmpty()
-            .contains(RoleEnum.ADMIN.roleKey)
+    /**
+     * 组装数据
+     */
+    private fun assembleTodoStepJoins(
+        tickets: List<IsJobTicket>,
+        modeSteps: Map<Long, List<WorkflowStep>>,
+        stepInstances: List<IsJobTicketStep>,
+        keyRecords: List<IsJobTicketKey>,
+        groups: List<IsJobTicketGroup>,
+        userId: Long,
+        userName: String
+    ): List<TodoStepJoin> {
+        val result = mutableListOf<TodoStepJoin>()
 
-        // 普通用户要排除 "异常" 状态
-        val exStatusList = CommonDictDataEnum.JOB_TICKET_STATUS
-            .commonDictRes
-            .filter { it.dictLabel == "异常" }
-            .mapNotNull { it.dictValue }
-            .ifEmpty { listOf("") }  // 管理员也能传,但不会被用到
-        return jobTicketDao.getMyTodoJobSize(
-            isAdmin = isAdmin,
-            exStatusList = exStatusList,
-            userId = MainDomainData.userInfo?.userId ?: 0
-        )
+        tickets.forEach { ticket ->
+            val ticketId = ticket.ticketId
+            val groupList = groups.filter { it.ticketId == ticketId }
+            val ticketSteps = stepInstances.filter { it.ticketId == ticketId }
+            val workflowSteps = modeSteps[ticket.modeId] ?: emptyList()
+
+            ticketSteps.forEach { step ->
+                val workflowStep =
+                    workflowSteps.firstOrNull { it.stepId == step.workflowStepId } ?: return@forEach
+
+                when {
+                    // 🔐 JTLOCKER 处理:上锁 or 解锁 ➜ 必须按 group 拆
+                    workflowStep.enableLock || workflowStep.enableUnlock -> {
+                        val type = if (workflowStep.enableLock) 0 else 1
+                        val keys = keyRecords.filter {
+                            it.ticketId == ticketId && it.ticketType == type && it.delFlag == "0"
+                        }
+
+                        // 如果 key 记录没有,说明尚未开始取钥匙,按 groupId 拆分一条占位待办
+                        if (keys.isEmpty()) {
+                            groupList.forEach { group ->
+                                result += buildTodoStepJoin(
+                                    step,
+                                    ticket,
+                                    workflowStep,
+                                    group.id,
+                                    group.groupName,
+                                    null
+                                )
+                            }
+                        } else {
+                            keys.forEach { key ->
+                                result += buildTodoStepJoin(
+                                    step,
+                                    ticket,
+                                    workflowStep,
+                                    key.groupId,
+                                    key
+                                )
+                            }
+                        }
+                    }
+
+                    // 🤝 JTCOLOCKER / RELEASE_COLOCK(不拆 group) todo 目前不需要添加共锁
+                    workflowStep.enableColock || workflowStep.enableReleaseColock -> {
+//                        result += buildTodoStepJoin(step, ticket, workflowStep, null, null)
+                    }
+
+                    // ✅ 步骤确认 / 结束作业(不拆 group)
+                    workflowStep.enableEndJob || workflowStep.currentUserCanConfirm() -> {
+                        result += buildTodoStepJoin(step, ticket, workflowStep, null, null)
+                    }
+
+                    // 🚫 其他无效步骤忽略(或不在权限范围)
+                    else -> {
+                        // Do nothing
+                    }
+                }
+            }
+        }
+
+        return result
+    }
+
+
+    /**
+     * 构建待办项 TodoStepJoin
+     */
+    private fun buildTodoStepJoin(
+        step: IsJobTicketStep,
+        ticket: IsJobTicket,
+        workflowStep: WorkflowStep,
+        groupId: Long?,
+        groupName: Long?,
+        key: IsJobTicketKey?
+    ): TodoStepJoin {
+        return TodoStepJoin(
+            stepId = step.stepId,
+            ticketId = ticket.ticketId,
+            stepIndex = step.stepIndex,
+            stepStatus = step.stepStatus,
+            workflowStepId = workflowStep.stepId,
+
+            ticketName = ticket.ticketName,
+            createBy = ticket.createBy,
+            ticketStartTime = ticket.ticketStartTime,
+
+            enableSetLocker = workflowStep.enableSetLocker,
+            enableSetColocker = workflowStep.enableSetColocker,
+            enableLock = workflowStep.enableLock,
+            enableColock = workflowStep.enableColock,
+            enableReleaseColock = workflowStep.enableReleaseColock,
+            enableUnlock = workflowStep.enableUnlock,
+            enableEndJob = workflowStep.enableEndJob,
+            confirmUser = workflowStep.confirmUser,
+            confirmRoleCode = workflowStep.confirmRoleCode,
+            stepTitle = workflowStep.stepTitle,
+            stepTitleShort = workflowStep.stepTitleShort,
+            stepDescription = workflowStep.stepDescription,
+
+            userRole = getUserRole(workflowStep),
+            jobStatus = ticket.ticketStatus,
+            keyStatus = getKeyStatusFromKey(key),
+        ).also {
+            // 将 groupId 写入(你如果实体字段没写,需要自己加上)
+            it.groupId = groupId
+            it.groupId = groupId
+        }
+    }
+
+
+    /**
+     * 根据钥匙获取作业状态
+     */
+    private fun getKeyStatusFromKey(key: IsJobTicketKey?): String {
+        return when {
+            key == null -> "0"
+            key.giveBackTime != null -> "2" // 已还
+            key.collectTime != null -> "1" // 已取
+            else -> "0" // 未取
+        }
+    }
+
+    /**
+     * 根据用户组获取角色
+     */
+    private fun getUserRole(step: WorkflowStep): String? {
+        return when {
+            step.enableLock || step.enableUnlock -> RoleEnum.JTLOCKER.roleKey
+            step.enableColock || step.enableReleaseColock -> RoleEnum.JTCOLOCKER.roleKey
+            else -> null
+        }
+    }
+
+    override fun getMyToDoListJobCount(): Int {
+        return jobTicketDao.getMyTodoList(MainDomainData.userInfo?.userId ?: 0L).count {
+            it.isMyTodo(
+                MainDomainData.userInfo?.userId ?: 0L, MainDomainData.userInfo?.userName ?: ""
+            )
+        }
     }
 
     override fun updateCoincideToUnLock(ticketDetail: TicketDetailRes, groupId: Long) {
@@ -713,8 +856,8 @@ class JobTicketRepository @Inject constructor(
             workflowSteps.find { it.stepId == ticketSteps.firstOrNull { it.stepStatus == "0" }?.workflowStepId }
         val isJobTicketKey = BeanUtils.copyProperties(
             isJobTicketKeys.firstOrNull {
-                it.keyId == keyId && it.groupId == groupId && ((currentStep?.enableLock == true && it.ticketType == 0L) ||
-                        (currentStep?.enableUnlock == true && it.ticketType == 1L))
+                it.keyId == keyId && it.groupId == groupId && ((currentStep?.enableLock == true && it.ticketType == 0) ||
+                        (currentStep?.enableUnlock == true && it.ticketType == 1))
             },
             IsJobTicketKey::class.java
         )

+ 1 - 1
gradle/libs.versions.toml

@@ -11,7 +11,7 @@ material = "1.10.0"
 activity = "1.8.0"
 constraintlayout = "2.1.4"
 jetbrainsKotlinJvm = "2.0.21"
-sikextension = "1.1.59"
+sikextension = "1.1.60"
 sikcamera = "1.0.11"
 sikcronjob = "1.0.3"
 sikfontmanager = "1.0.2"

+ 11 - 6
ui-base/src/main/java/com/grkj/ui_base/business/BleBusinessManager.kt

@@ -8,6 +8,7 @@ import com.grkj.data.data.DictConstants
 import com.grkj.data.data.MainDomainData
 import com.grkj.data.di.RepositoryManager
 import com.grkj.data.enums.JobTicketStatusEnum
+import com.grkj.data.enums.RoleEnum
 import com.grkj.data.model.dos.IsJobTicketStep
 import com.grkj.data.model.local.DeviceTakeUpdate
 import com.grkj.data.model.local.UpdateKeyReturn
@@ -360,9 +361,11 @@ object BleBusinessManager {
                                 logger.info("当前步骤:${currentStep}")
                                 RepositoryManager.jobTicketRepo.getTicketDetail(itBO.ticketId) { ticketDetail ->
                                     logger.info("步骤详情:${ticketDetail}")
-                                    val role = ticketDetail?.ticketUserVOList?.find {
-                                        it.userId == MainDomainData.userInfo?.userId && currentStep?.currentUserCanConfirm() == true
-                                    }
+                                    val role =
+                                        ticketDetail?.ticketUserVOList?.filter { it.userRole == RoleEnum.JTLOCKER.roleKey }
+                                            ?.find {
+                                                it.userId == MainDomainData.userInfo?.userId
+                                            }
                                     if (role == null) {
                                         ThreadUtils.runOnMain {
                                             PopTip.build()
@@ -790,9 +793,11 @@ object BleBusinessManager {
                 if (ticketDetail == null) {
                     return@getTicketDetail
                 }
-                val role = ticketDetail.ticketUserVOList?.find {
-                    it.userId == MainDomainData.userInfo?.userId && currentStep?.currentUserCanConfirm() == true
-                }
+                val role =
+                    ticketDetail.ticketUserVOList?.filter { it.userRole == RoleEnum.JTLOCKER.roleKey }
+                        ?.find {
+                            it.userId == MainDomainData.userInfo?.userId
+                        }
                 if (role == null) {
                     PopTip.build().tip(R.string.you_are_not_locker_tip)
                     return@getTicketDetail

+ 3 - 3
ui-base/src/main/java/com/grkj/ui_base/utils/event/FlashTipEvent.kt

@@ -13,11 +13,11 @@ class FlashTipEvent(val msg: String? = null, val isShow: Boolean = false) {
          */
         @JvmStatic
         fun sendFlashTipEvent(msg: String? = null, isShow: Boolean = !msg.isNullOrEmpty()) {
-            val loadingEventBean = EventBean<FlashTipEvent>(
-                EventConstants.EVENT_LOADING_CODE,
+            val flashTipEventBean = EventBean(
+                EventConstants.EVENT_FLASH_TIP_CODE,
                 FlashTipEvent(msg, isShow)
             )
-            EventHelper.sendEvent(loadingEventBean)
+            EventHelper.sendEvent(flashTipEventBean)
         }
     }
 }

+ 0 - 7
ui-base/src/main/java/com/grkj/ui_base/widget/FlashingTipTextView.kt

@@ -16,13 +16,6 @@ class FlashingTipTextView @JvmOverloads constructor(
     private var flashing = false
     private var flashAnimation: AlphaAnimation? = null
 
-    init {
-        // 自动 post 启动动画
-        post {
-            startFlashing()
-        }
-    }
-
     private fun setupAnimation() {
         flashAnimation = AlphaAnimation(1.0f, 0.3f).apply {
             duration = flashDuration