Эх сурвалжийг харах

1. 任务的执行已完成
2. 节点编辑保存自动引导
3. 对接App独立接口可显示进行中和已完成
4. 任务支持按照进行中和已完成去筛选

bjb 4 сар өмнө
parent
commit
45625c08a8
37 өөрчлөгдсөн 785 нэмэгдсэн , 280 устгасан
  1. 2 2
      .idea/deploymentTargetSelector.xml
  2. 9 2
      app/src/main/java/com/iscs/bozzys/api/ApiBean.kt
  3. 20 11
      app/src/main/java/com/iscs/bozzys/api/ApiRequest.kt
  4. 10 1
      app/src/main/java/com/iscs/bozzys/api/ApiService.kt
  5. 0 25
      app/src/main/java/com/iscs/bozzys/event/AuthEvent.kt
  6. 46 0
      app/src/main/java/com/iscs/bozzys/event/Event.kt
  7. 95 0
      app/src/main/java/com/iscs/bozzys/ui/common/Dialog.kt
  8. 27 0
      app/src/main/java/com/iscs/bozzys/ui/common/Empty.kt
  9. 1 1
      app/src/main/java/com/iscs/bozzys/ui/common/Loading.kt
  10. 7 2
      app/src/main/java/com/iscs/bozzys/ui/common/PageBase.kt
  11. 1 1
      app/src/main/java/com/iscs/bozzys/ui/common/Title.kt
  12. 1 1
      app/src/main/java/com/iscs/bozzys/ui/common/VMBase.kt
  13. 1 1
      app/src/main/java/com/iscs/bozzys/ui/pages/PageSplash.kt
  14. 46 22
      app/src/main/java/com/iscs/bozzys/ui/pages/compose/FormCompose.kt
  15. 13 5
      app/src/main/java/com/iscs/bozzys/ui/pages/compose/JobListItem.kt
  16. 1 1
      app/src/main/java/com/iscs/bozzys/ui/pages/compose/TaskListItem.kt
  17. 2 2
      app/src/main/java/com/iscs/bozzys/ui/pages/create/job/PageCreateJob.kt
  18. 2 2
      app/src/main/java/com/iscs/bozzys/ui/pages/detail/job/PageDetailJob.kt
  19. 29 21
      app/src/main/java/com/iscs/bozzys/ui/pages/detail/task/PageDetailTask.kt
  20. 55 4
      app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/PageEditStep.kt
  21. 15 0
      app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/compose/ZoomContainer.kt
  22. 50 41
      app/src/main/java/com/iscs/bozzys/ui/pages/home/HomeCompose.kt
  23. 17 35
      app/src/main/java/com/iscs/bozzys/ui/pages/home/JobsCompose.kt
  24. 1 1
      app/src/main/java/com/iscs/bozzys/ui/pages/home/PageHome.kt
  25. 15 32
      app/src/main/java/com/iscs/bozzys/ui/pages/home/TasksCompose.kt
  26. 1 1
      app/src/main/java/com/iscs/bozzys/ui/pages/login/PageLogin.kt
  27. 1 1
      app/src/main/java/com/iscs/bozzys/ui/pages/message/PageMessage.kt
  28. 2 2
      app/src/main/java/com/iscs/bozzys/ui/pages/select/job/PageSelectJobType.kt
  29. 2 2
      app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMCreateJob.kt
  30. 38 12
      app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMDetailTask.kt
  31. 53 6
      app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMEditStep.kt
  32. 118 30
      app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMHome.kt
  33. 2 2
      app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMLogin.kt
  34. 6 5
      app/src/main/java/com/iscs/bozzys/utils/DateUtil.kt
  35. 14 6
      app/src/main/java/com/iscs/bozzys/utils/JsonUtil.kt
  36. 6 0
      app/src/main/res/drawable/empty.xml
  37. 76 0
      app/src/main/res/layout/timer_picker.xml

+ 2 - 2
.idea/deploymentTargetSelector.xml

@@ -4,10 +4,10 @@
     <selectionStates>
       <SelectionState runConfigName="app">
         <option name="selectionMode" value="DROPDOWN" />
-        <DropdownSelection timestamp="2025-12-26T06:50:06.824288800Z">
+        <DropdownSelection timestamp="2025-12-30T07:14:50.054898300Z">
           <Target type="DEFAULT_BOOT">
             <handle>
-              <DeviceId pluginId="PhysicalDevice" identifier="serial=TPG5T17C05011309" />
+              <DeviceId pluginId="PhysicalDevice" identifier="serial=d8d12db95670c08" />
             </handle>
           </Target>
         </DropdownSelection>

+ 9 - 2
app/src/main/java/com/iscs/bozzys/api/ApiBean.kt

@@ -1,5 +1,6 @@
 package com.iscs.bozzys.api
 
+import android.util.Log
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.geometry.Offset
 import com.iscs.bozzys.ui.pages.edit.step.compose.Anchor
@@ -45,6 +46,11 @@ class LoginRsp(
  */
 class PageRsp<T>(val total: Int, val list: List<T>)
 
+/**
+ * 任务统计
+ */
+data class TaskStatistics(val inProgressCount: Int, val completedCount: Int)
+
 
 /**
  * 点位基类
@@ -103,7 +109,7 @@ data class Dict(val id: Int, val sort: Int, val label: String, val value: String
  * 任务信息
  */
 @Serializable
-class Node(
+data class Node(
     val id: Int = 0,
     val workId: Int = 0,
     val uuid: String = "",
@@ -132,8 +138,9 @@ class Node(
      *
      * @param forms 表单数据
      */
-    fun checkSubmitParams(forms: List<FormField>): String {
+    fun checkCanSubmit(forms: List<FormField>): String {
         forms.forEach {
+            Log.d("xiaoming","检查表单:$it")
             if (it.required) {
                 if ((it.value.getOrNull(0) ?: "").isEmpty()) {
                     return (it.placeholder.getOrNull(0) ?: "").ifEmpty { "请填写${it.label}" }

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

@@ -57,25 +57,20 @@ object ApiRequest {
     /**
      * 基础请求封装
      */
-    private suspend fun <T> requestApi(apiFun: suspend () -> Response<T>): Result<Response<T>> {
+    private suspend fun <T> requestApi(retryCount: Int = 3, apiFun: suspend () -> Response<T>): Result<Response<T>> {
         return try {
             val rsp = apiFun()
             // 请求成功
-            if (listOf(0, 200).contains(rsp.code)) {
+            if (rsp.code.isCodeOk()) {
                 Result.success(rsp)
             } else if (rsp.code == 401) {
                 // 授权过期,需要重新授权
                 val refreshRsp = refreshToken(Storage.readRefreshToken()).getOrNull()
-                if (listOf(0, 200).contains(refreshRsp?.code ?: -1)) {
+                if ((refreshRsp?.code ?: -1).isCodeOk() && retryCount > 0) {
                     refreshRsp?.data?.accessToken.saveToken()
                     refreshRsp?.data?.refreshToken.saveRefreshToken()
                     // 刷新完成执行一次请求并返回数据
-                    val rsp = apiFun()
-                    if (listOf(0, 200).contains(rsp.code)) {
-                        Result.success(rsp)
-                    } else {
-                        Result.failure(dealException(Exception(rsp.msg), rsp))
-                    }
+                    requestApi(retryCount - 1, apiFun)
                 } else {
                     Result.failure(dealException(Exception(rsp.msg), rsp))
                 }
@@ -87,13 +82,20 @@ object ApiRequest {
         }
     }
 
+    /**
+     * 响应码是否有效
+     */
+    fun Int.isCodeOk(): Boolean {
+        return listOf(0, 200).contains(this)
+    }
+
     /**
      * 获取响应数据结构
      */
-    fun Throwable.getResponse(): Response<Any> {
+    fun <T> Throwable.getResponse(): Response<T> {
         val msg = this.message ?: "{\"code\":404,\"msg\":\"Network error\"}"
         val json = if (msg.startsWith("{") && msg.endsWith("}")) msg else "{\"code\":404,\"msg\":\"Network error\"}"
-        val t = Response<Any>()
+        val t = Response<T>()
         return Gson().fromJson(json, t::class.java)
     }
 
@@ -137,6 +139,13 @@ object ApiRequest {
         return requestApi { api.getTasks(params) }
     }
 
+    /**
+     * 查询首页任务统计
+     */
+    suspend fun getTaskStatistics(): Result<Response<TaskStatistics>> {
+        return requestApi { api.getTaskStatistics() }
+    }
+
     /**
      * 通过节点Id获取任务信息
      *

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

@@ -47,14 +47,23 @@ interface ApiService {
 
     /**
      * 获取任务列表数据
+     * app /admin-api/adroid/iscs/workflow/getAppMyWorkPage
+     * web /admin-api/iscs/workflow-work/getMyWorkPage
      */
     @Headers("Content-Type: application/x-www-form-urlencoded")
-    @GET("/admin-api/iscs/workflow-work/getMyWorkPage")
+    @GET("/admin-api/adroid/iscs/workflow/getAppMyWorkPage")
     suspend fun getTasks(
         @QueryMap params: MutableMap<String, Any>,
         @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
     ): Response<PageRsp<Task>>
 
+    /**
+     * 查询任务数统计
+     */
+    @Headers("Content-Type: application/x-www-form-urlencoded")
+    @GET("/admin-api/adroid/iscs/workflow/getAppHomeStatistics")
+    suspend fun getTaskStatistics(@HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()): Response<TaskStatistics>
+
     /**
      * 通过节点Id获取任务信息
      */

+ 0 - 25
app/src/main/java/com/iscs/bozzys/event/AuthEvent.kt

@@ -1,25 +0,0 @@
-package com.iscs.bozzys.event
-
-import kotlinx.coroutines.flow.MutableSharedFlow
-import kotlinx.coroutines.flow.asSharedFlow
-
-/**
- * 授权事件总线
- */
-object AuthEventBus {
-
-    private val _events = MutableSharedFlow<AuthEvent>(extraBufferCapacity = 1)
-
-    val events = _events.asSharedFlow()
-
-    fun onTokenExpired() {
-        _events.tryEmit(AuthEvent.TokenExpired)
-    }
-}
-
-/**
- * 授权事件封装
- */
-sealed class AuthEvent {
-    object TokenExpired : AuthEvent()
-}

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

@@ -0,0 +1,46 @@
+package com.iscs.bozzys.event
+
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+
+/**
+ * 授权事件总线
+ */
+object AuthEventBus {
+
+    private val _events = MutableSharedFlow<AuthEvent>(extraBufferCapacity = 1)
+
+    val events = _events.asSharedFlow()
+
+    fun onTokenExpired() {
+        _events.tryEmit(AuthEvent.TokenExpired)
+    }
+}
+
+/**
+ * 授权事件封装
+ */
+sealed class AuthEvent {
+    object TokenExpired : AuthEvent()
+}
+
+/**
+ * 刷新事件总线
+ */
+object RefreshEventBus {
+    private val _events = MutableSharedFlow<RefreshEvent>(extraBufferCapacity = 1)
+
+    val events = _events.asSharedFlow()
+
+    fun onRefreshData(event: RefreshEvent) {
+        _events.tryEmit(event)
+    }
+}
+
+sealed class RefreshEvent(var any: Any = Any()) {
+    // 刷新首页数据 Home/Jobs/Tasks
+    object HomeData : RefreshEvent()
+
+    // 更新地图位置到顶部居中
+    object UpdateNodeToMapTopCenter : RefreshEvent()
+}

+ 95 - 0
app/src/main/java/com/iscs/bozzys/ui/common/Dialog.kt

@@ -0,0 +1,95 @@
+package com.iscs.bozzys.ui.common
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+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.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.Dialog
+import com.iscs.bozzys.ui.theme.Text
+
+/**
+ * 默认公共弹窗
+ */
+@Composable
+fun TipsDialog(
+    show: Boolean = false,
+    title: String = "提示",
+    content: String = "",
+    cancelText: String = "取消",
+    onCancel: () -> Unit = {},
+    confirmText: String = "确认",
+    onConfirm: () -> Unit = {}
+) {
+    if (show) {
+        Dialog(onDismissRequest = {}) {
+            Box(
+                modifier = Modifier
+                    .clip(RoundedCornerShape(12.dp))
+                    .background(Color.White)
+            ) {
+                Column(modifier = Modifier.width(240.dp), horizontalAlignment = Alignment.CenterHorizontally) {
+                    Text(title, fontSize = 16.sp, lineHeight = 36.sp, fontWeight = FontWeight.Bold, color = Text)
+                    Spacer(
+                        Modifier
+                            .fillMaxWidth()
+                            .height(1.dp)
+                            .background(Color(0xFFEEEEEE))
+                    )
+                    Text(
+                        content,
+                        fontSize = 15.sp,
+                        color = Text.copy(alpha = 0.8f),
+                        modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp)
+                    )
+                    Row(Modifier.padding(horizontal = 5.dp, vertical = 10.dp)) {
+                        Text(
+                            cancelText, color = Color.White, modifier = Modifier
+                                .padding(horizontal = 5.dp)
+                                .weight(1f)
+                                .clip(RoundedCornerShape(6.dp))
+                                .background(Color.Gray.copy(alpha = 0.6f))
+                                .clickable(onClick = { onCancel() })
+                                .padding(vertical = 5.dp),
+                            textAlign = TextAlign.Center,
+                            fontSize = 16.sp,
+                            fontWeight = FontWeight.Bold
+                        )
+                        Text(
+                            confirmText,
+                            color = Color.White,
+                            modifier = Modifier
+                                .padding(horizontal = 5.dp)
+                                .weight(1f)
+                                .clip(RoundedCornerShape(6.dp))
+                                .background(MaterialTheme.colorScheme.primary)
+                                .clickable(onClick = { onConfirm() })
+                                .padding(vertical = 5.dp),
+                            textAlign = TextAlign.Center,
+                            fontSize = 16.sp,
+                            fontWeight = FontWeight.Bold
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+}

+ 27 - 0
app/src/main/java/com/iscs/bozzys/ui/common/Empty.kt

@@ -0,0 +1,27 @@
+package com.iscs.bozzys.ui.common
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.iscs.bozzys.R
+import com.iscs.bozzys.ui.theme.Text
+
+/**
+ * 空数据
+ */
+@Composable
+fun Empty(tips: String = "", modifier: Modifier = Modifier) {
+    Column(modifier = modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
+        Icon(painter = painterResource(R.drawable.empty), contentDescription = null, modifier = Modifier.size(64.dp), tint = Text.copy(alpha = 0.8f))
+        Text(tips, color = Text.copy(alpha = 0.8f), fontSize = 14.sp)
+    }
+}

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/base/Loading.kt → app/src/main/java/com/iscs/bozzys/ui/common/Loading.kt

@@ -1,4 +1,4 @@
-package com.iscs.bozzys.ui.base
+package com.iscs.bozzys.ui.common
 
 import android.view.WindowManager
 import androidx.compose.foundation.background

+ 7 - 2
app/src/main/java/com/iscs/bozzys/ui/base/PageBase.kt → app/src/main/java/com/iscs/bozzys/ui/common/PageBase.kt

@@ -1,4 +1,4 @@
-package com.iscs.bozzys.ui.base
+package com.iscs.bozzys.ui.common
 
 import android.os.Bundle
 import android.view.Window
@@ -85,7 +85,12 @@ abstract class PageBase(
         lifecycleScope.launch {
             AuthEventBus.events.collect { event ->
                 when (event) {
-                    is AuthEvent.TokenExpired -> openPageLogin()
+                    is AuthEvent.TokenExpired -> {
+                        // 打开登录页面
+                        openPageLogin()
+                        // 关闭当前页面
+                        destroyDelay(500)
+                    }
                 }
             }
         }

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/base/Title.kt → app/src/main/java/com/iscs/bozzys/ui/common/Title.kt

@@ -1,4 +1,4 @@
-package com.iscs.bozzys.ui.base
+package com.iscs.bozzys.ui.common
 
 import android.app.Activity
 import androidx.compose.foundation.background

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/base/VMBase.kt → app/src/main/java/com/iscs/bozzys/ui/common/VMBase.kt

@@ -1,4 +1,4 @@
-package com.iscs.bozzys.ui.base
+package com.iscs.bozzys.ui.common
 
 import androidx.lifecycle.ViewModel
 import com.iscs.bozzys.ui.pages.vm.StateLoading

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

@@ -25,7 +25,7 @@ import com.alibaba.sdk.android.push.CommonCallback
 import com.alibaba.sdk.android.push.noonesdk.PushServiceFactory
 import com.iscs.bozzys.R
 import com.iscs.bozzys.service.AliPushService
-import com.iscs.bozzys.ui.base.PageBase
+import com.iscs.bozzys.ui.common.PageBase
 import com.iscs.bozzys.ui.pages.home.openPageHome
 import com.iscs.bozzys.ui.pages.login.openPageLogin
 import com.iscs.bozzys.utils.Storage

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

@@ -126,6 +126,7 @@ fun FormContainer(forms: List<FormField>, onValueChange: (FormField) -> Unit, mo
                             form.value = it
                             onValueChange(form)
                         },
+                        required = form.required,
                         placeholder = form.placeholder,
                         enable = form.enabled && enabled
                     )
@@ -138,7 +139,8 @@ fun FormContainer(forms: List<FormField>, onValueChange: (FormField) -> Unit, mo
                             onValueChange(form)
                         },
                         required = form.required,
-                        enable = form.enabled && enabled
+                        enable = form.enabled && enabled,
+                        placeholder = form.placeholder
                     )
                     // 单选
                     "radio" -> FormRadio(
@@ -619,7 +621,7 @@ fun FormDateSelect(
 ) {
     var date by remember {
         mutableStateOf(
-            (value.getOrNull(0) ?: "").dateToTimestamp("yyyy/MM/dd HH:mm")
+            (value.getOrNull(0) ?: "").dateToTimestamp("yyyy/MM/dd HH:mm:ss")
         )
     }
     val picker = CardDatePickerDialog.builder(LocalContext.current)
@@ -696,19 +698,15 @@ fun FormDateRangeSelect(
     label: String,
     value: List<String>,
     onSelectChange: (List<String>) -> Unit,
-    placeholder: List<String> = listOf("", ""),
+    placeholder: List<String> = listOf(),
     required: Boolean = false,
     enable: Boolean = true
 ) {
     val ctx = LocalContext.current
-    var start by remember { mutableStateOf("".dateToTimestamp("yyyy/MM/dd")) }
-    var end by remember { mutableStateOf("".dateToTimestamp("yyyy/MM/dd")) }
-    LaunchedEffect(Unit) {
-        if (value.size == 2) {
-            start = value[0].dateToTimestamp("yyyy/MM/dd")
-            end = value[1].dateToTimestamp("yyyy/MM/dd")
-        }
-    }
+    val startTs = (value.getOrNull(0) ?: "").dateToTimestamp("yyyy/MM/dd HH:mm:ss")
+    val endTs = (value.getOrNull(1) ?: "").dateToTimestamp("yyyy/MM/dd HH:mm:ss")
+    var start by remember { mutableStateOf(startTs) }
+    var end by remember { mutableStateOf(endTs) }
 
     Column(
         Modifier
@@ -742,7 +740,11 @@ fun FormDateRangeSelect(
             verticalAlignment = Alignment.CenterVertically
         ) {
             Text(
-                start.format("yyyy/MM/dd"),
+                if (start <= 0) {
+                    if ((placeholder.getOrNull(0) ?: "").isEmpty()) {
+                        "请选择开始日期"
+                    } else placeholder.getOrNull(0) ?: ""
+                } else start.format("yyyy/MM/dd"),
                 modifier = Modifier
                     .weight(1f)
                     .height(46.dp)
@@ -779,7 +781,11 @@ fun FormDateRangeSelect(
                 color = Text.copy(alpha = if (enable) 1f else 0.6f)
             )
             Text(
-                end.format("yyyy/MM/dd"),
+                if (end <= 0) {
+                    if ((placeholder.getOrNull(1) ?: "").isEmpty()) {
+                        "请选择结束日期"
+                    } else placeholder.getOrNull(1) ?: ""
+                } else end.format("yyyy/MM/dd"),
                 modifier = Modifier
                     .weight(1f)
                     .height(46.dp)
@@ -828,18 +834,16 @@ fun FormTimeSelect(
     value: List<String>,
     onSelectChange: (List<String>) -> Unit,
     required: Boolean = false,
-    enable: Boolean = true
+    enable: Boolean = true,
+    placeholder: List<String> = listOf(),
 ) {
-    var date by remember {
-        mutableStateOf((value.getOrNull(0) ?: "").dateToTimestamp("yyyy/MM/dd HH:mm:ss"))
-    }
+    val timestamp = (value.getOrNull(0) ?: "0").toLong()
+    var date by remember { mutableStateOf(timestamp) }
     val picker = CardDatePickerDialog.builder(LocalContext.current)
         .setTitle("请选择时间")
         .setDefaultTime(date)
-        .setDisplayType(
-            mutableListOf(DateTimeConfig.HOUR, DateTimeConfig.MIN)
-        )
-        .setPickerLayout(R.layout.date_picker)
+        .setDisplayType(mutableListOf(DateTimeConfig.HOUR, DateTimeConfig.MIN, DateTimeConfig.SECOND))
+        .setPickerLayout(R.layout.timer_picker)
         .showBackNow(false)
         .showFocusDateInfo(false)
         .showDateLabel(false)
@@ -874,7 +878,13 @@ fun FormTimeSelect(
         }
 
         Text(
-            date.format("HH:mm"),
+            if (date <= 0) {
+                if (placeholder.getOrNull(0).isNullOrEmpty()) {
+                    "请选择$label"
+                } else {
+                    placeholder.getOrNull(0) ?: ""
+                }
+            } else date.format("HH:mm:ss"),
             modifier = Modifier
                 .fillMaxWidth()
                 .height(46.dp)
@@ -948,3 +958,17 @@ fun String.getFormListByJsonObject(): List<FormField> {
 
     return list
 }
+
+/**
+ * 校验是否可以提交,并返回提示信息
+ */
+fun List<FormField>.checkCanCommitReturnTips(): String {
+    this.forEach {
+        if (it.required) {
+            if (it.value.isEmpty() || it.value.getOrNull(0).isNullOrEmpty()) {
+                return if (it.placeholder.isEmpty()) "请输入${it.label}" else (it.placeholder.getOrNull(0) ?: "").ifEmpty { "请输入${it.label}" }
+            }
+        }
+    }
+    return ""
+}

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

@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.FlowRow
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
@@ -26,12 +25,12 @@ import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 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.pages.detail.task.openPageDetailTask
 import com.iscs.bozzys.ui.theme.Text
 
 @OptIn(ExperimentalLayoutApi::class)
@@ -52,8 +51,17 @@ fun JobListItem(job: Job) {
             .padding(16.dp)
     ) {
         Row {
-            Text(job.name, fontSize = 15.sp, fontWeight = FontWeight.Medium, color = Text)
-            Spacer(Modifier.weight(1f))
+            Text(
+                job.name,
+                fontSize = 15.sp,
+                fontWeight = FontWeight.Medium,
+                color = Text,
+                maxLines = 1,
+                overflow = TextOverflow.Ellipsis,
+                modifier = Modifier
+                    .padding(end = 10.dp)
+                    .weight(1f)
+            )
             Text(
                 levelInfo.first, modifier = Modifier
                     .height(24.dp)
@@ -145,7 +153,7 @@ private fun getLevelNameAndColor(level: String): Pair<String, Color> {
 fun getJobStatusName(approvalStatus: String): String {
     return when (approvalStatus) {
         "approved" -> "已审核"
-        "unaudited" -> "未审核"
+        "running" -> "未审核"
         "pending" -> "待处理"
         else -> approvalStatus
     }

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/pages/compose/TaskListItem.kt

@@ -144,7 +144,7 @@ private fun getLevelNameAndColor(level: String): Pair<String, Color> {
  fun getTaskStatusName(approvalStatus: String): String {
     return when (approvalStatus) {
         "approved" -> "已审核"
-        "unaudited" -> "未审核"
+        "running" -> "未审核"
         "pending" -> "待处理"
         else -> approvalStatus
     }

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

@@ -32,8 +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.ui.base.PageBase
-import com.iscs.bozzys.ui.base.Title
+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.pages.compose.FormContainer
 import com.iscs.bozzys.ui.pages.edit.step.openPageEditStep

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

@@ -41,8 +41,8 @@ import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import com.iscs.bozzys.R
-import com.iscs.bozzys.ui.base.PageBase
-import com.iscs.bozzys.ui.base.Title
+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.theme.Text

+ 29 - 21
app/src/main/java/com/iscs/bozzys/ui/pages/detail/task/PageDetailTask.kt

@@ -38,8 +38,8 @@ import androidx.compose.ui.unit.sp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.iscs.bozzys.R
 import com.iscs.bozzys.api.Task
-import com.iscs.bozzys.ui.base.PageBase
-import com.iscs.bozzys.ui.base.Title
+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.pages.compose.FormContainer
 import com.iscs.bozzys.ui.pages.compose.getTaskStatusName
@@ -87,7 +87,7 @@ class PageDetailTask : PageBase() {
         }
         val state by vm.state.collectAsState()
         Column(Modifier.fillMaxSize()) {
-            Title(pv, task.name)
+            Title(pv, task.currentNodeName ?: "")
             Column(
                 modifier = Modifier
                     .weight(1f)
@@ -125,7 +125,7 @@ class PageDetailTask : PageBase() {
                             .size(16.dp),
                         tint = MaterialTheme.colorScheme.primary
                     )
-                    Text("任务信息", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
+                    Text(task.name, fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
                 }
                 FlowRow(
                     verticalArrangement = Arrangement.Center, modifier = Modifier
@@ -322,15 +322,15 @@ class PageDetailTask : PageBase() {
                                 painter = painterResource(R.drawable.job_close),
                                 contentDescription = null,
                                 modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
+                                    .padding(end = 8.dp)
+                                    .size(18.dp),
                                 tint = Color(0xFFFF4D4F)
                             )
-                            Text("取消", fontSize = 14.sp, lineHeight = 14.sp, color = Color(0xFFFF4D4F))
+                            Text("取消", fontSize = 16.sp, lineHeight = 16.sp, fontWeight = FontWeight.Bold, color = Color(0xFFFF4D4F))
                         }
                     }
                     Button(
-                        { vm.onSubmit() },
+                        { vm.onSubmit { destroy() } },
                         enabled = state.node.approvalStatus != "approved",
                         modifier = Modifier
                             .padding(horizontal = 8.dp)
@@ -344,16 +344,22 @@ class PageDetailTask : PageBase() {
                                 painter = painterResource(R.drawable.job_finish),
                                 contentDescription = null,
                                 modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
+                                    .padding(end = 8.dp)
+                                    .size(18.dp),
                                 tint = Color.White
                             )
-                            Text("提交", fontSize = 14.sp, lineHeight = 14.sp, color = Color.White)
+                            Text(
+                                if (state.node.type == "complete") "完成" else "提交",
+                                fontSize = 16.sp,
+                                lineHeight = 16.sp,
+                                fontWeight = FontWeight.Bold,
+                                color = Color.White
+                            )
                         }
                     }
                 } else if (listOf("review").contains(state.node.type)) {
                     Button(
-                        { destroy() },
+                        { vm.onSubmit("rejected") { destroy() } },
                         enabled = state.node.approvalStatus != "approved", // 审核通过禁用
                         modifier = Modifier
                             .padding(horizontal = 8.dp)
@@ -361,21 +367,23 @@ class PageDetailTask : PageBase() {
                             .height(50.dp)
                             .clip(RoundedCornerShape(12.dp)),
                         colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFFFF0E6)),
-                        shape = RoundedCornerShape(12.dp)
+                        shape = RoundedCornerShape(12.dp),
+                        contentPadding = PaddingValues(horizontal = 5.dp)
                     ) {
                         Row(verticalAlignment = Alignment.CenterVertically) {
                             Icon(
                                 painter = painterResource(R.drawable.job_close),
                                 contentDescription = null,
                                 modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
+                                    .padding(end = 8.dp)
+                                    .size(18.dp),
                                 tint = if (state.node.approvalStatus == "approved") Color.White else Color(0xFFFF4D4F)
                             )
                             Text(
                                 "审核不通过",
-                                fontSize = 14.sp,
-                                lineHeight = 14.sp,
+                                fontSize = 16.sp,
+                                lineHeight = 16.sp,
+                                fontWeight = FontWeight.Bold,
                                 color = if (state.node.approvalStatus == "approved") Color.White else Color(
                                     0xFFFF4D4F
                                 )
@@ -383,7 +391,7 @@ class PageDetailTask : PageBase() {
                         }
                     }
                     Button(
-                        {},
+                        { vm.onSubmit { destroy() } },
                         enabled = state.node.approvalStatus != "approved", // 审核通过禁用
                         modifier = Modifier
                             .padding(horizontal = 8.dp)
@@ -397,11 +405,11 @@ class PageDetailTask : PageBase() {
                                 painter = painterResource(R.drawable.job_finish),
                                 contentDescription = null,
                                 modifier = Modifier
-                                    .padding(end = 5.dp)
-                                    .size(16.dp),
+                                    .padding(end = 8.dp)
+                                    .size(18.dp),
                                 tint = Color.White
                             )
-                            Text("审核通过", fontSize = 14.sp, lineHeight = 14.sp, color = Color.White)
+                            Text("审核通过", fontSize = 16.sp, lineHeight = 16.sp, fontWeight = FontWeight.Bold, color = Color.White)
                         }
                     }
                 }

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

@@ -55,8 +55,11 @@ import androidx.compose.ui.zIndex
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.iscs.bozzys.R
-import com.iscs.bozzys.ui.base.PageBase
-import com.iscs.bozzys.ui.base.Title
+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.TipsDialog
+import com.iscs.bozzys.ui.common.Title
 import com.iscs.bozzys.ui.pages.compose.CardContainer
 import com.iscs.bozzys.ui.pages.compose.FormContainer
 import com.iscs.bozzys.ui.pages.edit.step.compose.NodeItem
@@ -86,6 +89,7 @@ class PageEditStep : PageBase() {
             vm.loading.showLoading()
             vm.init(intent.getIntExtra("job_id", 0))
         }
+        // 页面布局容器
         Column(Modifier.fillMaxSize()) {
             Title(pv, "作业流程管理")
             Box(Modifier.weight(1f)) {
@@ -148,7 +152,28 @@ class PageEditStep : PageBase() {
                             if (imeBottom <= 0) {
                                 Spacer(Modifier.height(10.dp))
                                 Button(
-                                    { vm.onNodeSave() },
+                                    {
+                                        vm.onNodeSave {
+                                            // 优化体验,这里如果保存成功,自动检测是否需要进行下一步操作
+                                            vm.onNext { complete, node ->
+                                                if (node != null) {
+                                                    // 刷新节点位置
+                                                    vm.updateNode(node)
+                                                    // 将当前Node平移到屏幕中间
+                                                    RefreshEventBus.onRefreshData(RefreshEvent.UpdateNodeToMapTopCenter.apply { any = node })
+                                                    lifecycleScope.launch {
+                                                        // 给移动位置增加点动画效果
+                                                        delay(280)
+                                                        // 弹出Dialog
+                                                        vm.formDialogShow(true)
+                                                    }
+                                                }
+                                                if (complete) {
+                                                    vm.showTipsDialog("提示", "当前任务节点信息都已录入,确认要进行下一步操作吗?")
+                                                }
+                                            }
+                                        }
+                                    },
                                     modifier = Modifier
                                         .fillMaxWidth()
                                         .height(50.dp)
@@ -171,7 +196,25 @@ class PageEditStep : PageBase() {
                     .padding(top = 15.dp, bottom = (15f + pv.calculateBottomPadding().value).dp)
             ) {
                 Button(
-                    onClick = { }, modifier = Modifier
+                    onClick = {
+                        vm.onNext { complete, node ->
+                            if (node != null) {
+                                // 刷新节点位置
+                                vm.updateNode(node)
+                                // 将当前Node平移到屏幕中间
+                                RefreshEventBus.onRefreshData(RefreshEvent.UpdateNodeToMapTopCenter.apply { any = node })
+                                lifecycleScope.launch {
+                                    // 给移动位置增加点动画效果
+                                    delay(280)
+                                    // 弹出Dialog
+                                    vm.formDialogShow(true)
+                                }
+                            }
+                            if (complete) {
+                                vm.showTipsDialog("提示", "当前任务节点信息都已录入,确认要进行下一步操作吗?")
+                            }
+                        }
+                    }, modifier = Modifier
                         .padding(horizontal = 16.dp)
                         .fillMaxWidth()
                         .height(50.dp)
@@ -192,6 +235,14 @@ class PageEditStep : PageBase() {
                 }
             }
         }
+        // Dialog
+        TipsDialog(show = state.showTipsDialog, content = state.showTipsDialogContent, onCancel = {
+            vm.hideTipsDialog()
+        }, onConfirm = {
+            vm.hideTipsDialog()
+            // 这里需要跳转到下一个页面,且关闭当前页面
+            destroyDelay(500)
+        })
     }
 
 

+ 15 - 0
app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/compose/ZoomContainer.kt

@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 import androidx.compose.material3.Icon
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -37,7 +38,10 @@ import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
 import com.iscs.bozzys.R
+import com.iscs.bozzys.event.RefreshEvent
+import com.iscs.bozzys.event.RefreshEventBus
 import com.iscs.bozzys.ui.pages.compose.CardContainer
+import kotlinx.coroutines.async
 import kotlinx.coroutines.launch
 import kotlin.math.ceil
 import kotlin.math.floor
@@ -127,6 +131,17 @@ fun ZoomPanContainer(
         }
     }
 
+    LaunchedEffect(Unit) {
+        async {
+            RefreshEventBus.events.collect {
+                when (it) {
+                    is RefreshEvent.UpdateNodeToMapTopCenter -> animateNodeToTopCenter(it.any as NodeUI)
+                    else -> {}
+                }
+            }
+        }
+    }
+
     Box(
         modifier = modifier
             .clipToBounds()

+ 50 - 41
app/src/main/java/com/iscs/bozzys/ui/pages/home/HomeCompose.kt

@@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.aspectRatio
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
@@ -167,7 +166,7 @@ private fun TODO(vmHome: VMHome) {
             .padding(top = 3.dp)
     ) {
         Text(
-            "中午好,今天有${state.todoCountPending}个待办任务", Modifier
+            "今天有${state.todoCountRunning}个待办任务", Modifier
                 .fillMaxWidth()
                 .height(51.dp)
                 .background(Color(0xFFE6F7FF)),
@@ -181,55 +180,65 @@ private fun TODO(vmHome: VMHome) {
             Column(
                 Modifier
                     .weight(1f)
-                    .aspectRatio(1f)
+                    .height(80.dp)
                     .clip(RoundedCornerShape(12.dp))
                     .background(Color(0xFFFFF5EB)),
-                verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally
+                horizontalAlignment = Alignment.CenterHorizontally,
+                verticalArrangement = Arrangement.Center
             ) {
-                Text("${state.todoCountPending}", fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color(0xFFFF4500))
-                Text("待处理", fontSize = 12.sp, color = Text, modifier = Modifier.padding(top = 5.dp))
-                Icon(
-                    painterResource(R.drawable.job_todo), contentDescription = null, modifier = Modifier
-                        .padding(top = 3.dp)
-                        .size(16.dp),
-                    tint = Text
-                )
-            }
-            Spacer(Modifier.width(10.dp))
-            Column(
-                Modifier
-                    .weight(1f)
-                    .aspectRatio(1f)
-                    .clip(RoundedCornerShape(12.dp))
-                    .background(Color(0xFFFFF5EB)),
-                verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally
-            ) {
-                Text("${state.todoCountRunning}", fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color(0xFFFFA500))
-                Text("进行中", fontSize = 12.sp, color = Text, modifier = Modifier.padding(top = 5.dp))
-                Icon(
-                    painterResource(R.drawable.job_ing), contentDescription = null, modifier = Modifier
-                        .padding(top = 3.dp)
-                        .size(16.dp),
-                    tint = Text
-                )
+                Text("${state.todoCountRunning}", fontSize = 24.sp, lineHeight = 24.sp, fontWeight = FontWeight.Bold, color = Color(0xFFFFA500))
+                Row(
+                    modifier = Modifier.fillMaxWidth(),
+                    horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically
+                ) {
+                    Icon(
+                        painterResource(R.drawable.job_ing),
+                        contentDescription = null,
+                        modifier = Modifier
+                            .padding(end = 5.dp)
+                            .size(16.dp),
+                        tint = Text
+                    )
+                    Text(
+                        "进行中",
+                        fontSize = 14.sp,
+                        color = Text,
+                        fontWeight = FontWeight.Bold,
+                    )
+                }
+
             }
             Spacer(Modifier.width(10.dp))
             Column(
                 Modifier
                     .weight(1f)
-                    .aspectRatio(1f)
+                    .height(80.dp)
                     .clip(RoundedCornerShape(12.dp))
                     .background(Color(0xFFFFF5EB)),
-                verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally
+                horizontalAlignment = Alignment.CenterHorizontally,
+                verticalArrangement = Arrangement.Center
             ) {
-                Text("${state.todoCountFinish}", fontSize = 24.sp, fontWeight = FontWeight.Bold, color = Color(0xFF32CD32))
-                Text("本月完成", fontSize = 12.sp, color = Text, modifier = Modifier.padding(top = 5.dp))
-                Icon(
-                    painterResource(R.drawable.job_finish), contentDescription = null, modifier = Modifier
-                        .padding(top = 3.dp)
-                        .size(16.dp),
-                    tint = Text
-                )
+                Text("${state.todoCountFinish}", fontSize = 24.sp, lineHeight = 24.sp, fontWeight = FontWeight.Bold, color = Color(0xFF32CD32))
+                Row(
+                    modifier = Modifier.fillMaxWidth(),
+                    horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically
+                ) {
+                    Icon(
+                        painterResource(R.drawable.job_finish),
+                        contentDescription = null,
+                        modifier = Modifier
+                            .padding(end = 5.dp)
+                            .size(16.dp),
+                        tint = Text
+                    )
+                    Text(
+                        "本月完成",
+                        fontSize = 14.sp,
+                        color = Text,
+                        fontWeight = FontWeight.Bold,
+                    )
+                }
+
             }
             Spacer(Modifier.width(10.dp))
         }
@@ -249,7 +258,7 @@ private fun TaskList(pv: PaddingValues, vmHome: VMHome) {
             .padding(bottom = pv.calculateBottomPadding())
             .fillMaxSize()
             .verticalScroll(state = rememberScrollState())
-            .padding(top = 205.dp)
+            .padding(top = 164.dp)
     ) {
         Row(
             Modifier

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

@@ -43,7 +43,9 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.zIndex
 import com.iscs.bozzys.R
+import com.iscs.bozzys.ui.common.Empty
 import com.iscs.bozzys.ui.pages.compose.JobListItem
+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.VMHome
 import com.iscs.bozzys.ui.theme.Main
@@ -101,9 +103,9 @@ private fun TopToolBar(pv: PaddingValues, vmHome: VMHome) {
                     .clickable(onClick = {
                         // 前期SOP没有做流程选择,这里先直接跳转到创建页面
                         // ctx.openPageSelectJobType()
-                        // ctx.openPageCreateJob()
+                        ctx.openPageCreateJob()
                         // 测试填写表单数据
-                        ctx.openPageEditStep(19)
+                        // ctx.openPageEditStep(36)
                     })
                     .padding(6.dp),
                 tint = Color.White
@@ -151,7 +153,6 @@ private fun TopToolBar(pv: PaddingValues, vmHome: VMHome) {
 @Composable
 private fun FilterBar(vm: VMHome) {
     val state by vm.state.collectAsState()
-    var idx by remember { mutableStateOf(0) }
     Row(
         modifier = Modifier
             .padding(vertical = 5.dp)
@@ -160,59 +161,39 @@ private fun FilterBar(vm: VMHome) {
             .padding(top = 6.dp)
     ) {
         Text(
-            "全部", fontSize = 14.sp, color = if (idx == 0) Color.White else Color(0xFF666666),
+            "进行中", fontSize = 14.sp, color = if (state.jobPage.type == "running") Color.White else Color(0xFF666666),
             modifier = Modifier
                 .padding(end = 10.dp)
                 .clip(RoundedCornerShape(50))
-                .background(
-                    color = if (idx == 0) Main else Color(0xFFF0F0F0)
-                )
-                .clickable(onClick = {
-                    idx = 0
-                    vm.getJobList(state.jobPage.copy(page = 1, type = ""))
-                })
+                .background(color = if (state.jobPage.type == "running") Main else Color(0xFFF0F0F0))
+                .clickable(onClick = { vm.getJobList(state.jobPage.copy(page = 1, type = "running")) })
                 .padding(horizontal = 12.dp, vertical = 5.dp),
         )
         Text(
-            "进行中", fontSize = 14.sp, color = if (idx == 1) Color.White else Color(0xFF666666),
+            "已完成", fontSize = 14.sp, color = if (state.jobPage.type == "completed") Color.White else Color(0xFF666666),
             modifier = Modifier
                 .padding(end = 10.dp)
                 .clip(RoundedCornerShape(50))
-                .background(
-                    color = if (idx == 1) Main else Color(0xFFF0F0F0)
-                )
-                .clickable(onClick = {
-                    idx = 1
-                    vm.getJobList(state.jobPage.copy(page = 1, type = "running"))
-                })
+                .background(color = if (state.jobPage.type == "completed") Main else Color(0xFFF0F0F0))
+                .clickable(onClick = { vm.getJobList(state.jobPage.copy(page = 1, type = "completed")) })
                 .padding(horizontal = 12.dp, vertical = 5.dp),
         )
         Text(
-            "已完成", fontSize = 14.sp, color = if (idx == 2) Color.White else Color(0xFF666666),
+            "已取消", fontSize = 14.sp, color = if (state.jobPage.type == "cancelled") Color.White else Color(0xFF666666),
             modifier = Modifier
                 .padding(end = 10.dp)
                 .clip(RoundedCornerShape(50))
-                .background(
-                    color = if (idx == 2) Main else Color(0xFFF0F0F0)
-                )
-                .clickable(onClick = {
-                    idx = 2
-                    vm.getJobList(state.jobPage.copy(page = 1, type = "completed"))
-                })
+                .background(color = if (state.jobPage.type == "cancelled") Main else Color(0xFFF0F0F0))
+                .clickable(onClick = { vm.getJobList(state.jobPage.copy(page = 1, type = "cancelled")) })
                 .padding(horizontal = 12.dp, vertical = 5.dp),
         )
         Text(
-            "已取消", fontSize = 14.sp, color = if (idx == 3) Color.White else Color(0xFF666666),
+            "全部", fontSize = 14.sp, color = if (state.jobPage.type == "") Color.White else Color(0xFF666666),
             modifier = Modifier
                 .padding(end = 10.dp)
                 .clip(RoundedCornerShape(50))
-                .background(
-                    color = if (idx == 3) Main else Color(0xFFF0F0F0)
-                )
-                .clickable(onClick = {
-                    idx = 3
-                    vm.getJobList(state.jobPage.copy(page = 1, type = "cancelled"))
-                })
+                .background(color = if (state.jobPage.type == "") Main else Color(0xFFF0F0F0))
+                .clickable(onClick = { vm.getJobList(state.jobPage.copy(page = 1, type = "")) })
                 .padding(horizontal = 12.dp, vertical = 5.dp),
         )
     }
@@ -255,6 +236,7 @@ private fun TaskList(vm: VMHome) {
 //                Box(Modifier.background(Color.White)) {
 //                    DateTitle("${list[topIdx]}")
 //                }
+                if (state.jobList.isEmpty()) Empty("暂无数据")
             }
         }
     }

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

@@ -36,7 +36,7 @@ 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.ui.base.PageBase
+import com.iscs.bozzys.ui.common.PageBase
 import com.iscs.bozzys.ui.pages.vm.VMHome
 
 /**

+ 15 - 32
app/src/main/java/com/iscs/bozzys/ui/pages/home/TasksCompose.kt

@@ -39,7 +39,9 @@ import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.zIndex
+import com.iscs.bozzys.ui.common.Empty
 import com.iscs.bozzys.ui.pages.compose.TaskListItem
+import com.iscs.bozzys.ui.pages.vm.StatePage
 import com.iscs.bozzys.ui.pages.vm.VMHome
 import com.iscs.bozzys.ui.theme.Main
 import com.iscs.bozzys.ui.theme.Text
@@ -87,7 +89,11 @@ private fun TopToolBar(pv: PaddingValues, vm: VMHome) {
             verticalAlignment = Alignment.CenterVertically
         ) {
             Text("我的任务", fontSize = 18.sp, color = Color.White, fontWeight = FontWeight.Medium)
-            Spacer(Modifier.weight(1f).height(36.dp))
+            Spacer(
+                Modifier
+                    .weight(1f)
+                    .height(36.dp)
+            )
 
             // 任务没有新建,只有查看
 //            Icon(
@@ -142,7 +148,7 @@ private fun TopToolBar(pv: PaddingValues, vm: VMHome) {
  */
 @Composable
 private fun FilterBar(vm: VMHome) {
-    var idx by remember { mutableStateOf(0) }
+    val state by vm.state.collectAsState()
     Row(
         modifier = Modifier
             .padding(vertical = 5.dp)
@@ -151,47 +157,23 @@ private fun FilterBar(vm: VMHome) {
             .padding(top = 6.dp)
     ) {
         Text(
-            "全部", fontSize = 14.sp, color = if (idx == 0) Color.White else Color(0xFF666666),
-            modifier = Modifier
-                .padding(end = 10.dp)
-                .clip(RoundedCornerShape(50))
-                .background(
-                    color = if (idx == 0) Main else Color(0xFFF0F0F0)
-                )
-                .clickable(onClick = { idx = 0 })
-                .padding(horizontal = 12.dp, vertical = 5.dp),
-        )
-        Text(
-            "进行中", fontSize = 14.sp, color = if (idx == 1) Color.White else Color(0xFF666666),
+            "进行中", fontSize = 14.sp, color = if (state.taskPage.type == "running") Color.White else Color(0xFF666666),
             modifier = Modifier
                 .padding(end = 10.dp)
                 .clip(RoundedCornerShape(50))
                 .background(
-                    color = if (idx == 1) Main else Color(0xFFF0F0F0)
+                    color = if (state.taskPage.type == "running") Main else Color(0xFFF0F0F0)
                 )
-                .clickable(onClick = { idx = 1 })
+                .clickable(onClick = { vm.getTaskList(StatePage(type = "running", page = 1)) })
                 .padding(horizontal = 12.dp, vertical = 5.dp),
         )
         Text(
-            "已完成", fontSize = 14.sp, color = if (idx == 2) Color.White else Color(0xFF666666),
+            "已完成", fontSize = 14.sp, color = if (state.taskPage.type == "approved") Color.White else Color(0xFF666666),
             modifier = Modifier
                 .padding(end = 10.dp)
                 .clip(RoundedCornerShape(50))
-                .background(
-                    color = if (idx == 2) Main else Color(0xFFF0F0F0)
-                )
-                .clickable(onClick = { idx = 2 })
-                .padding(horizontal = 12.dp, vertical = 5.dp),
-        )
-        Text(
-            "已取消", fontSize = 14.sp, color = if (idx == 3) Color.White else Color(0xFF666666),
-            modifier = Modifier
-                .padding(end = 10.dp)
-                .clip(RoundedCornerShape(50))
-                .background(
-                    color = if (idx == 3) Main else Color(0xFFF0F0F0)
-                )
-                .clickable(onClick = { idx = 3 })
+                .background(color = if (state.taskPage.type == "approved") Main else Color(0xFFF0F0F0))
+                .clickable(onClick = { vm.getTaskList(StatePage(type = "approved", page = 1)) })
                 .padding(horizontal = 12.dp, vertical = 5.dp),
         )
     }
@@ -235,6 +217,7 @@ private fun TaskList(vm: VMHome) {
 //                Box(Modifier.background(Color.White)) {
 //                    DateTitle("${list[topIdx]}")
 //                }
+                if (state.taskList.isEmpty()) Empty("暂无数据")
             }
         }
     }

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/pages/login/PageLogin.kt

@@ -46,7 +46,7 @@ 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.base.PageBase
+import com.iscs.bozzys.ui.common.PageBase
 import com.iscs.bozzys.ui.pages.home.openPageHome
 import com.iscs.bozzys.ui.pages.vm.VMLogin
 import com.iscs.bozzys.ui.theme.Main

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/pages/message/PageMessage.kt

@@ -42,7 +42,7 @@ import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import com.iscs.bozzys.R
-import com.iscs.bozzys.ui.base.PageBase
+import com.iscs.bozzys.ui.common.PageBase
 import com.iscs.bozzys.ui.theme.Text
 
 /**

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

@@ -41,8 +41,8 @@ import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import com.iscs.bozzys.R
-import com.iscs.bozzys.ui.base.PageBase
-import com.iscs.bozzys.ui.base.Title
+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.pages.create.job.openPageCreateJob
 import com.iscs.bozzys.ui.theme.Main

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

@@ -5,7 +5,7 @@ import com.iscs.bozzys.api.ApiRequest
 import com.iscs.bozzys.api.ApiRequest.getResponse
 import com.iscs.bozzys.api.FormField
 import com.iscs.bozzys.api.FormOption
-import com.iscs.bozzys.ui.base.VMBase
+import com.iscs.bozzys.ui.common.VMBase
 import com.iscs.bozzys.ui.pages.compose.getFormListByJsonList
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -97,7 +97,7 @@ class VMCreateJob : VMBase() {
             }.onFailure {
                 delay(500)
                 loading.emit(StateLoading())
-                toast.emit(it.getResponse().msg)
+                toast.emit(it.getResponse<Int>().msg)
             }
         }
     }

+ 38 - 12
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMDetailTask.kt

@@ -1,5 +1,6 @@
 package com.iscs.bozzys.ui.pages.vm
 
+import android.util.Log
 import androidx.lifecycle.viewModelScope
 import com.iscs.bozzys.api.ApiRequest
 import com.iscs.bozzys.api.ApiRequest.getResponse
@@ -7,7 +8,10 @@ import com.iscs.bozzys.api.FormField
 import com.iscs.bozzys.api.Node
 import com.iscs.bozzys.api.Task
 import com.iscs.bozzys.api.TaskFormInfo
-import com.iscs.bozzys.ui.base.VMBase
+import com.iscs.bozzys.event.RefreshEvent
+import com.iscs.bozzys.event.RefreshEventBus
+import com.iscs.bozzys.ui.common.VMBase
+import com.iscs.bozzys.ui.pages.compose.checkCanCommitReturnTips
 import com.iscs.bozzys.ui.pages.compose.getFormListByJsonList
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -51,7 +55,8 @@ class VMDetailTask : VMBase() {
                     val json = Json { ignoreUnknownKeys = true }
                     formInfo = json.decodeFromString<TaskFormInfo>(taskInfo.formData)
                     // 有些表单不需要显示的逻辑处理
-                    if (listOf("isolation").contains(taskInfo.type)) formInfo = TaskFormInfo()
+                    // 解除隔离和还锁不需要录入信息
+                    if (listOf("isolation", "returnLock").contains(taskInfo.type)) formInfo = TaskFormInfo()
                     _state.value = _state.value.copy(forms = formInfo.fields.getFormListByJsonList(), node = taskInfo)
                     delay(500)
                     loading.emit(StateLoading())
@@ -64,13 +69,13 @@ class VMDetailTask : VMBase() {
                     }.onFailure { err ->
                         delay(500)
                         loading.emit(StateLoading())
-                        toast.emit("获取表单异常:${err.getResponse().msg}")
+                        toast.emit("获取表单异常:${err.getResponse<TaskFormInfo>().msg}")
                     }
                 }
             }.onFailure {
                 delay(500)
                 loading.emit(StateLoading(show = false))
-                toast.emit("获取任务详情异常:${it.getResponse().msg}")
+                toast.emit("获取任务详情异常:${it.getResponse<Any>().msg}")
             }
         }
     }
@@ -78,14 +83,35 @@ class VMDetailTask : VMBase() {
     /**
      * 提交按钮被点击
      */
-    fun onSubmit() {
-        val forms = _state.value.forms
-        val params = mutableMapOf<String, Any>("nodeId" to task.nodeId, "approvalStatus" to "approved")
-        val json = Json
-        // 重点在FormData
-        val fields = forms.map { json.encodeToString(it) }
-        val formInfo = this.formInfo.copy(fields = fields)
-        params["formData"] = json.encodeToString(formInfo)
+    fun onSubmit(status: String = "approved", done: () -> Unit) {
+        viewModelScope.launch {
+            val forms = _state.value.forms
+            val tips = forms.checkCanCommitReturnTips()
+            if (tips.isNotEmpty()) {
+                toast.emit(tips)
+                return@launch
+            }
+            val params = mutableMapOf<String, Any>("nodeId" to task.nodeId, "approvalStatus" to status)
+            val json = Json
+            // 重点在FormData
+            val fields = forms.map { json.encodeToString(it) }
+            val formInfo = this@VMDetailTask.formInfo.copy(fields = fields)
+            params["formData"] = json.encodeToString(formInfo)
+            loading.emit(StateLoading(show = true))
+            ApiRequest.updateJobNodeApproval(params).onSuccess {
+                delay(1000)
+                loading.emit(StateLoading())
+                toast.emit("提交成功")
+                // 通知首页刷新数据
+                RefreshEventBus.onRefreshData(RefreshEvent.HomeData)
+                delay(500)
+                done()
+            }.onFailure {
+                delay(1000)
+                loading.emit(StateLoading())
+                toast.emit("提交失败:${it.getResponse<Any>().msg}")
+            }
+        }
     }
 }
 

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

@@ -1,6 +1,5 @@
 package com.iscs.bozzys.ui.pages.vm
 
-import android.util.Log
 import androidx.compose.runtime.MutableState
 import androidx.compose.runtime.mutableStateMapOf
 import androidx.compose.runtime.mutableStateOf
@@ -14,7 +13,7 @@ import com.iscs.bozzys.api.FormField
 import com.iscs.bozzys.api.IsolationPoint
 import com.iscs.bozzys.api.TaskFormInfo
 import com.iscs.bozzys.api.User
-import com.iscs.bozzys.ui.base.VMBase
+import com.iscs.bozzys.ui.common.VMBase
 import com.iscs.bozzys.ui.pages.edit.step.compose.Connection
 import com.iscs.bozzys.ui.pages.edit.step.compose.NodeUI
 import kotlinx.coroutines.delay
@@ -137,7 +136,7 @@ class VMEditStep : VMBase() {
                 loading.emit(StateLoading())
             }.onFailure {
                 loading.emit(StateLoading())
-                toast.emit(it.getResponse().msg)
+                toast.emit(it.getResponse<Any>().msg)
             }
         }
     }
@@ -214,11 +213,11 @@ class VMEditStep : VMBase() {
     /**
      * 节点保存按钮点击
      */
-    fun onNodeSave() {
+    fun onNodeSave(done: () -> Unit) {
         viewModelScope.launch {
             val node = _state.value.node.value.node ?: return@launch
             val forms = _state.value.nodeForms
-            val tips = node.checkSubmitParams(forms)
+            val tips = node.checkCanSubmit(forms)
             if (tips.isNotEmpty()) {
                 toast.emit(tips)
                 return@launch
@@ -234,13 +233,59 @@ class VMEditStep : VMBase() {
                 loading.emit(StateLoading())
                 _state.value = _state.value.copy(showFormDialog = false)
                 toast.emit("保存成功")
+                done()
             }.onFailure {
                 delay(500)
                 loading.emit(StateLoading())
-                toast.emit(it.getResponse().msg)
+                toast.emit(it.getResponse<Any>().msg)
             }
         }
     }
+
+    /**
+     * 查找下一个需要编辑的节点,若没有则提示是否继续
+     */
+    fun onNext(done: (complete: Boolean, node: NodeUI?) -> Unit) {
+        viewModelScope.launch {
+            val nodes = _state.value.nodes
+            // 找到下一个需要处理的节点
+            var record: NodeUI? = null
+            for (nui in nodes) {
+                val node = nui.value.node ?: continue
+                val tips = node.checkCanSubmit(
+                    node.buildFormList(
+                        workerUserList,
+                        lockerUserList,
+                        groupUserList,
+                        workFormList,
+                        isolationMethodList,
+                        isolationList,
+                        isLocker = node.isolationType == "1"
+                    )
+                )
+                if (tips.isNotEmpty()) {
+                    record = nui.value
+                    done(false, record)
+                    break
+                }
+            }
+            if (record == null) done(true, null)
+        }
+    }
+
+    /**
+     * 显示提示Dialog
+     */
+    fun showTipsDialog(title: String = "", content: String = "") {
+        _state.value = _state.value.copy(showTipsDialog = true, showTipsDialogContent = content)
+    }
+
+    /**
+     * 隐藏提示Dialog
+     */
+    fun hideTipsDialog() {
+        _state.value = _state.value.copy(showTipsDialog = false)
+    }
 }
 
 /**
@@ -258,4 +303,6 @@ data class StateEditStep(
     val nodeForms: List<FormField> = listOf(),
     val connections: List<Connection> = emptyList(),
     val showFormDialog: Boolean = false,
+    val showTipsDialog: Boolean = false,
+    val showTipsDialogContent: String = "",
 )

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

@@ -5,12 +5,17 @@ import androidx.lifecycle.viewModelScope
 import com.iscs.bozzys.R
 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.api.Message
+import com.iscs.bozzys.api.PageRsp
 import com.iscs.bozzys.api.Task
-import com.iscs.bozzys.ui.base.VMBase
+import com.iscs.bozzys.event.RefreshEvent
+import com.iscs.bozzys.event.RefreshEventBus
+import com.iscs.bozzys.ui.common.VMBase
 import com.iscs.bozzys.ui.theme.Text
 import com.iscs.bozzys.utils.Storage
+import kotlinx.coroutines.async
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -21,7 +26,11 @@ class VMHome : VMBase() {
     private val _state = MutableStateFlow(StateHome())
     val state = _state.asStateFlow()
 
+    /**
+     * 页面初始化操作
+     */
     fun init() {
+        initEvent()
         viewModelScope.launch {
             val navs = listOf(
                 NavBarItem(0, "首页", R.drawable.home),
@@ -32,90 +41,170 @@ class VMHome : VMBase() {
             _state.value = _state.value.copy(navs = navs, username = Storage.readUserName())
             // 延时等待页面配置数据初步加载完成
             delay(500)
-            getHomeTaskData(showLoading = true)
-            // 获取作业列表
-            getJobList(_state.value.jobPage)
-            // 获取任务列表数据
-            getTaskList(_state.value.taskPage)
+            refreshHome(true)
+        }
+    }
+
+    /**
+     * 初始化事件
+     */
+    private fun initEvent() {
+        viewModelScope.async {
+            RefreshEventBus.events.collect { event ->
+                when (event) {
+                    is RefreshEvent.HomeData -> refreshHome()
+                    else -> {}
+                }
+            }
         }
     }
 
+    /**
+     * 更新底部选择的导航
+     */
     fun navigationToId(id: Int) {
         val idx = _state.value.navs.indexOfFirst { it.id == id }
         _state.value = _state.value.copy(navigationId = idx)
     }
 
-    fun updateUnReadCount() {
+    /**
+     * 更新未读消息
+     *
+     * @param navId 导航id
+     * @param count 未读条数
+     */
+    fun updateUnReadCount(navId: Int, count: Int) {
         // 更新未读消息数演示
-        val navs = _state.value.navs.map { it.copy(count = 12) }
+        val navs = _state.value.navs.map { if (it.id == navId) it.copy(count = count) else it }
         _state.value = _state.value.copy(navs = ArrayList(navs))
     }
 
+    /**
+     * 刷新数据
+     */
     fun onRefreshHomeTab() {
         _state.value = _state.value.copy(isHomeTabRefresh = true)
         // 下拉刷新,刷新数据
-        getHomeTaskData()
+        refreshHome()
     }
 
     /**
-     * 获取作业数据列表
+     * 获取首页数据
      */
-    fun getHomeTaskData(showLoading: Boolean = false) {
+    fun getHomeTaskData(showLoading: Boolean = false, done: () -> Unit = {}) {
         viewModelScope.launch {
             loading.emit(StateLoading(show = showLoading))
-            // pending      待处理
-            // unaudited    未审核
-            // approved     已审核
-            // "approvalStatus" to "unaudited"
-            ApiRequest.getTasks(hashMapOf("pageNo" to 1, "pageSize" to 3)).onSuccess {
-                val list = it.data?.list ?: listOf()
-                // 后面看需求是否需要限制
-                // val maxLimitList = if (list.isEmpty()) list else list.subList(0, min(list.size, 3))
-                _state.value = _state.value.copy(homeTasks = ArrayList(list), todoCountPending = it.data?.total ?: 0, isHomeTabRefresh = false)
+            // 查询顶部的任务个数
+            val statisticsRsp = ApiRequest.getTaskStatistics().getOrElse { it.getResponse() }
+            if (!statisticsRsp.code.isCodeOk()) {
+                _state.value = _state.value.copy(isHomeTabRefresh = false)
                 delay(500)
                 loading.emit(StateLoading())
-            }.onFailure {
+                toast.emit(statisticsRsp.msg)
+                return@launch
+            }
+            // 查询进行中的作业
+            val jobRsp = ApiRequest.getJobs(hashMapOf("pageNo" to 1, "pageSize" to 1, "status" to "running")).getOrElse { it.getResponse() }
+            if (!jobRsp.code.isCodeOk()) {
+                _state.value = _state.value.copy(isHomeTabRefresh = false)
+                delay(500)
+                loading.emit(StateLoading())
+                toast.emit(jobRsp.msg)
+                return@launch
+            }
+            // 查询待审核的任务
+            val tasksRsp =
+                ApiRequest.getTasks(hashMapOf("pageNo" to 1, "pageSize" to 3, "approvalStatus" to "running")).getOrElse { it.getResponse() }
+            if (!tasksRsp.code.isCodeOk()) {
                 _state.value = _state.value.copy(isHomeTabRefresh = false)
                 delay(500)
                 loading.emit(StateLoading())
-                toast.emit(it.getResponse().msg)
+                toast.emit(statisticsRsp.msg)
+                return@launch
             }
+            val tasks = tasksRsp.data?.list ?: emptyList()
+            // 后面看需求是否需要限制
+            // val maxLimitList = if (list.isEmpty()) list else list.subList(0, min(list.size, 3))
+            _state.value = _state.value.copy(
+                homeTasks = ArrayList(tasks),
+                todoCountRunning = statisticsRsp.data?.inProgressCount ?: 0,
+                todoCountFinish = statisticsRsp.data?.completedCount ?: 0,
+                isHomeTabRefresh = false
+            )
+            // 更新进行中的任务数字
+            updateUnReadCount(1, jobRsp.data?.total ?: 0)
+            // 更新任务数字
+            updateUnReadCount(2, tasks.size)
+            delay(500)
+            loading.emit(StateLoading())
+            // 去加载作业和任务数据
+            done()
         }
     }
 
     /**
-     * 获取任务列表数据
+     * 获取任务列表数据,分页
      */
     fun getTaskList(page: StatePage) {
+        // 正在刷新禁止重复进行
+        if (_state.value.taskPage.isRefresh) return
         viewModelScope.launch {
             // 下拉即刷新
-            _state.value = _state.value.copy(taskPage = _state.value.taskPage.copy(isRefresh = true))
-            ApiRequest.getTasks(hashMapOf("pageNo" to page.page, "pageSize" to page.pageSize)).onSuccess {
+            _state.value = _state.value.copy(taskPage = _state.value.taskPage.copy(isRefresh = true, type = page.type))
+            ApiRequest.getTasks(hashMapOf("pageNo" to page.page, "pageSize" to page.pageSize, "approvalStatus" to page.type)).onSuccess {
                 if (page.page == 1) _state.value.taskList.clear()
                 val tasks = it.data?.list ?: emptyList()
                 _state.value.taskList.addAll(tasks)
+                delay(1000)
                 // 存储页面数据
                 _state.value = _state.value.copy(taskPage = page)
             }.onFailure {
+                delay(1000)
                 _state.value = _state.value.copy(taskPage = _state.value.taskPage.copy(isRefresh = false))
-                toast.emit(it.getResponse().msg)
+                toast.emit(it.getResponse<PageRsp<Task>>().msg)
             }
         }
     }
 
+    /**
+     * 获取作业数据,分页
+     */
     fun getJobList(page: StatePage) {
         viewModelScope.launch {
             // 下拉即刷新
-            _state.value = _state.value.copy(jobPage = _state.value.jobPage.copy(isRefresh = true))
+            _state.value = _state.value.copy(jobPage = _state.value.jobPage.copy(isRefresh = true, type = page.type))
             ApiRequest.getJobs(hashMapOf("pageNo" to page.page, "pageSize" to page.pageSize, "status" to page.type)).onSuccess {
                 if (page.page == 1) _state.value.jobList.clear()
                 val tasks = it.data?.list ?: emptyList()
                 _state.value.jobList.addAll(tasks)
                 // 存储页面数据
+                delay(1000)
                 _state.value = _state.value.copy(jobPage = page)
             }.onFailure {
+                delay(1000)
                 _state.value = _state.value.copy(jobPage = _state.value.jobPage.copy(isRefresh = false))
-                toast.emit(it.getResponse().msg)
+                toast.emit(it.getResponse<PageRsp<Job>>().msg)
+            }
+        }
+    }
+
+    /**
+     * 刷新首页全部数据
+     */
+    private fun refreshHome(showLoading: Boolean = false) {
+        viewModelScope.launch {
+            getHomeTaskData(showLoading = showLoading) {
+                val jobType = _state.value.jobPage.type.ifEmpty { "running" }
+                val taskType = _state.value.taskPage.type.ifEmpty { "running" }
+                // 刷新重置page
+                _state.value = _state.value.copy(
+                    jobPage = _state.value.jobPage.copy(page = 1, type = jobType),
+                    taskPage = _state.value.taskPage.copy(page = 1, type = taskType)
+                )
+                // 获取作业列表
+                getJobList(_state.value.jobPage)
+                // 获取任务列表数据
+                getTaskList(_state.value.taskPage)
             }
         }
     }
@@ -148,7 +237,6 @@ data class StateHome(
     val taskList: MutableList<Task> = mutableListOf(),
     val jobPage: StatePage = StatePage(),
     val jobList: MutableList<Job> = mutableListOf(),
-    val todoCountPending: Int = 0,
     val todoCountRunning: Int = 0,
     val todoCountFinish: Int = 0,
 )
@@ -177,7 +265,7 @@ data class NavBarItem(
  *
  * @param page      当前页面
  * @param pageSize  页面大小
- * @param type      请求数据类型
+ * @param type      请求数据类型 unaudited 默认取进行中的
  * @param isRefresh 是否正在刷新
  */
 data class StatePage(val page: Int = 1, val pageSize: Int = 10, val type: String = "", val isRefresh: Boolean = false)

+ 2 - 2
app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMLogin.kt

@@ -7,7 +7,7 @@ import androidx.lifecycle.viewModelScope
 import com.iscs.bozzys.R
 import com.iscs.bozzys.api.ApiRequest
 import com.iscs.bozzys.api.ApiRequest.getResponse
-import com.iscs.bozzys.ui.base.VMBase
+import com.iscs.bozzys.ui.common.VMBase
 import com.iscs.bozzys.utils.Storage
 import com.iscs.bozzys.utils.Storage.saveRefreshToken
 import com.iscs.bozzys.utils.Storage.saveToken
@@ -73,7 +73,7 @@ class VMLogin : VMBase() {
             }.onFailure {
                 delay(500)
                 loading.emit(StateLoading(show = false))
-                toast.emit(it.getResponse().msg)
+                toast.emit(it.getResponse<Any>().msg)
             }
         }
     }

+ 6 - 5
app/src/main/java/com/iscs/bozzys/utils/DateUtil.kt

@@ -3,7 +3,7 @@ package com.iscs.bozzys.utils
 import android.os.Build
 import java.text.SimpleDateFormat
 import java.time.Instant
-import java.time.LocalDate
+import java.time.LocalDateTime
 import java.time.ZoneId
 import java.time.format.DateTimeFormatter
 import java.util.Locale
@@ -24,15 +24,16 @@ object DateUtil {
      * 将传入的时间String格式化为pattern格式的时间戳
      */
     fun String.dateToTimestamp(pattern: String): Long {
+        if (this.isEmpty() || this == "0") return 0L
         val date = if (this.contains("T") && this.contains("Z")) {
             this.iso8601ToTimestamp().format(pattern)
         } else {
-            this.ifEmpty { System.currentTimeMillis().format(pattern) }
+            return this.toLong()
         }
         val formatter = DateTimeFormatter.ofPattern(pattern)
-        val localDate = LocalDate.parse(date, formatter)
-        return localDate
-            .atStartOfDay(ZoneId.systemDefault())
+        return LocalDateTime
+            .parse(date, formatter)
+            .atZone(ZoneId.systemDefault())
             .toInstant()
             .toEpochMilli()
     }

+ 14 - 6
app/src/main/java/com/iscs/bozzys/utils/SerializerUtil.kt → app/src/main/java/com/iscs/bozzys/utils/JsonUtil.kt

@@ -3,14 +3,13 @@ package com.iscs.bozzys.utils
 import kotlinx.serialization.ExperimentalSerializationApi
 import kotlinx.serialization.InternalSerializationApi
 import kotlinx.serialization.KSerializer
-import kotlinx.serialization.builtins.ListSerializer
-import kotlinx.serialization.builtins.serializer
 import kotlinx.serialization.descriptors.SerialDescriptor
 import kotlinx.serialization.descriptors.SerialKind
+import kotlinx.serialization.descriptors.StructureKind
 import kotlinx.serialization.descriptors.buildSerialDescriptor
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
-import kotlinx.serialization.json.Json
+import kotlinx.serialization.encoding.encodeCollection
 import kotlinx.serialization.json.JsonArray
 import kotlinx.serialization.json.JsonDecoder
 import kotlinx.serialization.json.JsonNull
@@ -24,8 +23,11 @@ import kotlinx.serialization.json.jsonPrimitive
 object StringToListSerializer : KSerializer<List<String>> {
 
     @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
-    override val descriptor: SerialDescriptor = buildSerialDescriptor("Placeholder", SerialKind.CONTEXTUAL)
+    override val descriptor: SerialDescriptor = buildSerialDescriptor("StringList", SerialKind.CONTEXTUAL)
 
+    /**
+     * 序列化为对象
+     */
     override fun deserialize(decoder: Decoder): List<String> {
         val jsonDecoder = decoder as? JsonDecoder ?: error("This serializer can be used only with Json")
 
@@ -49,12 +51,18 @@ object StringToListSerializer : KSerializer<List<String>> {
         }
     }
 
+    /**
+     * 序列化为JSON
+     */
+    @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
     override fun serialize(encoder: Encoder, value: List<String>) {
         if (value.size <= 1) {
             encoder.encodeString(value.getOrNull(0) ?: "")
         } else {
-            val jsonStr = Json.encodeToString(ListSerializer(String.serializer()), value)
-            encoder.encodeString(jsonStr)
+            val desc = buildSerialDescriptor("StringList", StructureKind.LIST)
+            encoder.encodeCollection(desc, value.size) {
+                value.forEachIndexed { index, item -> encodeStringElement(desc, index, item) }
+            }
         }
     }
 }

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 6 - 0
app/src/main/res/drawable/empty.xml


+ 76 - 0
app/src/main/res/layout/timer_picker.xml

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="180dp"
+    android:layout_gravity="center"
+    android:orientation="horizontal">
+
+    <com.loper7.date_time_picker.number_picker.NumberPicker
+        android:id="@+id/np_datetime_hour"
+        android:layout_width="60dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/np_datetime_day"
+        app:layout_constraintTop_toTopOf="parent"
+        app:np_dividerColor="#E5E5E5"
+        app:np_dividerThickness="0.6dp"
+        app:np_height="184dp"
+        app:np_selectedTextSize="13sp"
+        app:np_textSize="13sp"
+        app:np_wheelItemCount="3" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text=":"
+        android:textColor="@color/black"
+        android:textSize="16sp"
+        android:translationY="-2dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/np_datetime_minute"
+        app:layout_constraintStart_toEndOf="@+id/np_datetime_hour"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.loper7.date_time_picker.number_picker.NumberPicker
+        android:id="@+id/np_datetime_minute"
+        android:layout_width="60dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/np_datetime_hour"
+        app:layout_constraintTop_toTopOf="parent"
+        app:np_dividerColor="#E5E5E5"
+        app:np_dividerThickness="0.6dp"
+        app:np_height="184dp"
+        app:np_selectedTextSize="13sp"
+        app:np_textSize="13sp"
+        app:np_wheelItemCount="3" />
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text=":"
+        android:textColor="@color/black"
+        android:textSize="16sp"
+        android:translationY="-2dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@+id/np_datetime_second"
+        app:layout_constraintStart_toEndOf="@+id/np_datetime_minute"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.loper7.date_time_picker.number_picker.NumberPicker
+        android:id="@+id/np_datetime_second"
+        android:layout_width="60dp"
+        android:layout_height="wrap_content"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toEndOf="@+id/np_datetime_minute"
+        app:layout_constraintTop_toTopOf="parent"
+        app:np_dividerColor="#E5E5E5"
+        app:np_dividerThickness="0.6dp"
+        app:np_height="184dp"
+        app:np_selectedTextSize="13sp"
+        app:np_textSize="13sp"
+        app:np_wheelItemCount="3" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно