feat(task): 优化任务列表和审批功能
- 更新TaskExtraItem数据结构,将index/id/processStatus/aiDescription字段 替换为taskId/desc/numberNo字段 - 在Service中新增pushToNext、recommendHandler、recommendBackNode、backTo 等工作流相关API接口 - 在ReviewActivity中实现完整的审批流程,包括同意和驳回操作 - 在TaskListActivity中实现可视区域任务数据同步和动态索引更新 - 优化语音识别的额外数据提供机制,支持当前可见任务列表的准确传递 - 添加工作流实例ID和活动ID的参数传递功能
这个提交包含在:
父节点
f20255947c
当前提交
d6eb9bd3bd
@ -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
|
||||
val desc: String,
|
||||
val numberNo: Int // 当前可见列表中的显示序号(1-based)
|
||||
)
|
||||
@ -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<TaskSearchResponse>
|
||||
|
||||
@POST("/cbrain-gateway/cbrain-execute/cbrain-execute/workflow/instance/pushToNext")
|
||||
fun pushToNext(@Body body: PushToNextRequest): Observable<ApiResponse<PushToNextData>>
|
||||
|
||||
@POST("/cbrain-gateway/cbrain-execute/cbrain-execute/glasses/recommendHandler")
|
||||
fun recommendHandler(@Body body: RecommendHandlerRequest): Observable<ApiResponse<RecommendHandlerData>>
|
||||
|
||||
@POST("/cbrain-gateway/cbrain-execute/cbrain-execute/glasses/recommendBackNode")
|
||||
fun recommendBackNode(@Body body: RecommendBackNodeRequest): Observable<ApiResponse<RecommendBackNodeData>>
|
||||
|
||||
@POST("/cbrain-gateway/cbrain-execute/cbrain-execute/workflow/instance/backTo")
|
||||
fun backTo(@Body body: BackToRequest): Observable<ApiResponse<BackToData>>
|
||||
|
||||
}
|
||||
@ -6,11 +6,22 @@ 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<ItemItem, ItemListVM, ActivityReviewBinding>() {
|
||||
override fun getLayoutId(): Int = R.layout.activity_review
|
||||
@ -18,6 +29,17 @@ class ReviewActivity : BaseListFormLayoutNormalActivity<ItemItem, ItemListVM, Ac
|
||||
|
||||
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 {
|
||||
@ -26,18 +48,21 @@ class ReviewActivity : BaseListFormLayoutNormalActivity<ItemItem, ItemListVM, Ac
|
||||
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<ItemItem, ItemListVM, Ac
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
submitDisposable?.dispose()
|
||||
super.onDestroy()
|
||||
window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
}
|
||||
|
||||
private fun submitAgree() {
|
||||
if (isSubmitting) return
|
||||
if (workProcessInstanceId.isBlank() || currentActivityId.isBlank()) {
|
||||
"缺少流程参数,无法同意".showMessage()
|
||||
return
|
||||
}
|
||||
isSubmitting = true
|
||||
submitDisposable?.dispose()
|
||||
val service = HttpManager.getApi(Service::class.java)
|
||||
submitDisposable = service.pushToNext(
|
||||
PushToNextRequest(
|
||||
workProcessInstanceId = workProcessInstanceId,
|
||||
currentActivityId = currentActivityId,
|
||||
needHandle = 1,
|
||||
innerRequest = true
|
||||
)
|
||||
).flatMap { response ->
|
||||
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<ItemItem>(R.layout.item_item) {
|
||||
override fun convert(holder: ViewHolder, item: ItemItem, position: Int) {
|
||||
holder
|
||||
.setText(R.id.text, item.text)
|
||||
.setClickListener(R.id.text
|
||||
) {
|
||||
.setClickListener(R.id.text) {
|
||||
when (item.text) {
|
||||
"同意" -> {
|
||||
finish()
|
||||
submitAgree()
|
||||
}
|
||||
"拒绝" -> {
|
||||
finish()
|
||||
submitBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun adapter(): BasePagedAdapter<ItemItem> = adapter
|
||||
}
|
||||
|
||||
@ -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<TaskExtraItem> = 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<TaskItem>): List<TaskExtraItem> =
|
||||
items.mapIndexed { index, item ->
|
||||
TaskExtraItem(
|
||||
taskId = item.id,
|
||||
taskType = item.taskType,
|
||||
desc = item.aiDescription,
|
||||
numberNo = index + 1
|
||||
)
|
||||
}
|
||||
|
||||
private fun Map<String, String>.firstNotBlank(vararg keys: String): String =
|
||||
keys.firstNotNullOfOrNull { key -> this[key]?.takeIf { it.isNotBlank() } }.orEmpty()
|
||||
|
||||
private val adapter = object : CommonPagedAdapter<TaskItem>(R.layout.item_task_list) {
|
||||
override fun convert(holder: ViewHolder, item: TaskItem, position: Int) {
|
||||
val displayPosition = (position - pageStartPosition + 1).coerceAtLeast(1)
|
||||
|
||||
正在加载...
在新工单中引用
屏蔽一个用户