소스 검색

1. 首页作业列表和任务列表刷新机制优化,解决其他页面通知首页刷新数据,数据不发生变化的问题
2. 作业详情页面逻辑开发完成,补充了当前任务进度
3. 未发布的作业支持通过作业详情右上角的编辑进行编辑和发布操作
4. 解决拆除隔离节点的保存信息丢失的问题

bjb 4 달 전
부모
커밋
d9797c33d1
37개의 변경된 파일764개의 추가작업 그리고 497개의 파일을 삭제
  1. 1 1
      .idea/deploymentTargetSelector.xml
  2. 78 19
      app/src/main/java/com/iscs/bozzys/api/ApiBean.kt
  3. 3 3
      app/src/main/java/com/iscs/bozzys/api/ApiRequest.kt
  4. 11 1
      app/src/main/java/com/iscs/bozzys/api/ApiService.kt
  5. 3 0
      app/src/main/java/com/iscs/bozzys/event/Event.kt
  6. 24 2
      app/src/main/java/com/iscs/bozzys/ui/common/Title.kt
  7. 15 2
      app/src/main/java/com/iscs/bozzys/ui/pages/compose/FormCompose.kt
  8. 2 26
      app/src/main/java/com/iscs/bozzys/ui/pages/compose/JobListItem.kt
  9. 12 4
      app/src/main/java/com/iscs/bozzys/ui/pages/create/job/PageCreateJob.kt
  10. 259 402
      app/src/main/java/com/iscs/bozzys/ui/pages/detail/job/PageDetailJob.kt
  11. 7 7
      app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/PageEditStep.kt
  12. 40 4
      app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/compose/NodeUI.kt
  13. 1 1
      app/src/main/java/com/iscs/bozzys/ui/pages/home/JobsCompose.kt
  14. 1 1
      app/src/main/java/com/iscs/bozzys/ui/pages/home/PageHome.kt
  15. 1 2
      app/src/main/java/com/iscs/bozzys/ui/pages/home/SettingsCompose.kt
  16. 1 1
      app/src/main/java/com/iscs/bozzys/ui/pages/select/job/PageSelectJobType.kt
  17. 19 5
      app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMCreateJob.kt
  18. 118 0
      app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMDetailJob.kt
  19. 9 1
      app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMEditStep.kt
  20. 11 4
      app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMHome.kt
  21. 1 0
      app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMPushJob.kt
  22. 2 1
      app/src/main/java/com/iscs/bozzys/ui/theme/Theme.kt
  23. 15 0
      app/src/main/res/drawable/category.xml
  24. 12 0
      app/src/main/res/drawable/content.xml
  25. 12 0
      app/src/main/res/drawable/edit.xml
  26. 9 0
      app/src/main/res/drawable/node_icon_complete.xml
  27. 9 0
      app/src/main/res/drawable/node_icon_confirm.xml
  28. 9 0
      app/src/main/res/drawable/node_icon_create_job.xml
  29. 9 0
      app/src/main/res/drawable/node_icon_input_info.xml
  30. 9 0
      app/src/main/res/drawable/node_icon_isolation.xml
  31. 9 0
      app/src/main/res/drawable/node_icon_release_isolation.xml
  32. 9 0
      app/src/main/res/drawable/node_icon_return_lock.xml
  33. 9 0
      app/src/main/res/drawable/node_icon_review.xml
  34. 2 8
      app/src/main/res/drawable/number.xml
  35. 12 0
      app/src/main/res/drawable/progress.xml
  36. 5 2
      app/src/main/res/drawable/target.xml
  37. 15 0
      app/src/main/res/drawable/template.xml

+ 1 - 1
.idea/deploymentTargetSelector.xml

@@ -4,7 +4,7 @@
     <selectionStates>
       <SelectionState runConfigName="app">
         <option name="selectionMode" value="DROPDOWN" />
-        <DropdownSelection timestamp="2026-01-04T03:19:43.493054700Z">
+        <DropdownSelection timestamp="2026-01-07T07:43:33.274121Z">
           <Target type="DEFAULT_BOOT">
             <handle>
               <DeviceId pluginId="PhysicalDevice" identifier="serial=d8d12db95670c08" />

+ 78 - 19
app/src/main/java/com/iscs/bozzys/api/ApiBean.kt

@@ -3,7 +3,9 @@ package com.iscs.bozzys.api
 import android.util.Log
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
 import com.google.gson.Gson
+import com.iscs.bozzys.R
 import com.iscs.bozzys.ui.pages.edit.step.compose.Anchor
 import com.iscs.bozzys.ui.pages.edit.step.compose.Connection
 import com.iscs.bozzys.ui.pages.edit.step.compose.NodeUI
@@ -197,6 +199,11 @@ data class Node(
                     postfix = "]",
                     separator = ","
                 )
+                if (type == "releaseIsolation") {
+                    val isolationNode = forms.find { it.name == "isolationNode" }?.value?.getOrNull(0)
+                    // 解除隔离,这里需要填写关联的节点uuid
+                    params["isolationNodeUuid"] = isolationNode
+                }
             }
         }
         return params
@@ -490,6 +497,32 @@ data class Node(
         return list
     }
 
+    /**
+     * 获取node图标,暂时本地通过type获取
+     */
+    fun getIcon(): Int {
+        // Log.d("xiaoming", "节点类型:$type")
+        return when (type) {
+            // 开始/创建
+            "createJob" -> R.drawable.node_icon_create_job
+            // 审核
+            "review" -> R.drawable.node_icon_review
+            // 确认
+            "confirm" -> R.drawable.node_icon_confirm
+            // 录入信息
+            "inputInfo" -> R.drawable.node_icon_input_info
+            // 隔离
+            "isolation" -> R.drawable.node_icon_isolation
+            // 解除隔离
+            "releaseIsolation" -> R.drawable.node_icon_release_isolation
+            // 还锁
+            "returnLock" -> R.drawable.node_icon_return_lock
+            // 结束
+            "complete" -> R.drawable.node_icon_complete
+            else -> R.drawable.tips
+        }
+    }
+
 }
 
 /**
@@ -511,33 +544,35 @@ data class TaskFormInfo(
  */
 @Serializable
 data class Job(
-    val id: Int,                        // 作业Id
-    val orderNo: String,                // 作业编号
-    val name: String,                   // 作业名称
-    val type: String,                   // 作业类型
-    val urgencyLevel: String,           // 紧急等级
-    val designId: Int,                  // sopId
-    val designContent: String,          // sopInfo
-    val description: String,            // 描述
-    val initiatorId: Int,               // 创建者id
-    val initiatorName: String,          // 创建者名称
-    val initiatorTime: Long,            // 创建者创建时间
-    val status: String,                 // 作业状态
-    val createTime: Long,               // 创建时间
+    val id: Int = -1,                        // 作业Id
+    val orderNo: String = "",                // 作业编号
+    val name: String = "",                   // 作业名称
+    val type: String = "",                   // 作业类型
+    val typeName: String = "",               // 作业类型名称,服务端不返回,本地使用
+    val urgencyLevel: String = "",           // 紧急等级
+    val designId: Int = -1,                  // sopId
+    val designName: String = "",             // 设计模板
+    val designContent: String? = null,       // sopInfo
+    val description: String = "",            // 描述
+    val initiatorId: Int = -1,               // 创建者id
+    val initiatorName: String = "",          // 创建者名称
+    val initiatorTime: Long = -1L,           // 创建者创建时间
+    val status: String = "",                 // 作业状态
+    val createTime: Long = -1L,              // 创建时间
     val currentNodeId: String? = null,
     val currentNodeName: String? = null,
     val completionTime: Long? = null,
     val cancellationTime: Long? = null,
     val cancellationReason: String? = null,
-    val workflowWorkNodeDOList: List<Node> = listOf()
-) {
+    val workflowWorkNodeDOList: List<Node>? = null
+) : java.io.Serializable {
     /**
      * 将接口返回的node转换为可显示的Node
      */
     fun toUINodeMap(): SnapshotStateMap<String, NodeUI> {
         val json = Json { ignoreUnknownKeys = true }
         val map = SnapshotStateMap<String, NodeUI>()
-        workflowWorkNodeDOList.forEach {
+        workflowWorkNodeDOList?.forEach {
             val pos = json.decodeFromString<NodePositon>(it.position)
             map[it.id.toString()] = NodeUI(it.id.toString(), Offset(pos.x, pos.y), node = it)
         }
@@ -564,18 +599,42 @@ data class Job(
     fun toUIConnectList(): List<Connection> {
         val connections = ArrayList<Connection>()
         val json = Json { ignoreUnknownKeys = true }
-        val nodeInfo: NodeInfo = json.decodeFromString(designContent.ifEmpty { "{}" })
+        val nodeInfo: NodeInfo = json.decodeFromString(designContent?.ifEmpty { "{}" } ?: "{}")
         nodeInfo.edges.forEach {
             val fromUuid = it.target
             val toUuid = it.source
-            val fromId = workflowWorkNodeDOList.find { node -> node.uuid == fromUuid }?.id ?: 0
+            val fromId = workflowWorkNodeDOList?.find { node -> node.uuid == fromUuid }?.id ?: 0
             val fromAnchor = it.targetHandle.toAnchor()
-            val toId = workflowWorkNodeDOList.find { node -> node.uuid == toUuid }?.id ?: 0
+            val toId = workflowWorkNodeDOList?.find { node -> node.uuid == toUuid }?.id ?: 0
             val toAnchor = it.sourceHandle.toAnchor()
             connections.add(Connection(fromId.toString(), toId.toString(), fromAnchor, toAnchor))
         }
         return connections
     }
+
+    /**
+     * 获取等级名称和背景色
+     */
+    fun getLevelNameAndColor(): Pair<String, Color> {
+        return when (urgencyLevel) {
+            "1" -> "紧急" to Color(0xFFFF9800)
+            "2" -> "非常紧急" to Color(0xFFFF4500)
+            else -> "正常" to Color(0xFF1E90FF)
+        }
+    }
+
+    /**
+     * 获取任务状态名称
+     */
+    fun getJobStatusName(): String {
+        return when (status) {
+            "unreleased" -> "待发布"
+            "approved" -> "已审核"
+            "running" -> "进行中"
+            "completed" -> "已完成"
+            else -> status
+        }
+    }
 }
 
 /**

+ 3 - 3
app/src/main/java/com/iscs/bozzys/api/ApiRequest.kt

@@ -197,12 +197,12 @@ object ApiRequest {
     }
 
     /**
-     * 创建作业
+     * 创建或编辑作业
      *
      * @param params 请求参数
      */
-    suspend fun createJob(params: MutableMap<String, Any>): Result<Response<Int>> {
-        return requestApi { api.createJob(params) }
+    suspend fun createOrEditJob(id: Int, params: MutableMap<String, Any>): Result<Response<Any>> {
+        return requestApi { if (id <= 0) api.createJob(params) else api.editJob(params.apply { put("id", id) }) }
     }
 
     /**

+ 11 - 1
app/src/main/java/com/iscs/bozzys/api/ApiService.kt

@@ -131,7 +131,17 @@ interface ApiService {
     suspend fun createJob(
         @Body body: MutableMap<String, Any>,
         @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
-    ): Response<Int>
+    ): Response<Any>
+
+    /**
+     * 编辑作业
+     */
+    @Headers("Content-Type: application/json")
+    @PUT("/admin-api/iscs/workflow-work/updateWorkflowWork")
+    suspend fun editJob(
+        @Body body: MutableMap<String, Any>,
+        @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
+    ): Response<Any>
 
     /**
      * 发布作业

+ 3 - 0
app/src/main/java/com/iscs/bozzys/event/Event.kt

@@ -43,4 +43,7 @@ sealed class RefreshEvent(var any: Any = Any()) {
 
     // 更新地图位置到顶部居中
     object UpdateNodeToMapTopCenter : RefreshEvent()
+
+    // 刷新Job详情数据
+    object JobDetail : RefreshEvent()
 }

+ 24 - 2
app/src/main/java/com/iscs/bozzys/ui/common/Title.kt

@@ -1,6 +1,7 @@
 package com.iscs.bozzys.ui.common
 
 import android.app.Activity
+import androidx.annotation.DrawableRes
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
@@ -28,8 +29,16 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import com.iscs.bozzys.R
 
+/**
+ * 通用标题
+ *
+ * @param pv            页面顶部和底部占位
+ * @param rightShow     右边按钮是否显示
+ * @param rightIcon     右边icon
+ * @param rightClick    右边点击事件
+ */
 @Composable
-fun Title(pv: PaddingValues, title: String) {
+fun Title(pv: PaddingValues, title: String, rightShow: Boolean = false, @DrawableRes rightIcon: Int = 0, rightClick: () -> Unit = {}) {
     val ctx = LocalContext.current
     Column(
         Modifier
@@ -66,7 +75,20 @@ fun Title(pv: PaddingValues, title: String) {
                 textAlign = TextAlign.Center,
                 color = Color.White
             )
-            Spacer(Modifier.size(40.dp))
+            if (rightShow) {
+                Icon(
+                    painter = painterResource(rightIcon),
+                    contentDescription = null,
+                    modifier = Modifier
+                        .size(40.dp)
+                        .clip(RoundedCornerShape(20))
+                        .clickable(onClick = rightClick)
+                        .padding(8.dp),
+                    tint = Color.White
+                )
+            } else {
+                Spacer(Modifier.size(40.dp))
+            }
         }
     }
 }

+ 15 - 2
app/src/main/java/com/iscs/bozzys/ui/pages/compose/FormCompose.kt

@@ -54,6 +54,7 @@ import androidx.compose.ui.unit.sp
 import com.iscs.bozzys.R
 import com.iscs.bozzys.api.FormField
 import com.iscs.bozzys.api.FormOption
+import com.iscs.bozzys.api.Job
 import com.iscs.bozzys.ui.theme.Main
 import com.iscs.bozzys.ui.theme.Text
 import com.iscs.bozzys.utils.DateUtil.dateToTimestamp
@@ -918,7 +919,7 @@ fun List<String>.getFormListByJsonList(): List<FormField> {
 /**
  * 转换表单列表
  */
-fun String.getFormListByJsonList(): List<FormField> {
+fun String.getFormListByJsonList(job: Job?): List<FormField> {
     val json = Json { ignoreUnknownKeys = true }
     val fields = json.decodeFromString<List<FormField>>(this)
     val list = ArrayList<FormField>()
@@ -926,7 +927,19 @@ fun String.getFormListByJsonList(): List<FormField> {
     fun List<FormField>.forEach() {
         this.forEach { ch ->
             if (ch.children.isNotEmpty()) ch.children.forEach()
-            else list.add(ch)
+            else {
+                job?.let {
+                    // 默认赋值操作
+                    when (ch.id) {
+                        "job_name" -> ch.value = listOf(it.name)
+                        "job_sop" -> ch.value = listOf(it.designId.toString())
+                        "job_type" -> ch.value = listOf(it.type)
+                        "job_content" -> ch.value = listOf(it.description)
+                        "job_urgency_level" -> ch.value = listOf(it.urgencyLevel)
+                    }
+                }
+                list.add(ch)
+            }
         }
     }
 

+ 2 - 26
app/src/main/java/com/iscs/bozzys/ui/pages/compose/JobListItem.kt

@@ -32,7 +32,6 @@ import com.iscs.bozzys.R
 import com.iscs.bozzys.api.Job
 import com.iscs.bozzys.ui.pages.detail.job.openPageDetailJob
 import com.iscs.bozzys.ui.theme.Text
-import com.iscs.bozzys.utils.DateUtil.getShowDate
 import com.iscs.bozzys.utils.DateUtil.getShowDateOrTime
 
 @OptIn(ExperimentalLayoutApi::class)
@@ -40,7 +39,7 @@ import com.iscs.bozzys.utils.DateUtil.getShowDateOrTime
 fun JobListItem(job: Job) {
     val ctx = LocalContext.current
     // 获取等级名称和颜色
-    val levelInfo = getLevelNameAndColor(job.urgencyLevel)
+    val levelInfo = job.getLevelNameAndColor()
     CardContainer(
         modifier = Modifier
             .padding(vertical = 5.dp)
@@ -51,7 +50,7 @@ fun JobListItem(job: Job) {
         Column(
             Modifier
                 .fillMaxWidth()
-                .clickable(onClick = { ctx.openPageDetailJob() })
+                .clickable(onClick = { ctx.openPageDetailJob(job.id) })
                 .padding(16.dp)
         ) {
             Row {
@@ -144,26 +143,3 @@ fun JobListItem(job: Job) {
     }
 }
 
-/**
- * 获取等级名称和背景色
- */
-private fun getLevelNameAndColor(level: String): Pair<String, Color> {
-    return when (level) {
-        "1" -> "紧急" to Color(0xFFFF9800)
-        "2" -> "非常紧急" to Color(0xFFFF4500)
-        else -> "正常" to Color(0xFF1E90FF)
-    }
-}
-
-/**
- * 获取任务状态名称
- */
-fun getJobStatusName(approvalStatus: String): String {
-    return when (approvalStatus) {
-        "approved" -> "已审核"
-        "running" -> "未审核"
-        "pending" -> "待处理"
-        else -> approvalStatus
-    }
-}
-

+ 12 - 4
app/src/main/java/com/iscs/bozzys/ui/pages/create/job/PageCreateJob.kt

@@ -32,6 +32,8 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.iscs.bozzys.R
+import com.iscs.bozzys.event.RefreshEvent
+import com.iscs.bozzys.event.RefreshEventBus
 import com.iscs.bozzys.ui.common.PageBase
 import com.iscs.bozzys.ui.common.Title
 import com.iscs.bozzys.ui.pages.compose.CardContainer
@@ -39,27 +41,30 @@ import com.iscs.bozzys.ui.pages.compose.FormContainer
 import com.iscs.bozzys.ui.pages.edit.step.openPageEditStep
 import com.iscs.bozzys.ui.pages.vm.VMCreateJob
 
-fun Context.openPageCreateJob() {
-    startActivity(Intent(this, PageCreateJob::class.java))
+fun Context.openPageCreateJob(id: Int) {
+    startActivity(Intent(this, PageCreateJob::class.java).apply {
+        putExtra("id", id)
+    })
 }
 
 class PageCreateJob : PageBase() {
 
     @Composable
     override fun GetViews(pv: PaddingValues) {
+        val id = intent.getIntExtra("id", 0)
         val vm: VMCreateJob = viewModel()
         val state by vm.state.collectAsState()
         LaunchedEffect(Unit) {
             vm.toast.showToast()
             vm.loading.showLoading()
-            vm.init()
+            vm.init(id)
         }
         Column(
             Modifier
                 .fillMaxSize()
                 .background(Color(0xFFF8F9FA))
         ) {
-            Title(pv, "新建作业")
+            Title(pv, if (id > 0) "编辑作业" else "新建作业")
             Column(
                 modifier = Modifier
                     .weight(1f)
@@ -84,6 +89,9 @@ class PageCreateJob : PageBase() {
                 Button(
                     onClick = {
                         vm.onNext {
+                            // 通知首页刷新数据
+                            RefreshEventBus.onRefreshData(RefreshEvent.JobDetail)
+                            RefreshEventBus.onRefreshData(RefreshEvent.HomeData)
                             openPageEditStep(it)
                             destroyDelay(500)
                         }

+ 259 - 402
app/src/main/java/com/iscs/bozzys/ui/pages/detail/job/PageDetailJob.kt

@@ -2,10 +2,10 @@ package com.iscs.bozzys.ui.pages.detail.job
 
 import android.content.Context
 import android.content.Intent
+import androidx.annotation.DrawableRes
 import androidx.compose.foundation.background
-import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.FlowRow
 import androidx.compose.foundation.layout.PaddingValues
@@ -20,38 +20,43 @@ import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.foundation.text.BasicTextField
-import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.Icon
-import androidx.compose.material3.LocalTextStyle
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.layout.FirstBaseline
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import androidx.lifecycle.viewmodel.compose.viewModel
 import com.iscs.bozzys.R
 import com.iscs.bozzys.ui.common.PageBase
 import com.iscs.bozzys.ui.common.Title
 import com.iscs.bozzys.ui.pages.compose.CardContainer
-import com.iscs.bozzys.ui.theme.Main
+import com.iscs.bozzys.ui.pages.create.job.openPageCreateJob
+import com.iscs.bozzys.ui.pages.edit.step.openPageEditStep
+import com.iscs.bozzys.ui.pages.vm.VMDetailJob
 import com.iscs.bozzys.ui.theme.Text
+import com.iscs.bozzys.utils.DateUtil.getShowDateOrTime
 
 /**
  * 跳转到作业详情页面
  */
-fun Context.openPageDetailJob() {
-    startActivity(Intent(this, PageDetailJob::class.java))
+fun Context.openPageDetailJob(id: Int) {
+    startActivity(Intent(this, PageDetailJob::class.java).apply {
+        // 传递序列化的对象
+        putExtra("id", id)
+    })
 }
 
 /**
@@ -61,416 +66,268 @@ class PageDetailJob : PageBase() {
 
     @Composable
     override fun GetViews(pv: PaddingValues) {
-        val pb = pv.calculateBottomPadding()
-        Column(
-            modifier = Modifier
-                .background(Color(0xFFF8F8F8))
-        ) {
-            Title(pv, "作业详情")
+        val id = intent.getIntExtra("id", 0)
+        val vm: VMDetailJob = viewModel()
+        val state by vm.state.collectAsState()
+        // val pb = pv.calculateBottomPadding()
+        LaunchedEffect(Unit) {
+            vm.toast.showToast()
+            vm.loading.showLoading()
+            vm.init(id)
+        }
+        Column(modifier = Modifier.background(Color(0xFFF8F8F8))) {
+            Title(
+                pv,
+                state.job.name,
+                rightShow = state.job.id > 0 && state.job.status == "unreleased",
+                rightIcon = R.drawable.edit,
+                rightClick = { openPageCreateJob(state.job.id) })
+            // 作业信息完整再加载
+            if (state.job.id > 0) {
+                // 作业基本信息
+                JobInfo(vm)
+                // 任务进度
+                JobProgress(vm)
+            }
             Column(
                 modifier = Modifier
                     .weight(1f)
                     .verticalScroll(state = rememberScrollState())
                     .padding(16.dp)
             ) {
-                CardContainer(
-                    modifier = Modifier
-                        .fillMaxWidth()
-                ) {
-                    Column(Modifier.padding(10.dp)) {
-                        Row(verticalAlignment = Alignment.CenterVertically) {
-                            Icon(
-                                painterResource(R.drawable.jobs),
-                                contentDescription = null,
-                                modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
-                                tint = MaterialTheme.colorScheme.primary
-                            )
-                            Text("审核作业", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
-                        }
-                        FlowRow(
-                            verticalArrangement = Arrangement.Center, modifier = Modifier
-                                .padding(top = 6.dp)
-                                .offset(x = (-3).dp)
-                        ) {
-                            Row(
-                                modifier = Modifier
-                                    .padding(horizontal = 3.dp, vertical = 3.dp)
-                                    .clip(RoundedCornerShape(50))
-                                    .background(Color(0xFFFFF8E6))
-                                    .padding(horizontal = 10.dp, vertical = 2.dp),
-                                verticalAlignment = Alignment.CenterVertically
-                            ) {
-                                Icon(
-                                    painterResource(R.drawable.number),
-                                    contentDescription = null,
-                                    tint = MaterialTheme.colorScheme.primary,
-                                    modifier = Modifier
-                                        .padding(end = 3.dp)
-                                        .size(14.dp)
-                                )
-                                Text("WO-2025-003", fontSize = 12.sp, color = Text)
-                            }
-                            Row(
-                                modifier = Modifier
-                                    .padding(horizontal = 3.dp, vertical = 3.dp)
-                                    .clip(RoundedCornerShape(50))
-                                    .background(Color(0xFFFFF8E6))
-                                    .padding(horizontal = 10.dp, vertical = 2.dp),
-                                verticalAlignment = Alignment.CenterVertically
-                            ) {
-                                Icon(
-                                    painterResource(R.drawable.location),
-                                    contentDescription = null,
-                                    tint = MaterialTheme.colorScheme.primary,
-                                    modifier = Modifier
-                                        .padding(end = 3.dp)
-                                        .size(14.dp)
-                                )
-                                Text("车间A区", fontSize = 12.sp, color = Text)
-                            }
-                            Row(
-                                modifier = Modifier
-                                    .padding(horizontal = 3.dp, vertical = 3.dp)
-                                    .clip(RoundedCornerShape(50))
-                                    .background(Color(0xFFFFF8E6))
-                                    .padding(horizontal = 10.dp, vertical = 2.dp),
-                                verticalAlignment = Alignment.CenterVertically
-                            ) {
-                                Icon(
-                                    painterResource(R.drawable.user),
-                                    contentDescription = null,
-                                    tint = MaterialTheme.colorScheme.primary,
-                                    modifier = Modifier
-                                        .padding(end = 3.dp)
-                                        .size(14.dp)
-                                )
-                                Text("张三", fontSize = 12.sp, color = Text)
-                            }
-                            Row(
-                                modifier = Modifier
-                                    .padding(horizontal = 3.dp, vertical = 3.dp)
-                                    .clip(RoundedCornerShape(50))
-                                    .background(Color(0xFFFFF8E6))
-                                    .padding(horizontal = 10.dp, vertical = 2.dp),
-                                verticalAlignment = Alignment.CenterVertically
-                            ) {
-                                Icon(
-                                    painterResource(R.drawable.target),
-                                    contentDescription = null,
-                                    tint = MaterialTheme.colorScheme.primary,
-                                    modifier = Modifier
-                                        .padding(end = 3.dp)
-                                        .size(14.dp)
-                                )
-                                Text("待审核", fontSize = 12.sp, color = Text)
-                            }
-                            Row(
-                                modifier = Modifier
-                                    .padding(horizontal = 3.dp, vertical = 3.dp)
-                                    .clip(RoundedCornerShape(50))
-                                    .background(Color(0xFFFFF8E6))
-                                    .padding(horizontal = 10.dp, vertical = 2.dp),
-                                verticalAlignment = Alignment.CenterVertically
-                            ) {
-                                Icon(
-                                    painterResource(R.drawable.job_todo),
-                                    contentDescription = null,
-                                    tint = MaterialTheme.colorScheme.primary,
-                                    modifier = Modifier
-                                        .padding(end = 3.dp)
-                                        .size(14.dp)
-                                )
-                                Text("8小时", fontSize = 12.sp, color = Text)
-                            }
-                        }
-                    }
-                }
-                // 检修计划方案
-                CardContainer(
-                    modifier = Modifier
-                        .padding(top = 16.dp)
-                        .fillMaxWidth()
-                ) {
-                    Column(Modifier.padding(10.dp)) {
-                        Row(verticalAlignment = Alignment.CenterVertically) {
-                            Icon(
-                                painterResource(R.drawable.job_check),
-                                contentDescription = null,
-                                modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
-                                tint = MaterialTheme.colorScheme.primary
-                            )
-                            Text("检修计划方案", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
-                        }
-                        Spacer(
-                            Modifier
-                                .padding(vertical = 10.dp)
-                                .fillMaxWidth()
-                                .height(1.dp)
-                                .background(Color(0xFFF5F5F5))
-                        )
-                        Text("本次检修主要针对三号线履带传送线分装设备进行日常维护保养,包括:", fontSize = 14.sp, color = Color(0xFF666666))
-                    }
+
+            }
+            // 底部功能按钮
+//            Row(
+//                modifier = Modifier
+//                    .padding(bottom = if (pb.value <= 0) 10.dp else pb, top = 10.dp)
+//                    .padding(horizontal = 24.dp)
+//                    .fillMaxWidth()
+//            ) {
+//                Button(
+//                    {}, modifier = Modifier
+//                        .weight(1f)
+//                        .height(50.dp)
+//                        .clip(RoundedCornerShape(12.dp)),
+//                    colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFFF0E6)),
+//                    shape = RoundedCornerShape(12.dp)
+//                ) {
+//                    Row(verticalAlignment = Alignment.CenterVertically) {
+//                        Icon(
+//                            painter = painterResource(R.drawable.job_close),
+//                            contentDescription = null,
+//                            modifier = Modifier
+//                                .padding(end = 5.dp)
+//                                .size(16.dp),
+//                            tint = Color(0xFFFF4D4F)
+//                        )
+//                        Text("审核不通过", color = Color(0xFFFF4D4F))
+//                    }
+//                }
+//                Spacer(Modifier.width(16.dp))
+//                Button(
+//                    {}, modifier = Modifier
+//                        .weight(1f)
+//                        .height(50.dp)
+//                        .clip(RoundedCornerShape(12.dp)),
+//                    shape = RoundedCornerShape(12.dp)
+//                ) {
+//                    Row(verticalAlignment = Alignment.CenterVertically) {
+//                        Icon(
+//                            painter = painterResource(R.drawable.job_finish),
+//                            contentDescription = null,
+//                            modifier = Modifier
+//                                .padding(end = 5.dp)
+//                                .size(16.dp),
+//                            tint = Color.White
+//                        )
+//                        Text("审核通过", color = Color.White)
+//                    }
+//                }
+//            }
+        }
+    }
+
+    /**
+     * 作业的基本信息
+     */
+    @Composable
+    fun JobInfo(vm: VMDetailJob) {
+        val state by vm.state.collectAsState()
+        CardContainer(
+            modifier = Modifier
+                .padding(16.dp)
+                .fillMaxWidth()
+        ) {
+            Column(Modifier.padding(16.dp)) {
+                Row(verticalAlignment = Alignment.CenterVertically) {
+                    Icon(
+                        painterResource(R.drawable.jobs),
+                        contentDescription = null,
+                        modifier = Modifier
+                            .padding(end = 5.dp)
+                            .size(20.dp),
+                        tint = MaterialTheme.colorScheme.primary
+                    )
+                    Text("作业信息", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
                 }
-                // 核心风险点
-                CardContainer(
+                // 分割线
+                Spacer(
                     modifier = Modifier
-                        .padding(top = 16.dp)
+                        .padding(vertical = 10.dp)
                         .fillMaxWidth()
-                ) {
-                    Column(Modifier.padding(10.dp)) {
-                        Row(verticalAlignment = Alignment.CenterVertically) {
-                            Icon(
-                                painterResource(R.drawable.job_warning),
-                                contentDescription = null,
-                                modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
-                                tint = MaterialTheme.colorScheme.primary
-                            )
-                            Text("核心风险点", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
-                        }
-                        Spacer(
-                            Modifier
-                                .padding(vertical = 10.dp)
-                                .fillMaxWidth()
-                                .height(1.dp)
-                                .background(Color(0xFFF5F5F5))
-                        )
-                        Box(
-                            Modifier
-                                .padding(bottom = 6.dp)
-                                .clip(RoundedCornerShape(4.dp))
-                                .background(Color(0xFFFF4D4F))
-                        ) {
-                            Row(
-                                Modifier
-                                    .offset(x = 3.dp)
-                                    .clip(RoundedCornerShape(2.dp))
-                                    .background(Color(0xFFFFF0E6))
-                                    .padding(horizontal = 8.dp, vertical = 5.dp)
-                            ) {
-                                Text("1.", color = Color(0xFFFF4D4F), fontSize = 14.sp, modifier = Modifier.padding(end = 6.dp))
-                                Text(
-                                    "机械伤害风险:设备运行时可能造成人员伤害",
-                                    fontSize = 14.sp,
-                                    color = Color(0xFF666666),
-                                    modifier = Modifier.weight(1f)
-                                )
-                            }
-                        }
-                        Box(
-                            Modifier
-                                .padding(bottom = 6.dp)
-                                .clip(RoundedCornerShape(4.dp))
-                                .background(Color(0xFFFF4D4F))
-                        ) {
-                            Row(
-                                Modifier
-                                    .offset(x = 3.dp)
-                                    .clip(RoundedCornerShape(2.dp))
-                                    .background(Color(0xFFFFF0E6))
-                                    .padding(horizontal = 8.dp, vertical = 5.dp)
-                            ) {
-                                Text("2.", color = Color(0xFFFF4D4F), fontSize = 14.sp, modifier = Modifier.padding(end = 6.dp))
-                                Text(
-                                    "电气安全风险:设备断电不彻底可能导致触电",
-                                    fontSize = 14.sp,
-                                    color = Color(0xFF666666),
-                                    modifier = Modifier.weight(1f)
-                                )
-                            }
-                        }
-                        Box(
-                            Modifier
-                                .padding(bottom = 6.dp)
-                                .clip(RoundedCornerShape(4.dp))
-                                .background(Color(0xFFFF4D4F))
-                        ) {
-                            Row(
-                                Modifier
-                                    .offset(x = 3.dp)
-                                    .clip(RoundedCornerShape(2.dp))
-                                    .background(Color(0xFFFFF0E6))
-                                    .padding(horizontal = 8.dp, vertical = 5.dp)
-                            ) {
-                                Text("3.", color = Color(0xFFFF4D4F), fontSize = 14.sp, modifier = Modifier.padding(end = 6.dp))
-                                Text(
-                                    "高处作业风险:部分检修位置需要登高作业",
-                                    fontSize = 14.sp,
-                                    color = Color(0xFF666666),
-                                    modifier = Modifier.weight(1f)
-                                )
-                            }
-                        }
-                    }
+                        .height(1.dp)
+                        .background(Color.Black.copy(alpha = 0.1f))
+                )
+                FlowRow(verticalArrangement = Arrangement.Center) {
+                    // 作业编号
+                    JobInfoItem(R.drawable.number, "作业编号", state.job.orderNo)
+                    // 作业负责人
+                    JobInfoItem(R.drawable.user, "负责人", state.job.initiatorName)
+                    // 作业紧急程度
+                    JobInfoItem(R.drawable.job_warning, "紧急程度", state.job.getLevelNameAndColor().first)
+                    // 作业状态
+                    JobInfoItem(R.drawable.target, "作业状态", state.job.getJobStatusName())
+                    // 车间岗位
+                    // JobInfoItem(R.drawable.location,"车间A区")
+                    // 作业类型
+                    JobInfoItem(R.drawable.category, "作业类型", state.job.typeName.ifEmpty { "--" })
+                    // 作业模板
+                    JobInfoItem(R.drawable.template, "作业模板", state.job.designName)
+                    // 作业创建时间
+                    JobInfoItem(R.drawable.job_todo, "创建时间", state.job.createTime.getShowDateOrTime())
+                    // 作业内容/描述
+                    if (state.job.description.isNotEmpty()) JobInfoItem(R.drawable.content, "作业内容", state.job.description)
                 }
-                // 相关资料附件
-                CardContainer(
-                    modifier = Modifier
-                        .padding(top = 16.dp)
-                        .fillMaxWidth()
-                ) {
-                    Column(Modifier.padding(10.dp)) {
-                        Row(verticalAlignment = Alignment.CenterVertically) {
-                            Icon(
-                                painterResource(R.drawable.job_attach),
-                                contentDescription = null,
-                                modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
-                                tint = MaterialTheme.colorScheme.primary
-                            )
-                            Text("相关资料附件", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
-                        }
-                        Spacer(
-                            Modifier
-                                .padding(vertical = 10.dp)
-                                .fillMaxWidth()
-                                .height(1.dp)
-                                .background(Color(0xFFF5F5F5))
-                        )
-                        Row(
-                            verticalAlignment = Alignment.CenterVertically,
-                            modifier = Modifier
-                                .padding(bottom = 6.dp)
-                                .fillMaxWidth()
-                                .border(1.dp, color = Color(0xFFEEEEEE), shape = RoundedCornerShape(12.dp))
-                                .clip(RoundedCornerShape(12.dp))
-                                .background(Color(0xFFF8F8F8))
-                                .padding(10.dp)
-                        ) {
-                            Icon(
-                                painterResource(R.drawable.file_pdf),
-                                contentDescription = null,
-                                modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
-                                tint = MaterialTheme.colorScheme.primary
-                            )
-                            Text("检修方案.pdf", color = Text, fontSize = 14.sp)
-                        }
-                        Row(
-                            verticalAlignment = Alignment.CenterVertically,
-                            modifier = Modifier
-                                .padding(bottom = 6.dp)
-                                .fillMaxWidth()
-                                .border(1.dp, color = Color(0xFFEEEEEE), shape = RoundedCornerShape(12.dp))
-                                .clip(RoundedCornerShape(12.dp))
-                                .background(Color(0xFFF8F8F8))
-                                .padding(10.dp)
-                        ) {
-                            Icon(
-                                painterResource(R.drawable.file_jpg),
-                                contentDescription = null,
-                                modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
-                                tint = MaterialTheme.colorScheme.primary
-                            )
-                            Text("设备位置图.jpg", color = Text, fontSize = 14.sp)
-                        }
-                    }
+            }
+        }
+    }
+
+    /**
+     * 作业信息item
+     *
+     * @param fraction  item占比宽度
+     */
+    @Composable
+    fun JobInfoItem(@DrawableRes icon: Int, title: String, value: String, fraction: Float = 1f) {
+        Row(
+            modifier = Modifier
+                .fillMaxWidth(fraction)
+                .padding(bottom = 5.dp),
+            verticalAlignment = Alignment.Top
+        ) {
+            Icon(
+                painterResource(icon),
+                contentDescription = null,
+                tint = MaterialTheme.colorScheme.primary,
+                modifier = Modifier
+                    .padding(end = 3.dp)
+                    .size(18.dp)
+                    .padding(1.dp)
+                    .offset(y = 4.dp)
+            )
+            Text(
+                title,
+                fontSize = 14.sp,
+                color = Text,
+                modifier = Modifier
+                    .width(65.dp)
+                    .heightIn(min = 18.dp)
+                    .alignBy(FirstBaseline)
+            )
+            Text(
+                value,
+                fontSize = 14.sp,
+                color = Text.copy(alpha = 0.6f),
+                modifier = Modifier
+                    .weight(1f)
+                    .heightIn(min = 18.dp)
+                    .alignBy(FirstBaseline)
+            )
+        }
+    }
+
+    /**
+     * 任务的当前进度
+     */
+    @Composable
+    fun JobProgress(vm: VMDetailJob) {
+        val state by vm.state.collectAsState()
+        CardContainer(
+            modifier = Modifier
+                .padding(16.dp)
+                .fillMaxWidth()
+                .offset(y = (-16).dp)
+        ) {
+            Column(Modifier.padding(16.dp)) {
+                Row(verticalAlignment = Alignment.CenterVertically) {
+                    Icon(
+                        painterResource(R.drawable.progress),
+                        contentDescription = null,
+                        modifier = Modifier
+                            .padding(end = 5.dp)
+                            .size(20.dp),
+                        tint = MaterialTheme.colorScheme.primary
+                    )
+                    Text("作业进度", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
+                    Spacer(modifier = Modifier.weight(1f))
+                    // 只有待发布的时候不显示
+                    if (state.job.status != "unreleased") Text(
+                        "查看详情",
+                        modifier = Modifier
+                            .height(26.dp)
+                            .clip(RoundedCornerShape(4.dp))
+                            .clickable { openPageEditStep(state.job.id) }
+                            .background(Color(0xFF1E90FF))
+                            .padding(horizontal = 6.dp),
+                        fontSize = 14.sp,
+                        lineHeight = 26.sp,
+                        fontWeight = FontWeight.Bold,
+                        color = Color.White,
+                        textAlign = TextAlign.Center
+                    )
                 }
-                // 审核意见
-                CardContainer(
+                // 分割线
+                Spacer(
                     modifier = Modifier
-                        .padding(top = 16.dp)
+                        .padding(vertical = 10.dp)
                         .fillMaxWidth()
-                ) {
-                    Column(Modifier.padding(10.dp)) {
-                        Row(verticalAlignment = Alignment.CenterVertically) {
-                            Icon(
-                                painterResource(R.drawable.message),
-                                contentDescription = null,
-                                modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
-                                tint = MaterialTheme.colorScheme.primary
-                            )
-                            Text("审核意见", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
-                        }
-                        Spacer(
-                            Modifier
-                                .padding(vertical = 10.dp)
-                                .fillMaxWidth()
-                                .height(1.dp)
-                                .background(Color(0xFFF5F5F5))
-                        )
-                        BasicTextField(
-                            "",
-                            onValueChange = {},
-                            Modifier
-                                .fillMaxWidth()
-                                .heightIn(min = 120.dp)
-                                .border(1.dp, color = Color(0xFFEEEEEE), shape = RoundedCornerShape(12.dp))
-                                .clip(RoundedCornerShape(12.dp))
-                                .background(Color(0xFFF8F8F8))
-                                .padding(10.dp),
-                            textStyle = LocalTextStyle.current.copy(fontSize = 14.sp, lineHeight = 18.sp),
-                            decorationBox = { innerTextField ->
-                                Box(contentAlignment = Alignment.TopStart) {
-                                    innerTextField()
-                                    if ("".isEmpty()) {
-                                        val text = "请输入审核意见"
-                                        Text(text, color = Color(0xFF9CA3AF), fontSize = 14.sp, lineHeight = 18.sp)
-                                    }
-                                }
-                            },
-                            cursorBrush = SolidColor(Main),
-                            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
-                        )
-                    }
-                }
-            }
-            // 底部功能按钮
-            Row(
-                modifier = Modifier
-                    .padding(bottom = if (pb.value <= 0) 10.dp else pb, top = 10.dp)
-                    .padding(horizontal = 24.dp)
-                    .fillMaxWidth()
-            ) {
-                Button(
-                    {}, modifier = Modifier
-                        .weight(1f)
-                        .height(50.dp)
-                        .clip(RoundedCornerShape(12.dp)),
-                    colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFFF0E6)),
-                    shape = RoundedCornerShape(12.dp)
-                ) {
-                    Row(verticalAlignment = Alignment.CenterVertically) {
+                        .height(1.dp)
+                        .background(Color.Black.copy(alpha = 0.1f))
+                )
+                // 做当前节点和下一个节点显示
+                Row {
+                    Row(
+                        verticalAlignment = Alignment.CenterVertically, modifier = Modifier
+                            .padding(vertical = 5.dp)
+                            .weight(1f)
+                    ) {
                         Icon(
-                            painter = painterResource(R.drawable.job_close),
+                            painter = painterResource(R.drawable.job_ing),
                             contentDescription = null,
-                            modifier = Modifier
-                                .padding(end = 5.dp)
-                                .size(16.dp),
-                            tint = Color(0xFFFF4D4F)
+                            modifier = Modifier.size(32.dp),
+                            tint = MaterialTheme.colorScheme.primary
                         )
-                        Text("审核不通过", color = Color(0xFFFF4D4F))
+                        Column(modifier = Modifier.padding(horizontal = 10.dp)) {
+                            Text("当前执行", fontSize = 15.sp, lineHeight = 15.sp, color = Text)
+                            Text(state.currentNodeName, fontSize = 14.sp, lineHeight = 14.sp, color = Text.copy(alpha = 0.6f))
+                        }
                     }
-                }
-                Spacer(Modifier.width(16.dp))
-                Button(
-                    {}, modifier = Modifier
-                        .weight(1f)
-                        .height(50.dp)
-                        .clip(RoundedCornerShape(12.dp)),
-                    shape = RoundedCornerShape(12.dp)
-                ) {
-                    Row(verticalAlignment = Alignment.CenterVertically) {
+                    if (state.nextNodeName.isNotEmpty()) Row(
+                        verticalAlignment = Alignment.CenterVertically, modifier = Modifier
+                            .padding(vertical = 5.dp)
+                            .weight(1f)
+                    ) {
                         Icon(
-                            painter = painterResource(R.drawable.job_finish),
+                            painter = painterResource(R.drawable.job_todo),
                             contentDescription = null,
-                            modifier = Modifier
-                                .padding(end = 5.dp)
-                                .size(16.dp),
-                            tint = Color.White
+                            modifier = Modifier.size(32.dp),
+                            tint = Color.Gray
                         )
-                        Text("审核通过", color = Color.White)
+                        Column(modifier = Modifier.padding(horizontal = 10.dp)) {
+                            Text("即将执行", fontSize = 15.sp, lineHeight = 15.sp, color = Text)
+                            Text(state.nextNodeName, fontSize = 14.sp, lineHeight = 14.sp, color = Text.copy(alpha = 0.6f))
+                        }
                     }
                 }
             }

+ 7 - 7
app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/PageEditStep.kt

@@ -73,10 +73,10 @@ import kotlinx.coroutines.launch
 /**
  * 跳转到编辑流程页面
  */
-fun Context.openPageEditStep(jobId: Int) {
+fun Context.openPageEditStep(id: Int) {
     // 传递作业票id进入页面
     startActivity(Intent(this, PageEditStep::class.java).apply {
-        putExtra("job_id", jobId)
+        putExtra("id", id)
     })
 }
 
@@ -88,7 +88,7 @@ class PageEditStep : PageBase() {
         LaunchedEffect(Unit) {
             vm.toast.showToast()
             vm.loading.showLoading()
-            vm.init(intent.getIntExtra("job_id", 0))
+            vm.init(intent.getIntExtra("id", 0))
         }
         // 页面布局容器
         Column(Modifier.fillMaxSize()) {
@@ -147,12 +147,12 @@ class PageEditStep : PageBase() {
                                         .imePadding()
                                         .padding(vertical = 5.dp)
                                 ) {
-                                    item { FormContainer(state.nodeForms, { vm.onNodeFormChanged(it) }) }
+                                    item { FormContainer(state.nodeForms, { vm.onNodeFormChanged(it) }, enabled = state.enableEdit) }
                                 }
                             }
                             if (imeBottom <= 0) {
                                 Spacer(Modifier.height(10.dp))
-                                Button(
+                                if (state.enableEdit) Button(
                                     {
                                         vm.onNodeSave {
                                             // 优化体验,这里如果保存成功,自动检测是否需要进行下一步操作
@@ -190,7 +190,7 @@ class PageEditStep : PageBase() {
                 }
             }
             // 底部下一步按钮
-            Column(
+            if (state.enableEdit) Column(
                 Modifier
                     .fillMaxWidth()
                     .background(MaterialTheme.colorScheme.background)
@@ -241,7 +241,7 @@ class PageEditStep : PageBase() {
             vm.hideTipsDialog()
         }, onConfirm = {
             vm.hideTipsDialog()
-            openPagePushJob(intent.getIntExtra("job_id", 0))
+            openPagePushJob(intent.getIntExtra("id", 0))
             // 这里需要跳转到下一个页面,且关闭当前页面
             destroyDelay(500)
         })

+ 40 - 4
app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/compose/NodeUI.kt

@@ -1,5 +1,11 @@
 package com.iscs.bozzys.ui.pages.edit.step.compose
 
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
@@ -16,6 +22,7 @@ import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
@@ -93,8 +100,37 @@ fun NodeItem(
     onNodeClick: (NodeUI) -> Unit,
     modifier: Modifier = Modifier
 ) {
+    // 当前item的节点
     val node = nodes[id] ?: return
     val isSelect = selectNodeUI.value.id == node.id
+    // 是否已完成的节点
+    val finished = node.node?.approvalStatus == "approved"
+    // 是否进行中的节点
+    val running = node.node?.approvalStatus == "running"
+    // 处理卡片阴影颜色
+    val shadowColor = if (isSelect) {
+        MaterialTheme.colorScheme.primary.copy(alpha = 0.25f)
+    } else {
+        // 比对当前节点是否已完成,已完成显示绿色
+        if (running) Color(0xFF3B82F6).copy(alpha = 0.25f)
+        else if (finished) Color(0xFF00C950).copy(alpha = 0.25f)
+        else Color.Black.copy(alpha = 0.15f)
+    }
+    // 处理卡片边框颜色
+    val colorBorder = if (isSelect) {
+        MaterialTheme.colorScheme.primary
+    } else {
+        if (running) Color(0xFF3B82F6)
+        else if (finished) Color(0xFF00C950)
+        else Color.White
+    }
+    // 处理当前进行中的节点闪烁提示
+    val infiniteTransition = rememberInfiniteTransition()
+    val alpha by infiniteTransition.animateFloat(
+        initialValue = 0.2f,
+        targetValue = 0.8f,
+        animationSpec = infiniteRepeatable(animation = tween(durationMillis = 1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse)
+    )
     CardContainer(
         modifier = modifier
             .size(node.baseSize.width.dp, node.baseSize.height.dp)
@@ -108,21 +144,21 @@ fun NodeItem(
                     node = node.node
                 )
             },
-        shadowColor = if (isSelect) MaterialTheme.colorScheme.primary.copy(alpha = 0.25f) else Color.Black.copy(alpha = 0.15f)
+        shadowColor = shadowColor
     ) {
         Column(
             modifier = Modifier
                 .fillMaxSize()
-                .border(2.dp, color = if (isSelect) MaterialTheme.colorScheme.primary else Color.White, shape = RoundedCornerShape(12.dp))
+                .border(2.dp, color = colorBorder.copy(alpha = if (running) alpha else 1f), shape = RoundedCornerShape(12.dp))
                 .clickable(onClick = { onNodeClick(node) })
                 .padding(10.dp)
         ) {
             Icon(
-                painter = painterResource(R.mipmap.logo),
+                painter = painterResource(node.node?.getIcon() ?: R.mipmap.logo),
                 contentDescription = "",
                 modifier = Modifier
                     .size(26.dp)
-                    .clip(RoundedCornerShape(50))
+                    .clip(RoundedCornerShape(30))
                     .background(Color(0xFF3B82F6))
                     .padding(5.dp),
                 tint = Color.White

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/pages/home/JobsCompose.kt

@@ -113,7 +113,7 @@ private fun TopToolBar(pv: PaddingValues, vm: VMHome) {
                         .clickable(onClick = {
                             // 前期SOP没有做流程选择,这里先直接跳转到创建页面
                             // ctx.openPageSelectJobType()
-                            ctx.openPageCreateJob()
+                            ctx.openPageCreateJob(0)
                             // 测试流程模板编辑数据
                             // ctx.openPageEditStep(69)
                             // 测试发布作业

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/pages/home/PageHome.kt

@@ -80,7 +80,7 @@ class PageHome : PageBase() {
                         0 -> HomeCompose(pv, if (state.navigationId == 0) 99f else 0f, vm)
                         1 -> JobsCompose(pv, if (state.navigationId == 1) 99f else 0f, vm)
                         2 -> TasksCompose(pv, if (state.navigationId == 2) 99f else 0f, vm)
-                        3 -> MyCompose(pv, if (state.navigationId == 3) 99f else 0f, vm)
+                        3 -> SettingsCompose(pv, if (state.navigationId == 3) 99f else 0f, vm)
                     }
                 }
 

+ 1 - 2
app/src/main/java/com/iscs/bozzys/ui/pages/home/MyCompose.kt → app/src/main/java/com/iscs/bozzys/ui/pages/home/SettingsCompose.kt

@@ -47,10 +47,9 @@ import com.iscs.bozzys.ui.pages.compose.CardContainer
 import com.iscs.bozzys.ui.pages.login.openPageLogin
 import com.iscs.bozzys.ui.pages.vm.VMHome
 import com.iscs.bozzys.ui.theme.Text
-import com.iscs.bozzys.utils.getRoleName
 
 @Composable
-fun MyCompose(pv: PaddingValues, zIndex: Float, vm: VMHome) {
+fun SettingsCompose(pv: PaddingValues, zIndex: Float, vm: VMHome) {
     var showExitDialog by remember { mutableStateOf(false) }
     val ctx = LocalContext.current
     Column(

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/pages/select/job/PageSelectJobType.kt

@@ -145,7 +145,7 @@ class PageSelectJobType : PageBase() {
                                             Modifier
                                                 .fillMaxSize()
                                                 .background(Color.White)
-                                                .clickable(onClick = { openPageCreateJob() }),
+                                                .clickable(onClick = { openPageCreateJob(0) }),
                                             horizontalAlignment = Alignment.CenterHorizontally,
                                             verticalArrangement = Arrangement.Center
                                         ) {

+ 19 - 5
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMCreateJob.kt

@@ -24,12 +24,18 @@ class VMCreateJob : VMBase() {
     val json =
         "[{\"id\":\"job_name\",\"label\":\"作业名称\",\"value\":[\"\"],\"type\":\"input\",\"placeholder\":[\"请输入作业名称\"],\"required\":true},{\"id\":\"job_sop\",\"label\":\"流程模板\",\"value\":[\"\"],\"type\":\"select\",\"placeholder\":[\"\"],\"required\":true,\"options\":[]},{\"id\":\"job_type\",\"label\":\"作业分类\",\"value\":[\"\"],\"type\":\"select\",\"placeholder\":[\"\"],\"options\":[],\"required\":true},{\"id\":\"job_content\",\"label\":\"作业内容\",\"value\":[\"\"],\"type\":\"textarea\",\"placeholder\":[\"\"],\"required\":true},{\"id\":\"job_urgency_level\",\"label\":\"紧急程度\",\"value\":[\"0\"],\"type\":\"radio\",\"placeholder\":[\"\"],\"required\":false,\"options\":[{\"label\":\"普通\",\"value\":\"0\"},{\"label\":\"紧急\",\"value\":\"1\"},{\"label\":\"非常紧急\",\"value\":\"2\"}]}]"
 
+    // 当前作业id
+    private var id = 0
+
     /**
      * 初始化
+     * @param id 如果id > 0认为编辑
      */
-    fun init() {
+    fun init(id: Int) {
+        this.id = id
         viewModelScope.launch {
-            val forms = json.getFormListByJsonList()
+            val job = if (id > 0) ApiRequest.getJobById(id).getOrElse { it.getResponse() }.data else null
+            val forms = json.getFormListByJsonList(job)
             _state.value = _state.value.copy(forms = forms)
             // 流程模板
             ApiRequest.getSopList(mutableMapOf("pageNo" to 1, "pageSize" to -1)).onSuccess {
@@ -80,7 +86,8 @@ class VMCreateJob : VMBase() {
             val type = _state.value.forms.find { it.id == "job_type" }?.value?.getOrNull(0) ?: ""
             val content = _state.value.forms.find { it.id == "job_content" }?.value?.getOrNull(0) ?: ""
             val level = _state.value.forms.find { it.id == "job_urgency_level" }?.value?.getOrNull(0) ?: ""
-            ApiRequest.createJob(
+            ApiRequest.createOrEditJob(
+                id,
                 mutableMapOf(
                     "name" to name,
                     "designId" to sop.toInt(),
@@ -91,9 +98,16 @@ class VMCreateJob : VMBase() {
             ).onSuccess {
                 delay(500)
                 loading.emit(StateLoading())
-                toast.emit("创建成功")
+                toast.emit(if (id > 0) "修改成功" else "创建成功")
                 delay(500)
-                done(it.data ?: 0)
+                val res = it.data
+                if (res is Boolean) {
+                    // 修改返回的数据是布尔型
+                    done(id)
+                } else if (res is Double) {
+                    // 创建成功返回的是Int类型
+                    done(res.toInt())
+                }
             }.onFailure {
                 delay(500)
                 loading.emit(StateLoading())

+ 118 - 0
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMDetailJob.kt

@@ -0,0 +1,118 @@
+package com.iscs.bozzys.ui.pages.vm
+
+import androidx.lifecycle.viewModelScope
+import com.iscs.bozzys.api.ApiRequest
+import com.iscs.bozzys.api.ApiRequest.getResponse
+import com.iscs.bozzys.api.ApiRequest.isCodeOk
+import com.iscs.bozzys.api.Job
+import com.iscs.bozzys.event.RefreshEvent
+import com.iscs.bozzys.event.RefreshEventBus
+import com.iscs.bozzys.ui.common.VMBase
+import kotlinx.coroutines.async
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+
+class VMDetailJob : VMBase() {
+
+    private val _state = MutableStateFlow(StateDetailJob())
+    val state = _state.asStateFlow()
+
+    // 当前作业id
+    private var id = 0
+
+    /**
+     * 数据初始化
+     */
+    fun init(id: Int) {
+        this.id = id
+        initEvent()
+        getData(true)
+    }
+
+    private fun getData(showLoading: Boolean = false) {
+        // 获取数据
+        viewModelScope.launch {
+            if (showLoading) loading.emit(StateLoading(show = true))
+            ApiRequest.getJobById(id).onSuccess {
+                it.data?.let { job -> getJobStatus(job) }
+            }.onFailure {
+                delay(1000)
+                loading.emit(StateLoading())
+                toast.emit(it.getResponse<Any>().msg)
+            }
+        }
+    }
+
+    /**
+     * 获取作业状态数据
+     *
+     * @param job
+     */
+    private fun getJobStatus(job: Job) {
+        viewModelScope.launch {
+            // 获取作业类型字典列表
+            val typeRsp = ApiRequest.getDictData(mutableMapOf("pageNo" to 1, "pageSize" to -1, "dictType" to "work_type"))
+                .getOrElse { it.getResponse() }
+            if (!typeRsp.code.isCodeOk()) {
+                delay(1000)
+                loading.emit(StateLoading())
+                toast.emit(typeRsp.msg)
+                return@launch
+            }
+            // 处理作业类型
+            val dictList = typeRsp.data?.list ?: emptyList()
+            val typeName = dictList.find { dict -> dict.value == job.type }?.label ?: ""
+            var nodeId = -1
+            // 找到当前执行中的任务
+            val currentNodeName = if (job.status == "unreleased") {
+                "待作业发布"
+            } else {
+                val node = job.workflowWorkNodeDOList?.find { it.approvalStatus == "running" }
+                nodeId = node?.id ?: -1
+                node?.nodeName ?: "--"
+            }
+            // 寻找下一个待执行的作业
+            val connections = job.toUIConnectList()
+            var nextNodeName = ""
+            connections.forEach {
+                if (it.fromId == nodeId.toString()) {
+                    // 在这里查找toId,并且是还没有执行过的pending
+                    job.workflowWorkNodeDOList?.find { node -> node.id == it.toId.toInt() && node.approvalStatus == "pending" }?.let { node ->
+                        nextNodeName = node.nodeName
+                        return@forEach
+                    }
+                } else if (it.toId == nodeId.toString()) {
+                    // 在这里查找toId,并且是还没有执行过的pending
+                    job.workflowWorkNodeDOList?.find { node -> node.id == it.fromId.toInt() && node.approvalStatus == "pending" }?.let { node ->
+                        nextNodeName = node.nodeName
+                        return@forEach
+                    }
+                }
+            }
+            // 更新数据源
+            _state.value = _state.value.copy(job = job.copy(typeName = typeName), currentNodeName = currentNodeName, nextNodeName = nextNodeName)
+            delay(500)
+            loading.emit(StateLoading())
+        }
+    }
+
+    /**
+     * 初始化事件
+     */
+    private fun initEvent() {
+        viewModelScope.async {
+            RefreshEventBus.events.collect { event ->
+                when (event) {
+                    is RefreshEvent.JobDetail -> getData()
+                    else -> {}
+                }
+            }
+        }
+    }
+
+
+}
+
+data class StateDetailJob(val job: Job = Job(), val currentNodeName: String = "", val nextNodeName: String = "")

+ 9 - 1
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMEditStep.kt

@@ -124,11 +124,18 @@ class VMEditStep : VMBase() {
     fun getJobInfo() {
         viewModelScope.launch {
             ApiRequest.getJobById(id).onSuccess {
+                val enableEdit = listOf("unreleased").contains(it.data?.status)
                 val nodes = it.data?.toUINodeMap() ?: mutableStateMapOf()
                 val connections = it.data?.toUIConnectList() ?: emptyList()
-                val nodeId = it.data?.workflowWorkNodeDOList?.getOrNull(0)?.id ?: 0
+                val nodeId = if (enableEdit) {
+                    it.data?.workflowWorkNodeDOList?.getOrNull(0)?.id ?: 0
+                } else {
+                    // 如果不能编辑,说明是执行过程,默认选中进行中的
+                    it.data?.workflowWorkNodeDOList?.find { n -> n.approvalStatus == "running" }?.id ?: 0
+                }
                 val node = nodes[nodeId.toString()]
                 _state.value = _state.value.copy(
+                    enableEdit = enableEdit,
                     nodes = nodes,
                     connections = connections,
                     node = if (node == null) _state.value.node else mutableStateOf(node)
@@ -294,6 +301,7 @@ class VMEditStep : VMBase() {
  * @param showFormDialog    是否显示表单Dialog
  */
 data class StateEditStep(
+    val enableEdit: Boolean = false,
     val nodes: SnapshotStateMap<String, NodeUI> = mutableStateMapOf(),
     val node: MutableState<NodeUI> = mutableStateOf(NodeUI("", Offset(0f, 0f))),
     val nodeForms: List<FormField> = listOf(),

+ 11 - 4
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMHome.kt

@@ -174,12 +174,14 @@ class VMHome : VMBase() {
             ApiRequest.getJobs(hashMapOf("pageNo" to page.page, "pageSize" to page.pageSize, "status" to page.type, "key" to page.keywords))
                 .onSuccess {
                     if (page.page == 1) _state.value.jobList.clear()
+                    val newJobs = _state.value.jobList.toMutableList()
                     val jobs = it.data?.list ?: emptyList()
+                    newJobs.addAll(jobs)
                     _state.value.jobList.addAll(jobs)
                     // 存储页面数据
                     delay(1000)
                     _state.value =
-                        _state.value.copy(jobPage = page.copy(isRefresh = false, noMore = (it.data?.total ?: 0) == _state.value.jobList.size))
+                        _state.value.copy(jobPage = page.copy(isRefresh = false, noMore = (it.data?.total ?: 0) == newJobs.size))
                 }.onFailure {
                     delay(1000)
                     _state.value = _state.value.copy(jobPage = _state.value.jobPage.copy(isRefresh = false))
@@ -201,12 +203,16 @@ class VMHome : VMBase() {
                 hashMapOf("pageNo" to page.page, "pageSize" to page.pageSize, "approvalStatus" to page.type, "key" to page.keywords)
             ).onSuccess {
                 if (page.page == 1) _state.value.taskList.clear()
+                val newTasks = _state.value.taskList.toMutableList()
                 val tasks = it.data?.list ?: emptyList()
-                _state.value.taskList.addAll(tasks)
+                newTasks.addAll(tasks)
                 delay(1000)
                 // 存储页面数据
                 _state.value =
-                    _state.value.copy(taskPage = page.copy(isRefresh = false, noMore = (it.data?.total ?: 0) == _state.value.taskList.size))
+                    _state.value.copy(
+                        taskPage = page.copy(isRefresh = false, noMore = (it.data?.total ?: 0) == newTasks.size),
+                        taskList = newTasks
+                    )
             }.onFailure {
                 delay(1000)
                 _state.value =
@@ -270,6 +276,7 @@ class VMHome : VMBase() {
  * @param todoCountFinish   已完成
  * @param roles             用户角色
  * @param user              当前用户
+ * @param isRefreshHome     是否刷新首页数据
  */
 data class StateHome(
     val navs: List<NavBarItem> = arrayListOf(),
@@ -284,7 +291,7 @@ data class StateHome(
     val todoCountRunning: Int = 0,
     val todoCountFinish: Int = 0,
     val roles: List<String> = listOf(),
-    val user: User = User()
+    val user: User = User(),
 )
 
 /**

+ 1 - 0
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMPushJob.kt

@@ -30,6 +30,7 @@ class VMPushJob : VMBase() {
                 loading.emit(StateLoading())
                 toast.emit("发布成功")
                 // 刷新首页数据
+                RefreshEventBus.onRefreshData(RefreshEvent.JobDetail)
                 RefreshEventBus.onRefreshData(RefreshEvent.HomeData)
                 done()
             }.onFailure {

+ 2 - 1
app/src/main/java/com/iscs/bozzys/ui/theme/Theme.kt

@@ -4,6 +4,7 @@ import android.app.Activity
 import android.os.Build
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.LocalOverscrollConfiguration
+import androidx.compose.foundation.LocalOverscrollFactory
 import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.darkColorScheme
@@ -72,7 +73,7 @@ fun BozzysTheme(
         typography = Typography,
         content = {
             // 全局去除滑动顶部和底部的阴影效果
-            CompositionLocalProvider(LocalOverscrollConfiguration provides null) { content() }
+            CompositionLocalProvider(LocalOverscrollFactory provides null) { content() }
         }
     )
 }

+ 15 - 0
app/src/main/res/drawable/category.xml

@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="33.4dp"
+    android:height="32dp"
+    android:viewportWidth="1069"
+    android:viewportHeight="1024">
+  <path
+      android:pathData="M0,173.2C1.2,255.2 102.7,327.2 155.7,337 155.7,294.8 155.4,277.6 155.4,277.6 155.3,277.6 58.4,250.8 58,176.2 57.6,108.1 69.4,46.3 159.1,46.3 237.7,46.3 264.4,96 288.1,156L351.4,156C318.6,72 274.3,0 152.6,0 33.4,0 -0.4,91.2 0,173.2Z"
+      android:fillColor="#4F5F6E"/>
+  <path
+      android:pathData="M537.8,155.8 L289.4,155.8 345.4,305.4 333.9,333.4C333.9,350.2 336.7,355.1 333.9,363.1 341.4,389.6 365.3,405.1 391.1,405.1 421.5,405.1 447.3,383 447.3,348.9 447.3,333.4 430.1,301.4 403.8,296.1L383.7,243.2C461.6,254.7 496.7,302.8 495.7,350.2 494.3,412.3 456.4,456 390.3,455.7 332.1,455.4 289.6,419.8 289.4,356.2 289.3,338.6 288.1,325.6 295.1,304.2 300.7,285.3 266.7,204 266.7,204L247.8,155.8 200.2,155.8 200.2,482.5C199.2,509.9 237.9,543.3 237.9,543.3L716.2,1018 1065.1,665C1065.1,665 639.7,241.4 592.7,194.3 559.4,160.9 537.8,155.8 537.8,155.8Z"
+      android:fillColor="#4F5F6E"/>
+  <path
+      android:pathData="M155.8,489.7 L155.8,378.4 89,378.4 89,575.6C89,617 133.4,645.4 133.4,645.4L509,1021.9 569.9,960.4C569.9,960.4 245.1,634.9 196.1,586.9 147.9,541.4 155.8,489.7 155.8,489.7Z"
+      android:fillColor="#4F5F6E"/>
+</vector>

+ 12 - 0
app/src/main/res/drawable/content.xml

@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M601.6,307.2L601.6,32L262.4,32C192,32 128,89.6 128,166.4v691.2c0,76.8 57.6,134.4 134.4,134.4h492.8c76.8,0 134.4,-57.6 134.4,-134.4L889.6,371.2h-224c-32,0 -64,-25.6 -64,-64zM704,819.2L339.2,819.2c-25.6,0 -51.2,-25.6 -51.2,-51.2s25.6,-51.2 51.2,-51.2L704,716.8c25.6,0 51.2,25.6 51.2,51.2s-19.2,51.2 -51.2,51.2zM704,640L339.2,640c-25.6,0 -51.2,-25.6 -51.2,-51.2s25.6,-51.2 51.2,-51.2L704,537.6c25.6,0 51.2,25.6 51.2,51.2 0,32 -19.2,51.2 -51.2,51.2z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M633.6,32v230.4c0,44.8 25.6,76.8 64,76.8h192L633.6,32z"/>
+</vector>

+ 12 - 0
app/src/main/res/drawable/edit.xml

@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:pathData="M313.5,584.6l18.4,80.6a80,80 0,0 0,60.2 60.2l80.6,18.4a80,80 0,0 0,74.3 -21.4l361,-361a80,80 0,0 0,0 -113.1l-99,-99a80,80 0,0 0,-113.1 0L335,510.3a80,80 0,0 0,-21.5 74.3zM752.5,234.2l70.7,70.7 -339,339 -57.6,-13.1 -13.1,-57.6z"
+      android:fillColor="#3A3939"/>
+  <path
+      android:pathData="M429,98.3H298c-110.4,0 -200,89.5 -200,200v431c0,110.4 89.6,200 200,200h431c110.5,0 200,-89.6 200,-200V577.5a50,50 0,0 0,-100 0V731a100,100 0,0 1,-100 98.3H296.4a100,100 0,0 1,-98.3 -100V296.7a100,100 0,0 1,100 -98.3h131.9a50,50 0,0 0,-0.8 -100z"
+      android:fillColor="#3A3939"/>
+</vector>

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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M777.8,896 L244.2,896c-64.7,0 -117.2,-52.5 -117.2,-117.2L127,245.2c0,-64.7 52.5,-117.2 117.2,-117.2l533.5,0C842.5,128 895,180.5 895,245.2l0,533.5C895,843.5 842.5,896 777.8,896z"/>
+</vector>

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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:pathData="M931,308.3c20.8,-20.8 21.1,-54.2 0.6,-74.7 -20.4,-20.4 -53.9,-20.2 -74.7,0.6L410.8,680.4 167.1,436.7c-20.8,-20.8 -54.3,-21.1 -74.7,-0.7 -20.4,20.4 -20.1,53.9 0.7,74.7l280,280c20.8,20.8 54.3,21.1 74.7,0.7 0.1,-0.1 0.3,-0.3 0.4,-0.4 0.1,-0.1 0.3,-0.3 0.5,-0.4l482.3,-482.3z"
+      android:fillColor="#006569"/>
+</vector>

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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:pathData="M161.2,839.9v-654c0,-56.1 60.7,-91.1 109.3,-63.1l566.3,327c48.6,28 48.6,98.1 0,126.2L270.4,903c-48.5,28 -109.2,-7.1 -109.2,-63.1z"
+      android:fillColor="#424242"/>
+</vector>

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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M492.5,267.9L388.3,371.7l-62.5,62.5 -152.9,152 -22.1,22 -10.4,10.4 -10.4,10.6c-4.7,4.4 -8.1,10.4 -9.4,16.6l-20.3,89.5 -35.4,154.8c-4.4,19.2 12.8,36.3 32,31.9l245.4,-55.5c6.3,-1.3 12.2,-4.7 16.9,-9.3l10.4,-10.4 54.2,-53.9 298,-296.7 -229.2,-228.2zM840.2,378.1c52.4,-52.1 64.3,-124.2 27.4,-161.3l-94.6,-94.1c-37.2,-36.8 -109.7,-24.9 -161.8,27.2l-22.9,22.6 -40.6,40.5 229.2,228.2 63.3,-63zM922.7,810.5h-373.2c-28.8,0 -46.9,31.7 -46.9,60.3s18.2,51.7 46.9,51.7h373.2c28.8,0 37.3,-23 37.3,-51.7s-8.5,-60.3 -37.3,-60.3z"/>
+</vector>

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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1025"
+    android:viewportHeight="1024">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M983.7,313.1a511.9,511.9 0,1 0,40.2 199A510.4,510.4 0,0 0,983.7 313.1zM128.2,512C128.2,299.9 300.8,127.4 512.8,127.4a382.2,382.2 0,0 1,215.4 66.2L194.3,727.4A382.2,382.2 0,0 1,128.2 512zM512.8,896.6a382,382 0,0 1,-218.7 -68.5l534.8,-534.8a382,382 0,0 1,68.5 218.7c0,212 -172.6,384.6 -384.6,384.6z"/>
+</vector>

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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1025"
+    android:viewportHeight="1024">
+  <path
+      android:pathData="M214.6,455.6l80.3,80.4 -80.3,80.3a170.4,170.4 0,0 0,241 241l80.3,-80.3 80.3,80.3 -80.3,80.3a284,284 0,1 1,-401.7 -401.7l80.3,-80.3zM455.6,214.6l80.3,-80.3a284,284 0,1 1,401.7 401.7l-80.3,80.3 -80.3,-80.3 80.3,-80.3a170.4,170.4 0,0 0,-241 -241L536,294.9 455.6,214.6zM254.8,174.4L897.5,817.1a56.8,56.8 0,1 1,-80.3 80.3L174.4,254.8a56.8,56.8 0,1 1,80.3 -80.3z"
+      android:fillColor="#666666"/>
+</vector>

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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M825.6,483.4H392.5V311.3c0,-75.1 61.1,-136.2 136.2,-136.2 75.1,0 136.2,61.1 136.2,136.2 0,25.8 20.9,46.8 46.8,46.8 25.8,0 46.8,-20.9 46.8,-46.8 0,-126.7 -103,-229.7 -229.7,-229.7S299,184.7 299,311.3v172.1h-67.3c-25.7,0 -46.8,21 -46.8,46.8v364c0,25.7 21,46.8 46.8,46.8h593.9c25.7,0 46.8,-21 46.8,-46.8V530.2c0,-25.7 -21,-46.8 -46.8,-46.8zM567.4,706.4v87.7c0,20.9 -17.1,38 -38,38h-1.4c-20.9,0 -38,-17.1 -38,-38v-87.7c-18.3,-12.4 -30.4,-33.4 -30.4,-57.2 0,-38.2 30.9,-69.1 69.1,-69.1 38.2,0 69.1,30.9 69.1,69.1 0,23.8 -12,44.8 -30.4,57.2z"/>
+</vector>

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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:pathData="M128,896h768c70.4,0 128,-57.6 128,-128s-57.6,-128 -128,-128h-256v-25.6c115.2,-51.2 192,-160 192,-294.4 0,-179.2 -140.8,-320 -320,-320S192,140.8 192,320c0,134.4 76.8,243.2 192,294.4v25.6H128c-70.4,0 -128,57.6 -128,128s57.6,128 128,128zM992,960H32c-19.2,0 -32,12.8 -32,32s12.8,32 32,32h960c19.2,0 32,-12.8 32,-32s-12.8,-32 -32,-32z"
+      android:fillColor="#777777"/>
+</vector>

+ 2 - 8
app/src/main/res/drawable/number.xml

@@ -4,12 +4,6 @@
     android:viewportWidth="1024"
     android:viewportHeight="1024">
   <path
-      android:pathData="M128,341.3m42.7,0l682.7,0q42.7,0 42.7,42.7l0,0q0,42.7 -42.7,42.7l-682.7,0q-42.7,0 -42.7,-42.7l0,0q0,-42.7 42.7,-42.7Z"
-      android:fillColor="#666666"/>
-  <path
-      android:pathData="M422.6,85.3H426.7a38.6,38.6 0,0 1,38.4 42.5L387.8,900.3a42.7,42.7 0,0 1,-42.5 38.4H341.3a38.6,38.6 0,0 1,-38.4 -42.5L380.2,123.7a42.7,42.7 0,0 1,42.5 -38.4zM678.6,85.3H682.7a38.6,38.6 0,0 1,38.4 42.5L643.8,900.3a42.7,42.7 0,0 1,-42.5 38.4H597.3a38.6,38.6 0,0 1,-38.4 -42.5L636.2,123.7a42.7,42.7 0,0 1,42.5 -38.4z"
-      android:fillColor="#666666"/>
-  <path
-      android:pathData="M128,597.3m42.7,0l682.7,0q42.7,0 42.7,42.7l0,0q0,42.7 -42.7,42.7l-682.7,0q-42.7,0 -42.7,-42.7l0,0q0,-42.7 42.7,-42.7Z"
-      android:fillColor="#666666"/>
+      android:pathData="M146.3,0h731.4a146.3,146.3 0,0 1,146.3 146.3v731.4a146.3,146.3 0,0 1,-146.3 146.3L146.3,1024a146.3,146.3 0,0 1,-146.3 -146.3L0,146.3a146.3,146.3 0,0 1,146.3 -146.3zM585.1,658.3l-18.5,146.3h73.1L658.3,658.3h146.3l9.3,-73.1h-146.3l18.5,-146.3h146.3l9.3,-73.1h-146.3l18.5,-146.3h-73.1l-18.5,146.3h-146.3l18.5,-146.3h-73.1l-18.5,146.3h-146.3L247.2,438.9h146.3l-18.5,146.3h-146.3L219.4,658.3h146.3l-18.5,146.3h73.1L438.9,658.3h146.3zM594.4,585.1h-146.3l18.5,-146.3h146.3l-18.5,146.3z"
+      android:fillColor="#333333"/>
 </vector>

+ 12 - 0
app/src/main/res/drawable/progress.xml

@@ -0,0 +1,12 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M690.1,0L200.3,0C102.4,0 22.3,80.1 22.3,178.1v667.8c0,97.9 80.1,178.1 178.1,178.1h298.3c-73.5,-64.6 -120.2,-160.3 -120.2,-267.1 0,-195.9 160.3,-356.2 356.2,-356.2 46.7,0 91.3,8.9 133.6,26.7L868.2,178.1c0,-97.9 -80.1,-178.1 -178.1,-178.1zM289.4,756.9c0,28.9 -15.6,44.5 -44.5,44.5s-44.5,-15.6 -44.5,-44.5 15.6,-44.5 44.5,-44.5 44.5,15.6 44.5,44.5zM378.4,489.7c0,28.9 -15.6,44.5 -44.5,44.5h-89c-28.9,0 -44.5,-15.6 -44.5,-44.5s15.6,-44.5 44.5,-44.5h89c28.9,0 44.5,15.6 44.5,44.5zM690.1,222.6c0,28.9 -15.6,44.5 -44.5,44.5L244.9,267.1c-28.9,0 -44.5,-15.6 -44.5,-44.5s15.6,-44.5 44.5,-44.5h400.7c28.9,0 44.5,15.6 44.5,44.5z"/>
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M734.6,1024c-146.9,0 -267.1,-120.2 -267.1,-267.1s120.2,-267.1 267.1,-267.1 267.1,120.2 267.1,267.1 -120.2,267.1 -267.1,267.1zM912.7,607.7c0,-4.5 -2.2,-6.7 -4.5,-11.1 -2.2,-2.2 -6.7,-4.5 -11.1,-4.5s-6.7,2.2 -11.1,4.5l-31.2,28.9c-15.6,-15.6 -35.6,-28.9 -57.9,-35.6 -17.8,-6.7 -40.1,-11.1 -62.3,-11.1 -42.3,0 -80.1,13.4 -111.3,37.8 -31.2,26.7 -53.4,60.1 -62.3,100.2v2.2c0,2.2 0,4.5 2.2,4.5 2.2,2.2 2.2,2.2 4.5,2.2h46.7c4.5,0 6.7,-2.2 6.7,-4.5 6.7,-15.6 11.1,-24.5 13.4,-26.7 11.1,-17.8 24.5,-31.2 42.3,-42.3 17.8,-11.1 37.8,-15.6 57.9,-15.6 31.2,0 57.9,11.1 80.1,31.2l-31.2,31.2c-2.2,6.7 -4.5,8.9 -4.5,13.4 0,4.5 2.2,6.7 4.5,11.1 2.2,2.2 6.7,4.5 11.1,4.5h104.6c4.5,0 6.7,-2.2 11.1,-4.5 2.2,-2.2 4.5,-6.7 4.5,-11.1 -2.2,0 -2.2,-104.6 -2.2,-104.6zM906,794.7c0,-2.2 0,-4.5 -2.2,-4.5 -2.2,-2.2 -2.2,-2.2 -4.5,-2.2h-44.5c-4.5,0 -6.7,2.2 -6.7,4.5 -6.7,15.6 -11.1,24.5 -13.4,26.7 -11.1,17.8 -24.5,31.2 -42.3,42.3s-37.8,15.6 -57.9,15.6c-15.6,0 -28.9,-2.2 -42.3,-8.9 -13.4,-4.5 -26.7,-13.4 -37.8,-24.5l31.2,-31.2c2.2,-2.2 4.5,-6.7 4.5,-11.1s-2.2,-6.7 -4.5,-11.1c-2.2,-2.2 -6.7,-4.5 -11.1,-4.5h-104.6c-4.5,0 -6.7,2.2 -11.1,4.5 0,4.5 -2.2,6.7 -2.2,11.1v104.6c0,4.5 2.2,6.7 4.5,11.1 2.2,2.2 6.7,4.5 11.1,4.5s6.7,-2.2 11.1,-4.5l28.9,-28.9c15.6,15.6 35.6,28.9 55.7,35.6 22.3,6.7 42.3,11.1 66.8,11.1 42.3,0 80.1,-13.4 111.3,-37.8 31.2,-26.7 51.2,-60.1 60.1,-102.4z"/>
+</vector>

+ 5 - 2
app/src/main/res/drawable/target.xml

@@ -4,6 +4,9 @@
     android:viewportWidth="1024"
     android:viewportHeight="1024">
   <path
-      android:pathData="M788.5,629.4c0,-17.1 -5.8,-31.7 -17.7,-44.7L421.2,235.9c-12.3,-12.3 -29.4,-22.9 -49.8,-31.7 -21.2,-8.9 -39.9,-13 -57,-13L110.9,191.1c-17.1,0 -31.7,6.5 -44,18.8 -12.3,12.3 -18.8,27 -18.8,44l0,202.8c0,17.1 4.1,36.5 13,57 8.9,21.2 18.8,37.5 31.7,49.8l349.5,349.5c12.3,12.3 27,17.7 44,17.7 17.1,0 31.7,-5.8 44.7,-17.7l239.6,-239.6C782,661.2 788.5,646.5 788.5,629.4L788.5,629.4 788.5,629.4zM248.5,392.2c-12.3,12.3 -27,18.1 -44,18.1 -17.1,0 -31.7,-5.8 -44,-18.1 -12.3,-12.3 -18.1,-27 -18.1,-44 0,-17.1 5.8,-31.7 18.1,-44 12.3,-12.3 27,-18.1 44,-18.1 17.1,0 31.7,5.8 44,18.1C260.8,316.4 266.2,331.1 266.2,348.2 267.3,365.6 260.8,379.9 248.5,392.2L248.5,392.2 248.5,392.2zM958.1,584.4 L608.6,235.9c-12.3,-12.3 -29.4,-22.9 -49.8,-31.7C537.6,195.2 518.8,191.1 501.8,191.1l-109.9,0c17.1,0 36.5,4.1 57,13 21.2,8.9 37.5,18.8 49.8,31.7l349.5,348.5c12.3,13 18.1,27.6 18.1,44.7s-5.8,31.7 -18.1,44l-229,229.7c9.9,9.9 18.8,17.1 25.9,21.8 7.2,4.8 17.1,6.5 28.7,6.5 17.1,0 31.7,-5.8 44.7,-18.1l239.6,-240.3c12.3,-12.3 17.7,-27 17.7,-44S970.1,597.7 958.1,584.4L958.1,584.4 958.1,584.4zM958.1,584.4"
-      android:fillColor="#272636"/>
+      android:pathData="M933,77.7h-89.6v115.5c0,58.4 -45.6,105.6 -101.5,105.6 -56.1,0 -101.5,-47.3 -101.5,-105.6V77.7H381.1v115.5c0,58.4 -45.6,105.6 -101.5,105.6 -56.1,0 -101.5,-47.3 -101.5,-105.6V77.7h-87C40.8,77.7 0,119 0,170v761.7C0,982.7 40.8,1024 91,1024h841.9c50.3,0 91,-41.3 91,-92.3V170c0.2,-51 -40.6,-92.3 -90.8,-92.3zM742.1,460.8L490.7,731.3c-8.2,8.9 -19.5,13.8 -31.2,13.8 -11.4,0 -22.5,-4.8 -30.5,-13.3l-149.2,-158.2c-13,-11.3 -18.4,-29.4 -14,-46.4 4.4,-16.9 17.9,-29.7 34.5,-32.6 16.6,-2.9 33.5,4.4 43,18.8l115.5,125.1 222,-240.5c17.1,-13.7 41.3,-12.1 56.7,3.6 15.5,16 17.4,41.1 4.6,59.2z"
+      android:fillColor="#2B3F64"/>
+  <path
+      android:pathData="M741.7,0c25.3,0 45.9,21.2 45.9,47.4v142.8c0,26.3 -20.5,47.4 -45.9,47.4s-45.9,-21.2 -45.9,-47.4V47.4c0,-26.3 20.5,-47.4 45.9,-47.4zM279.6,0c25.4,0 45.9,21.2 45.9,47.4v142.8c0,26.3 -20.5,47.4 -45.9,47.4 -25.3,0 -45.9,-21.2 -45.9,-47.4V47.4c0,-26.3 20.5,-47.4 45.9,-47.4z"
+      android:fillColor="#2B3F64"/>
 </vector>

+ 15 - 0
app/src/main/res/drawable/template.xml

@@ -0,0 +1,15 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:pathData="M42.7,42.7m42.7,0l341.3,0q42.7,0 42.7,42.7l0,341.3q0,42.7 -42.7,42.7l-341.3,0q-42.7,0 -42.7,-42.7l0,-341.3q0,-42.7 42.7,-42.7Z"
+      android:fillColor="#5D6166"/>
+  <path
+      android:pathData="M768,256m-213.3,0a213.3,213.3 0,1 0,426.7 0,213.3 213.3,0 1,0 -426.7,0Z"
+      android:fillColor="#5D6166"/>
+  <path
+      android:pathData="M42.7,554.7m42.7,0l853.3,0q42.7,0 42.7,42.7l0,341.3q0,42.7 -42.7,42.7l-853.3,0q-42.7,0 -42.7,-42.7l0,-341.3q0,-42.7 42.7,-42.7Z"
+      android:fillColor="#5D6166"/>
+</vector>