From d6eb9bd3bdb52df7a531e8fdb917b7a3ad1f9265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=8B=A4=E6=B0=91?= Date: Sat, 18 Apr 2026 15:00:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(task):=20=E4=BC=98=E5=8C=96=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E5=88=97=E8=A1=A8=E5=92=8C=E5=AE=A1=E6=89=B9=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新TaskExtraItem数据结构,将index/id/processStatus/aiDescription字段 替换为taskId/desc/numberNo字段 - 在Service中新增pushToNext、recommendHandler、recommendBackNode、backTo 等工作流相关API接口 - 在ReviewActivity中实现完整的审批流程,包括同意和驳回操作 - 在TaskListActivity中实现可视区域任务数据同步和动态索引更新 - 优化语音识别的额外数据提供机制,支持当前可见任务列表的准确传递 - 添加工作流实例ID和活动ID的参数传递功能 --- .../brain/glass/model/data/RecognizeData.kt | 11 +- .../nova/brain/glass/repository/Service.kt | 24 ++- .../com/nova/brain/glass/ui/ReviewActivity.kt | 163 ++++++++++++++++-- .../nova/brain/glass/ui/TaskListActivity.kt | 76 ++++++-- 4 files changed, 244 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/nova/brain/glass/model/data/RecognizeData.kt b/app/src/main/java/com/nova/brain/glass/model/data/RecognizeData.kt index e9f69ac..82c231c 100644 --- a/app/src/main/java/com/nova/brain/glass/model/data/RecognizeData.kt +++ b/app/src/main/java/com/nova/brain/glass/model/data/RecognizeData.kt @@ -4,7 +4,7 @@ package com.nova.brain.glass.model.data // "text": "查看下C919第45架机的零部件采购情况", // "scence": "home", // "extra": [ -// { "index": 1, "id": "xxx", "taskType": "审核任务", "processStatus": "进行中", "aiDescription": "..." } +// { "taskId": "xxx", "taskType": "审核任务", "desc": "...", "numberNo": 1 } // ], // "actions": ["goToDecisionCenter"] //} @@ -17,9 +17,8 @@ data class RecognizeData( // list 场景下传给服务端,帮助 NLP 定位用户所说的任务 data class TaskExtraItem( - val index: Int, // 列表中的显示序号(1-based) - val id: String, + val taskId: String, val taskType: String, - val processStatus: String, - val aiDescription: String -) \ No newline at end of file + val desc: String, + val numberNo: Int // 当前可见列表中的显示序号(1-based) +) diff --git a/app/src/main/java/com/nova/brain/glass/repository/Service.kt b/app/src/main/java/com/nova/brain/glass/repository/Service.kt index 5d98ff9..6d842bc 100644 --- a/app/src/main/java/com/nova/brain/glass/repository/Service.kt +++ b/app/src/main/java/com/nova/brain/glass/repository/Service.kt @@ -2,7 +2,16 @@ package com.nova.brain.glass.repository import com.nova.brain.glass.model.RecognizeModel import com.nova.brain.glass.model.data.ChatData +import com.nova.brain.glass.model.data.ApiResponse +import com.nova.brain.glass.model.data.BackToData +import com.nova.brain.glass.model.data.BackToRequest import com.nova.brain.glass.model.data.RecognizeData +import com.nova.brain.glass.model.data.PushToNextData +import com.nova.brain.glass.model.data.PushToNextRequest +import com.nova.brain.glass.model.data.RecommendBackNodeData +import com.nova.brain.glass.model.data.RecommendBackNodeRequest +import com.nova.brain.glass.model.data.RecommendHandlerData +import com.nova.brain.glass.model.data.RecommendHandlerRequest import com.nova.brain.glass.model.data.TaskListData import com.nova.brain.glass.model.TaskSearchResponse import com.nova.brain.glass.model.data.TopicData @@ -12,6 +21,7 @@ import okhttp3.RequestBody import okhttp3.ResponseBody import retrofit2.http.Body import retrofit2.http.GET +import retrofit2.http.Headers import retrofit2.http.POST import retrofit2.http.Streaming @@ -36,4 +46,16 @@ interface Service { @POST("/cbrain-gateway/cbrain-task-server/cbrain-task/task/glassesTaskSearch") fun glassesTaskSearch(@Body body: TaskListData): Observable -} \ No newline at end of file + @POST("/cbrain-gateway/cbrain-execute/cbrain-execute/workflow/instance/pushToNext") + fun pushToNext(@Body body: PushToNextRequest): Observable> + + @POST("/cbrain-gateway/cbrain-execute/cbrain-execute/glasses/recommendHandler") + fun recommendHandler(@Body body: RecommendHandlerRequest): Observable> + + @POST("/cbrain-gateway/cbrain-execute/cbrain-execute/glasses/recommendBackNode") + fun recommendBackNode(@Body body: RecommendBackNodeRequest): Observable> + + @POST("/cbrain-gateway/cbrain-execute/cbrain-execute/workflow/instance/backTo") + fun backTo(@Body body: BackToRequest): Observable> + +} diff --git a/app/src/main/java/com/nova/brain/glass/ui/ReviewActivity.kt b/app/src/main/java/com/nova/brain/glass/ui/ReviewActivity.kt index 6812487..73ee12d 100644 --- a/app/src/main/java/com/nova/brain/glass/ui/ReviewActivity.kt +++ b/app/src/main/java/com/nova/brain/glass/ui/ReviewActivity.kt @@ -6,38 +6,63 @@ import com.nova.brain.glass.databinding.ActivityReviewBinding import com.nova.brain.glass.helper.OfflineCmdListener import com.nova.brain.glass.helper.OfflineCmdServiceHelper import com.nova.brain.glass.model.ItemItem +import com.nova.brain.glass.model.data.BackToRequest +import com.nova.brain.glass.model.data.PushToNextRequest +import com.nova.brain.glass.model.data.RecommendBackNodeRequest +import com.nova.brain.glass.model.data.RecommendHandlerRequest +import com.nova.brain.glass.repository.Service import com.nova.brain.glass.viewmodel.ItemListVM import com.xuqm.base.adapter.BasePagedAdapter import com.xuqm.base.adapter.CommonPagedAdapter import com.xuqm.base.adapter.ViewHolder +import com.xuqm.base.di.manager.HttpManager +import com.xuqm.base.extensions.showMessage import com.xuqm.base.ui.BaseListFormLayoutNormalActivity +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers class ReviewActivity : BaseListFormLayoutNormalActivity() { - override fun getLayoutId(): Int =R.layout.activity_review + override fun getLayoutId(): Int = R.layout.activity_review override fun fullscreen(): Boolean = true override fun getRecyclerOrientation(): Int = RecyclerView.HORIZONTAL + private var isSubmitting = false + private var submitDisposable: Disposable? = null + + private val workProcessInstanceId: String by lazy { + intent.getStringExtra("workProcessInstanceId").orEmpty() + } + + private val currentActivityId: String by lazy { + intent.getStringExtra("currentActivityId").orEmpty() + } + private val listener = object : OfflineCmdListener { override fun onOfflineCmd(cmd: String) { runOnUiThread { - when( cmd){ - "退出","返回","退回"->{ + when (cmd) { + "退出", "返回", "退回" -> { finish() } - "驳回","拒绝","不同意"->{ - finish() + "驳回", "拒绝", "不同意" -> { + submitBack() } - "同意","通过"->{ - finish() + "同意", "通过", "拟同意" -> { + submitAgree() } } } } } + override fun initData() { super.initData() window.addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) + binding.tvTaskHeader.text = intent.getStringExtra("taskType").orEmpty() + binding.content.text = intent.getStringExtra("content").orEmpty() } override fun onResume() { @@ -53,26 +78,140 @@ class ReviewActivity : BaseListFormLayoutNormalActivity + if (response.status != 200 || response.data == null) { + return@flatMap Observable.error( + IllegalStateException(if (response.msg.isBlank()) "同意失败" else response.msg) + ) + } + val needHandlerActivityIds = response.data.result.orEmpty().mapNotNull { it.activityId }.distinct() + if (needHandlerActivityIds.isEmpty()) { + Observable.just(emptyList()) + } else { + service.recommendHandler( + RecommendHandlerRequest( + workProcessInstanceId = workProcessInstanceId, + needHandlerActivityIds = needHandlerActivityIds + ) + ).flatMap { recommendResponse -> + if (recommendResponse.status != 200 || recommendResponse.data == null) { + return@flatMap Observable.error( + IllegalStateException( + if (recommendResponse.msg.isBlank()) "推荐处理人失败" else recommendResponse.msg + ) + ) + } + Observable.just(recommendResponse.data.selectedHandler.orEmpty()) + } + } + }.flatMap { selectedHandler -> + service.pushToNext( + PushToNextRequest( + workProcessInstanceId = workProcessInstanceId, + currentActivityId = currentActivityId, + needHandle = 0, + innerRequest = true, + comment = "拟同意", + selectedHandler = selectedHandler + ) + ) + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doFinally { isSubmitting = false } + .subscribe({ response -> + if (response.status == 200) { + "已同意".showMessage() + finish() + } else { + (if (response.msg.isBlank()) "同意失败" else response.msg).showMessage() + } + }, { e -> + (e.message ?: "同意失败").showMessage() + }) + } + + private fun submitBack() { + if (isSubmitting) return + if (workProcessInstanceId.isBlank() || currentActivityId.isBlank()) { + "缺少流程参数,无法回退".showMessage() + return + } + isSubmitting = true + submitDisposable?.dispose() + val service = HttpManager.getApi(Service::class.java) + submitDisposable = service.recommendBackNode( + RecommendBackNodeRequest( + workProcessInstanceId = workProcessInstanceId, + currentActivityId = currentActivityId + ) + ).flatMap { response -> + if (response.status != 200 || response.data?.backToActivityId.isNullOrBlank()) { + return@flatMap Observable.error( + IllegalStateException(if (response.msg.isBlank()) "推荐回退节点失败" else response.msg) + ) + } + service.backTo( + BackToRequest( + workProcessInstanceId = workProcessInstanceId, + currentActivityId = currentActivityId, + backToActivityId = response.data?.backToActivityId.orEmpty(), + comment = "驳回,请线下沟通原因" + ) + ) + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doFinally { isSubmitting = false } + .subscribe({ response -> + if (response.status == 200) { + "已回退".showMessage() + finish() + } else { + (if (response.msg.isBlank()) "回退失败" else response.msg).showMessage() + } + }, { e -> + (e.message ?: "回退失败").showMessage() + }) + } + private val adapter = object : CommonPagedAdapter(R.layout.item_item) { override fun convert(holder: ViewHolder, item: ItemItem, position: Int) { holder .setText(R.id.text, item.text) - .setClickListener(R.id.text - ) { - when(item.text){ + .setClickListener(R.id.text) { + when (item.text) { "同意" -> { - finish() + submitAgree() } "拒绝" -> { - finish() + submitBack() } } } } } + override fun adapter(): BasePagedAdapter = adapter } diff --git a/app/src/main/java/com/nova/brain/glass/ui/TaskListActivity.kt b/app/src/main/java/com/nova/brain/glass/ui/TaskListActivity.kt index 4e70363..fafbea1 100644 --- a/app/src/main/java/com/nova/brain/glass/ui/TaskListActivity.kt +++ b/app/src/main/java/com/nova/brain/glass/ui/TaskListActivity.kt @@ -26,6 +26,7 @@ class TaskListActivity : override fun getLayoutId(): Int = R.layout.activity_task_list override fun fullscreen(): Boolean = true private var pageStartPosition = 0 + private var visibleTaskExtras: List = emptyList() companion object { private val EXIT_CMDS = setOf("退出", "返回", "退回", "关闭") @@ -41,6 +42,15 @@ class TaskListActivity : } } }) + adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { + override fun onChanged() { + binding.baseRecyclerView.post { refreshVisibleOrder() } + } + + override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { + binding.baseRecyclerView.post { refreshVisibleOrder() } + } + }) binding.baseRecyclerView.post { refreshVisibleOrder() } @@ -100,7 +110,9 @@ class TaskListActivity : private fun refreshVisibleOrder() { val lm = binding.baseRecyclerView.layoutManager as? LinearLayoutManager ?: return val firstVisible = lm.findFirstVisibleItemPosition() - if (firstVisible == RecyclerView.NO_POSITION || firstVisible == pageStartPosition) return + if (firstVisible == RecyclerView.NO_POSITION) return + updateVisibleTaskExtras(lm, firstVisible) + if (firstVisible == pageStartPosition) return pageStartPosition = firstVisible adapter.notifyDataSetChanged() } @@ -121,6 +133,20 @@ class TaskListActivity : Intent(this, ReviewActivity::class.java) .putExtra("taskId", item.id) .putExtra("taskType", item.taskType) + .putExtra("content", item.aiDescription) + .putExtra( + "workProcessInstanceId", + item.params.firstNotBlank("processInstanceId", "work_process_instance_id") + ) + .putExtra( + "currentActivityId", + item.params.firstNotBlank( + "currentActivityId", + "current_activity_id", + "activityId", + "activity_id" + ) + ) ) else -> Log.d("TaskListActivity", "unknown taskType: ${item.taskType}") } @@ -132,15 +158,7 @@ class TaskListActivity : OfflineCmdServiceHelper.addOnLineListener(offlineCmdListener) AsrHelper.scene = "list" AsrHelper.extraProvider = { - viewModel.currentItems.mapIndexed { i, item -> - TaskExtraItem( - index = i + 1, - id = item.id, - taskType = item.taskType, - processStatus = item.processStatus, - aiDescription = item.aiDescription - ) - } + if (visibleTaskExtras.isNotEmpty()) visibleTaskExtras else buildTaskExtra(viewModel.currentItems) } AsrHelper.onGoToDecisionCenter = { action -> Log.d("TaskListActivity", "recognize result: $action") @@ -166,6 +184,42 @@ class TaskListActivity : AsrHelper.onOpenTaskDetail = null } + private fun updateVisibleTaskExtras( + lm: LinearLayoutManager, + firstVisible: Int = lm.findFirstVisibleItemPosition() + ) { + val items = viewModel.currentItems + if (items.isEmpty()) { + visibleTaskExtras = emptyList() + return + } + val lastVisible = lm.findLastVisibleItemPosition() + if (firstVisible == RecyclerView.NO_POSITION || lastVisible == RecyclerView.NO_POSITION) { + visibleTaskExtras = buildTaskExtra(items) + return + } + val safeFirst = firstVisible.coerceAtLeast(0).coerceAtMost(items.lastIndex) + val safeLast = lastVisible.coerceAtLeast(0).coerceAtMost(items.lastIndex) + visibleTaskExtras = if (safeFirst <= safeLast) { + buildTaskExtra(items.subList(safeFirst, safeLast + 1)) + } else { + buildTaskExtra(items) + } + } + + private fun buildTaskExtra(items: List): List = + items.mapIndexed { index, item -> + TaskExtraItem( + taskId = item.id, + taskType = item.taskType, + desc = item.aiDescription, + numberNo = index + 1 + ) + } + + private fun Map.firstNotBlank(vararg keys: String): String = + keys.firstNotNullOfOrNull { key -> this[key]?.takeIf { it.isNotBlank() } }.orEmpty() + private val adapter = object : CommonPagedAdapter(R.layout.item_task_list) { override fun convert(holder: ViewHolder, item: TaskItem, position: Int) { val displayPosition = (position - pageStartPosition + 1).coerceAtLeast(1) @@ -181,4 +235,4 @@ class TaskListActivity : super.onDestroy() window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) } -} \ No newline at end of file +}