Procházet zdrojové kódy

1. 任务详情支持回显
2. 作业流程管理支持编辑节点

bjb před 4 měsíci
rodič
revize
479fb73d7a

+ 309 - 7
app/src/main/java/com/iscs/bozzys/api/ApiBean.kt

@@ -5,8 +5,9 @@ import androidx.compose.ui.geometry.Offset
 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
-import com.iscs.bozzys.utils.PlaceholderSerializer
+import com.iscs.bozzys.utils.StringToListSerializer
 import kotlinx.serialization.Serializable
+import kotlinx.serialization.encodeToString
 import kotlinx.serialization.json.Json
 
 
@@ -28,7 +29,13 @@ open class Response<T>(var code: Int = 500, var msg: String = "", var data: T? =
  * @param accessToken   使用token
  * @param refreshToken  刷新token
  */
-class LoginRsp(val userId: Int, val username: String, val nickname: String, val accessToken: String, val refreshToken: String)
+class LoginRsp(
+    val userId: Int,
+    val username: String,
+    val nickname: String,
+    val accessToken: String,
+    val refreshToken: String
+)
 
 /**
  * 获取分页数据
@@ -38,6 +45,39 @@ class LoginRsp(val userId: Int, val username: String, val nickname: String, val
  */
 class PageRsp<T>(val total: Int, val list: List<T>)
 
+
+/**
+ * 点位基类
+ */
+@Serializable
+data class IsolationPoint(val id: Int, val pointName: String, val pointNfc: String)
+
+/**
+ * 用户
+ */
+@Serializable
+data class User(
+    val id: Int,
+    val userId: Int?,
+    val username: String,
+    val nickname: String,
+    val cardNfc: String?,
+    val remark: String,
+    val deptId: Int,
+    val deptName: String?,
+    val postIds: List<Int>,
+    val email: String?,
+    val mobile: String?,
+    val sex: Int,
+    val avatar: String,
+    val status: Int,
+    val loginIp: String,
+    val loginDate: Long,
+    val createTime: Long,
+    val workstationIds: List<Int>?,
+    val type: String?,
+)
+
 /**
  * 流程模板
  */
@@ -82,12 +122,272 @@ class Node(
     val createTime: Long = 0L,
     val approvalStatus: String = "",
     val approvalOpinion: String = "",
-)
+    val isolationType: String? = null,
+    val isolationPoints: String? = null,
+    val nodeUserList: List<User>? = null,
+) {
+
+    /**
+     * 校验请求参数
+     *
+     * @param forms 表单数据
+     */
+    fun checkSubmitParams(forms: List<FormField>): String {
+        forms.forEach {
+            if (it.required) {
+                if ((it.value.getOrNull(0) ?: "").isEmpty()) {
+                    return (it.placeholder.getOrNull(0) ?: "").ifEmpty { "请填写${it.label}" }
+                }
+            }
+        }
+        return ""
+    }
+
+    /**
+     * 构建请求参数
+     *
+     * @param forms 表单数据
+     */
+    fun buildSubmitParams(forms: List<FormField>, workFormList: List<TaskFormInfo>): MutableMap<String, Any> {
+        val nodeName = forms.find { it.name == "nodeName" }?.value?.getOrNull(0) ?: ""
+        val params = mutableMapOf<String, Any>("nodeId" to id, "nodeName" to nodeName)
+        if (type != "createJob") {
+            val workerUserId = forms.find { it.name == "workerUserId" }?.value?.getOrNull(0) ?: "0"
+            val formId = forms.find { it.name == "formId" }?.value?.getOrNull(0) ?: "0"
+            val findWork = workFormList.find { it.id == formId.toInt() }
+            val formData = if (findWork != null) {
+                Json.encodeToString(formData)
+            } else null
+            params["workerUserId"] = workerUserId.toInt()
+            params["formId"] = formId.toInt()
+            if (formData != null) params["formData"] = formData
+            // 处理隔离点信息表单
+            if (type == "isolation") {
+                val isolationType = forms.find { it.name == "isolationType" }?.value?.getOrNull(0) ?: "-1"
+                val isolationPoints = forms.find { it.name == "isolationPoints" }?.value ?: emptyList()
+                if (isolationType == "1") {
+                    // 移除负责人
+                    params.remove("workerUserId")
+                    // 上锁人和共锁人
+                    val locker = forms.find { it.name == "locker" }?.value?.getOrNull(0) ?: "-1"
+                    val group = forms.find { it.name == "group" }?.value ?: emptyList()
+                    val userList = mutableListOf<MutableMap<String, Any>>()
+                    if (locker.toInt() > 0) userList += mutableMapOf("userId" to locker.toInt(), "type" to "jtlocker")
+                    group.forEach { userList += mutableMapOf("userId" to it.toInt(), "type" to "jtcolocker") }
+                    params["nodeUserDOList"] = userList
+                }
+                params["isolationType"] = isolationType
+                params["isolationPoints"] = isolationPoints.joinToString(
+                    prefix = "[",
+                    postfix = "]",
+                    separator = ","
+                )
+            }
+        }
+        return params
+    }
+
+    /**
+     * 更新节点信息
+     *
+     * @param forms
+     */
+    fun updateNodeInfo(forms: List<FormField>): Node {
+        val nodeName = forms.find { it.name == "nodeName" }?.value?.getOrNull(0) ?: ""
+        if (type != "createJob") {
+            val workerUserId = forms.find { it.name == "workerUserId" }?.value?.getOrNull(0) ?: "0"
+            val formId = forms.find { it.name == "formId" }?.value?.getOrNull(0) ?: "0"
+            val formData = forms.find { it.name == "formData" }?.value?.getOrNull(0) ?: ""
+            return Node(
+                id = id,
+                workId = workId,
+                uuid = uuid,
+                parentUuid = parentUuid,
+                childrenUuid = childrenUuid,
+                nodeName = nodeName,
+                type = type,
+                position = position,
+                workerUserId = workerUserId.toInt(),
+                formId = formId.toInt(),
+                formData = formData
+            )
+        }
+        return Node(
+            id = id,
+            workId = workId,
+            uuid = uuid,
+            parentUuid = parentUuid,
+            childrenUuid = childrenUuid,
+            nodeName = nodeName,
+            type = type,
+            position = position
+        )
+    }
+
+    /**
+     * 构建表单列表
+     *
+     * @param workerUserList        负责人列表
+     * @param lockerUserList        上锁人列表
+     * @param groupUserList         共锁人列表
+     * @param workFormList          工作表单列表
+     * @param isolationMethodList   获取隔离方式列表
+     * @param isolationList         点位列表
+     * @param isLocker              是否上锁人
+     */
+    fun buildFormList(
+        workerUserList: List<User>,
+        lockerUserList: List<User>,
+        groupUserList: List<User>,
+        workFormList: List<TaskFormInfo>,
+        isolationMethodList: List<Dict>,
+        isolationList: List<IsolationPoint>,
+        isLocker: Boolean = false,
+    ): List<FormField> {
+        val list = ArrayList<FormField>()
+        // 添加节点名称
+        list += FormField(
+            id.toString(),
+            "input",
+            "节点名称",
+            name = "nodeName",
+            true,
+            value = listOf(nodeName),
+            placeholder = listOf("请输入节点名称")
+        )
+        if (type == "createJob") return list
+        // 添加节点负责人
+        list += FormField(
+            id.toString(),
+            "select",
+            "负责人",
+            "workerUserId",
+            true,
+            placeholder = listOf("请选择负责人"),
+            options = workerUserList.map { FormOption(it.nickname, it.id.toString()) },
+            value = if (workerUserId > 0) listOf(workerUserId.toString()) else listOf()
+        )
+        // 添加业务表单
+        list += FormField(
+            id.toString(),
+            "select",
+            "业务表单",
+            name = "formId",
+            true,
+            placeholder = listOf("请选择业务表单"),
+            options = workFormList.map { FormOption(it.name, it.id.toString()) },
+            value = if (formId > 0) listOf(formId.toString()) else listOf()
+        )
+        // 隔离配置
+        if (listOf("isolation", "releaseIsolation").contains(type)) {
+            // 先移除负责人,需要在后面进行添加
+            list.removeIf { it.name == "workerUserId" }
+            // 而且业务表单非必填
+            val formField = list.find { it.name == "formId" }?.copy(required = false)
+            list.removeIf { it.name == "formId" }
+            formField?.let { list.add(it) }
+            // 隔离方式
+            list += FormField(
+                id.toString(),
+                "select",
+                "隔离方式",
+                "isolationType",
+                true,
+                placeholder = listOf("请选择隔离方式"),
+                options = isolationMethodList.map { FormOption(it.label, it.value) },
+                value = if (isolationType.isNullOrEmpty()) listOf() else listOf(isolationType)
+            )
+            val points = if (isolationPoints.isNullOrEmpty()) "" else isolationPoints.replace("[", "").replace("]", "")
+            // 隔离点选择,可多选
+            list += FormField(
+                id.toString(),
+                "select",
+                "隔离点选择(可多选)",
+                "isolationPoints",
+                true,
+                listOf("请选择隔离点"),
+                multiSelect = true,
+                options = isolationList.map { FormOption(it.pointName, it.id.toString()) },
+                value = if (points.isNotEmpty()) points.split(",") else listOf()
+            )
+            if (isLocker) {
+                val locker = nodeUserList?.find { it.type == "jtlocker" }
+                val group = nodeUserList?.filter { it.type == "jtcolocker" } ?: emptyList()
+                // 上锁人
+                list += FormField(
+                    id.toString(),
+                    "select",
+                    "上锁人",
+                    "locker",
+                    true,
+                    placeholder = listOf("请选择上锁人"),
+                    options = lockerUserList.map { FormOption(it.nickname, it.id.toString()) },
+                    value = if (locker != null) listOf(locker.userId.toString()) else listOf()
+                )
+                // 共锁人
+                list += FormField(
+                    id.toString(),
+                    "select",
+                    "共锁人(可多选)",
+                    "group",
+                    false,
+                    placeholder = listOf("请选择共锁人"),
+                    options = groupUserList.map { FormOption(it.nickname, it.id.toString()) },
+                    value = if (group.isNotEmpty()) group.map { it.userId.toString() } else listOf(),
+                    multiSelect = true
+                )
+            } else {
+                // 负责人
+                list += FormField(
+                    id.toString(),
+                    "select",
+                    "负责人",
+                    "workerUserId",
+                    true,
+                    placeholder = listOf("请选择负责人"),
+                    options = workerUserList.map { FormOption(it.nickname, it.id.toString()) },
+                    value = if (workerUserId > 0) listOf(workerUserId.toString()) else listOf()
+                )
+            }
+        }
+//        // 通知方式
+//        list += FormField(
+//            id.toString(), "checkbox", "通知方式", nodeName, false, listOf(), listOf(nodeName),
+//            options = listOf(
+//                FormOption("短信", "message"),
+//                FormOption("站内信", "sms"),
+//                FormOption("邮件", "email"),
+//                FormOption("App通知", "app"),
+//            )
+//        )
+//        // 通知人
+//        list += FormField(
+//            id.toString(), "select", "通知人", nodeName, false, listOf(), options = listOf(
+//                FormOption("任务负责人", "p1"),
+//                FormOption("任务参与人", "p2"),
+//                FormOption("任务负责和参与人", "all"),
+//                FormOption("指定人", "custom"),
+//            )
+//        )
+//        // 通知时间
+//        list += FormField(
+//            id.toString(), "select", "通知时间", nodeName, false, listOf(), options = listOf(
+//                FormOption("任务开始前30分钟", "0.5"),
+//                FormOption("任务开始前1小时", "1"),
+//                FormOption("任务开始前2小时", "2"),
+//                FormOption("任务开始前5小时", "5"),
+//            )
+//        )
+        return list
+    }
+
+}
 
 /**
  * 任务表单信息
  */
-class TaskFormInfo(
+@Serializable
+data class TaskFormInfo(
     val id: Int = 0,
     val name: String = "",
     val conf: String = "",
@@ -216,13 +516,15 @@ data class FormField(
     val label: String = "",
     val name: String = "",
     val required: Boolean = false,
-    @Serializable(with = PlaceholderSerializer::class)
-    val placeholder: List<String> = listOf(""),
-    var value: List<String> = listOf(""),
+    @Serializable(with = StringToListSerializer::class)
+    val placeholder: List<String> = listOf(),
+    @Serializable(with = StringToListSerializer::class)
+    var value: List<String> = listOf(),
     var options: List<FormOption> = listOf(),
     val cardTitle: String = "",
     val gridColumns: Int = 0,
     val enabled: Boolean = true,
+    val multiSelect: Boolean = false,
     val children: List<FormField> = listOf()
 )
 

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

@@ -191,4 +191,45 @@ object ApiRequest {
         return requestApi { api.getJobById(id) }
     }
 
+    /**
+     * 更新节点信息
+     *
+     * @param params
+     */
+    suspend fun updateJobNodeInfo(params: MutableMap<String, Any>): Result<Response<Boolean>> {
+        return requestApi { api.updateJobNodeInfo(params) }
+    }
+
+    /**
+     * 更新节点完成情况
+     *
+     * @param params
+     */
+    suspend fun updateJobNodeApproval(params: MutableMap<String, Any>): Result<Response<Boolean>> {
+        return requestApi { api.updateJobNodeApproval(params) }
+    }
+
+    /**
+     * 获取用户列表,通过角色类型
+     *
+     * @param roleCode 角色类型
+     */
+    suspend fun getUsersByRoleCode(roleCode: String): Result<Response<List<User>>> {
+        return requestApi { api.getUsersByRoleCode(roleCode) }
+    }
+
+    /**
+     * 获取管理表单列表
+     */
+    suspend fun getBpmFormList(params: MutableMap<String, Any>): Result<Response<PageRsp<TaskFormInfo>>> {
+        return requestApi { api.getBpmFormList(params) }
+    }
+
+    /**
+     * 获取点位列表
+     */
+    suspend fun getIsolationPointList(params: MutableMap<String, Any>): Result<Response<PageRsp<IsolationPoint>>> {
+        return requestApi { api.getIsolationPointList(params) }
+    }
+
 }

+ 51 - 0
app/src/main/java/com/iscs/bozzys/api/ApiService.kt

@@ -6,6 +6,7 @@ import retrofit2.http.GET
 import retrofit2.http.HeaderMap
 import retrofit2.http.Headers
 import retrofit2.http.POST
+import retrofit2.http.PUT
 import retrofit2.http.Query
 import retrofit2.http.QueryMap
 
@@ -114,4 +115,54 @@ interface ApiService {
         @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
     ): Response<Job>
 
+    /**
+     * 更新工作中的节点信息
+     */
+    @Headers("Content-Type: application/json")
+    @PUT("/admin-api/iscs/workflow-work-node/updateWorkflowWorkNode")
+    suspend fun updateJobNodeInfo(
+        @Body body: MutableMap<String, Any>,
+        @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
+    ): Response<Boolean>
+
+    /**
+     * 更新当前节点任务状态
+     */
+    @Headers("Content-Type: application/json")
+    @POST("/admin-api/iscs/workflow-work/updateNodeApproval")
+    suspend fun updateJobNodeApproval(
+        @Body body: MutableMap<String, Any>,
+        @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
+    ): Response<Boolean>
+
+    /**
+     * 通过角色码获取角色列表
+     */
+    @Headers("Content-Type: application/x-www-form-urlencoded")
+    @GET("/admin-api/system/user/getUserByRoleCode")
+    suspend fun getUsersByRoleCode(
+        @Query("roleCode") roleCode: String,
+        @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
+    ): Response<List<User>>
+
+    /**
+     * 获取管理表单列表
+     */
+    @Headers("Content-Type: application/x-www-form-urlencoded")
+    @GET("/admin-api/bpm/form/page")
+    suspend fun getBpmFormList(
+        @QueryMap params: MutableMap<String, Any>,
+        @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
+    ): Response<PageRsp<TaskFormInfo>>
+
+    /**
+     * 获取点位列表
+     */
+    @Headers("Content-Type: application/x-www-form-urlencoded")
+    @GET("/admin-api/iscs/isolation-point/getIsolationPointPage")
+    suspend fun getIsolationPointList(
+        @QueryMap params: MutableMap<String, Any>,
+        @HeaderMap headers: Map<String, String> = ApiRequest.getUserHeaders()
+    ): Response<PageRsp<IsolationPoint>>
+
 }

+ 48 - 14
app/src/main/java/com/iscs/bozzys/ui/base/Loading.kt

@@ -1,5 +1,6 @@
 package com.iscs.bozzys.ui.base
 
+import android.view.WindowManager
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
@@ -12,13 +13,17 @@ import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.CircularProgressIndicator
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
 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.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.iscs.bozzys.ui.pages.vm.VMLoading
 
@@ -27,23 +32,52 @@ import com.iscs.bozzys.ui.pages.vm.VMLoading
  */
 @Composable
 fun LoadingCompose(vm: VMLoading = viewModel()) {
-    if (!vm.state.show) return
-    Box(
-        Modifier
-            .fillMaxSize()
-            .pointerInput(Unit) {}, // 不允许点击穿透
-        contentAlignment = Alignment.Center
+    val ctx = LocalContext.current
+    if (!vm.state.show || ctx !is PageBase) return
+    Dialog(
+        onDismissRequest = {},
+        properties = DialogProperties(
+            dismissOnBackPress = false,
+            dismissOnClickOutside = false,
+            usePlatformDefaultWidth = false,
+            decorFitsSystemWindows = false
+        )
     ) {
-        Column(
+        val activityWindow = ctx.window
+        val dialogWindow = ctx.getDialogWindow()
+        SideEffect {
+            if (activityWindow != null && dialogWindow != null) {
+                val attributes = WindowManager.LayoutParams()
+                // 复制Activity窗口属性
+                attributes.copyFrom(activityWindow.attributes)
+                // 这个一定要设置
+                attributes.type = dialogWindow.attributes.type
+                // 更新窗口属性
+                dialogWindow.attributes = attributes
+                // 设置窗口的宽度和高度
+                dialogWindow.setLayout(
+                    activityWindow.decorView.width,
+                    activityWindow.decorView.height
+                )
+            }
+        }
+        Box(
             Modifier
-                .sizeIn(90.dp, 90.dp, 150.dp, 150.dp)
-                .clip(RoundedCornerShape(8.dp))
-                .background(Color.Black.copy(alpha = 0.6f)),
-            verticalArrangement = Arrangement.Center,
-            horizontalAlignment = Alignment.CenterHorizontally
+                .fillMaxSize()
+                .pointerInput(Unit) {},     // 不允许点击穿透
+            contentAlignment = Alignment.Center
         ) {
-            CircularProgressIndicator(Modifier.size(30.dp))
-            Text("加载中...", Modifier.padding(top = 5.dp), color = Color.White, fontSize = 12.sp)
+            Column(
+                Modifier
+                    .sizeIn(90.dp, 90.dp, 150.dp, 150.dp)
+                    .clip(RoundedCornerShape(8.dp))
+                    .background(Color.Black.copy(alpha = 0.6f)),
+                verticalArrangement = Arrangement.Center,
+                horizontalAlignment = Alignment.CenterHorizontally
+            ) {
+                CircularProgressIndicator(Modifier.size(30.dp))
+                Text("加载中...", Modifier.padding(top = 5.dp), color = Color.White, fontSize = 12.sp)
+            }
         }
     }
 }

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

@@ -19,19 +19,23 @@ 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.Checkbox
 import androidx.compose.material3.DropdownMenu
 import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.Icon
 import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.RadioButton
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
+import androidx.compose.runtime.mutableStateListOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
@@ -60,7 +64,7 @@ import kotlinx.serialization.json.Json
 
 
 @Composable
-fun FormContainer(forms: List<FormField>, modifier: Modifier = Modifier) {
+fun FormContainer(forms: List<FormField>, onValueChange: (FormField) -> Unit, modifier: Modifier = Modifier, enabled: Boolean = true) {
     Column(modifier = modifier) {
         forms.forEach { form ->
             key(form.id) {
@@ -69,52 +73,96 @@ fun FormContainer(forms: List<FormField>, modifier: Modifier = Modifier) {
                     "input" -> FormInput(
                         form.label,
                         form.value,
-                        { form.value = it },
+                        {
+                            form.value = it
+                            onValueChange(form)
+                        },
                         form.placeholder,
                         required = form.required,
-                        enable = form.enabled
+                        enable = form.enabled && enabled
                     )
                     // 多行文本输入
                     "textarea" -> FormTextarea(
                         form.label,
                         form.value,
-                        { form.value = it },
+                        {
+                            form.value = it
+                            onValueChange(form)
+                        },
                         form.placeholder,
                         required = form.required,
-                        enable = form.enabled
+                        enable = form.enabled && enabled
                     )
                     // 选择器
                     "select" -> FormSelect(
                         form.label,
                         form.value,
                         form.options,
-                        { form.value = it },
+                        {
+                            form.value = it
+                            onValueChange(form)
+                        },
                         required = form.required,
-                        enable = form.enabled
+                        enable = form.enabled && enabled,
+                        multiSelect = form.multiSelect,
+                        placeholder = form.placeholder
                     )
                     // 日期选择
                     "date" -> FormDateSelect(
                         form.label,
                         form.value,
-                        { form.value = it },
-                        required = form.required, enable = form.enabled
+                        {
+                            form.value = it
+                            onValueChange(form)
+                        },
+                        required = form.required,
+                        enable = form.enabled && enabled
                     )
                     // 起止日期选择
                     "daterange" -> FormDateRangeSelect(
                         form.label,
                         form.value,
-                        { form.value = it },
+                        {
+                            form.value = it
+                            onValueChange(form)
+                        },
                         placeholder = form.placeholder,
-                        enable = form.enabled
+                        enable = form.enabled && enabled
+                    )
+                    // 时间选择器
+                    "timepicker" -> FormTimeSelect(
+                        form.label,
+                        form.value,
+                        {
+                            form.value = it
+                            onValueChange(form)
+                        },
+                        required = form.required,
+                        enable = form.enabled && enabled
                     )
                     // 单选
                     "radio" -> FormRadio(
                         form.label,
                         form.value,
                         form.options,
-                        { form.value = it },
+                        {
+                            form.value = it
+                            onValueChange(form)
+                        },
+                        required = form.required,
+                        enable = form.enabled && enabled
+                    )
+                    // 多选
+                    "checkbox" -> FormCheckbox(
+                        form.label,
+                        form.value,
+                        form.options,
+                        {
+                            form.value = it
+                            onValueChange(form)
+                        },
                         required = form.required,
-                        enable = form.enabled
+                        enable = form.enabled && enabled
                     )
                 }
             }
@@ -149,7 +197,12 @@ fun FormInput(
                 .fillMaxWidth()
                 .height(40.dp), verticalAlignment = Alignment.CenterVertically
         ) {
-            Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = Text.copy(alpha = if (enable) 1f else 0.6f))
+            Text(
+                label,
+                fontSize = 14.sp,
+                fontWeight = FontWeight.Bold,
+                color = Text.copy(alpha = if (enable) 1f else 0.6f)
+            )
             if (required) Text(
                 "*",
                 fontSize = 14.sp,
@@ -170,7 +223,7 @@ fun FormInput(
                 .border(1.dp, shape = RoundedCornerShape(6.dp), color = Color(0xFFE5E6EB))
                 .padding(horizontal = 10.dp),
             singleLine = true,
-            textStyle = LocalTextStyle.current.copy(fontSize = 14.sp, lineHeight = 18.sp),
+            textStyle = LocalTextStyle.current.copy(fontSize = 14.sp, lineHeight = 18.sp, color = Text.copy(alpha = if (enable) 1f else 0.6f)),
             decorationBox = { innerTextField ->
                 Box(contentAlignment = Alignment.CenterStart) {
                     innerTextField()
@@ -215,7 +268,12 @@ fun FormTextarea(
                 .fillMaxWidth()
                 .height(40.dp), verticalAlignment = Alignment.CenterVertically
         ) {
-            Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = Text.copy(alpha = if (enable) 1f else 0.6f))
+            Text(
+                label,
+                fontSize = 14.sp,
+                fontWeight = FontWeight.Bold,
+                color = Text.copy(alpha = if (enable) 1f else 0.6f)
+            )
             if (required) Text(
                 "*",
                 fontSize = 14.sp,
@@ -236,7 +294,7 @@ fun FormTextarea(
                 .border(1.dp, shape = RoundedCornerShape(6.dp), color = Color(0xFFE5E6EB))
                 .padding(10.dp)
                 .verticalScroll(rememberScrollState()),
-            textStyle = LocalTextStyle.current.copy(fontSize = 14.sp, lineHeight = 18.sp),
+            textStyle = LocalTextStyle.current.copy(fontSize = 14.sp, lineHeight = 18.sp, color = Text.copy(alpha = if (enable) 1f else 0.6f)),
             decorationBox = { innerTextField ->
                 Box(contentAlignment = Alignment.TopStart) {
                     innerTextField()
@@ -267,15 +325,33 @@ fun FormSelect(
     value: List<String>,
     options: List<FormOption>,
     onSelectChange: (List<String>) -> Unit,
-    placeholder: List<String> = listOf(""),
+    placeholder: List<String> = listOf(),
     required: Boolean = false,
     enable: Boolean = true,
+    multiSelect: Boolean = false,
 ) {
     var expanded by remember { mutableStateOf(false) }
     var width by remember { mutableStateOf(0) }
     val density = LocalDensity.current
     // 当前表单中的状态
-    var opt by remember { mutableStateOf(options.find { it.value == value.getOrNull(0) }) }
+    val values = remember { mutableStateListOf<String>() }
+    LaunchedEffect(value) {
+        values.clear()
+        values.addAll(value)
+    }
+
+    /**
+     * 转换数组列表
+     */
+    fun SnapshotStateList<String>.data2String(): String {
+        var datas = ""
+        this.forEachIndexed { index, value ->
+            val find = options.find { it.value == value }?.label ?: ""
+            datas += if (index == (this.size - 1)) find else "$find, "
+        }
+        return datas
+    }
+
     Column(
         Modifier
             .fillMaxWidth()
@@ -287,7 +363,12 @@ fun FormSelect(
                 .fillMaxWidth()
                 .height(40.dp), verticalAlignment = Alignment.CenterVertically
         ) {
-            Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = Text.copy(alpha = if (enable) 1f else 0.6f))
+            Text(
+                label,
+                fontSize = 14.sp,
+                fontWeight = FontWeight.Bold,
+                color = Text.copy(alpha = if (enable) 1f else 0.6f)
+            )
             if (required) Text(
                 "*",
                 fontSize = 14.sp,
@@ -306,16 +387,20 @@ fun FormSelect(
             verticalAlignment = Alignment.CenterVertically
         ) {
             Text(
-                opt?.label ?: "请选择$label",
+                values.data2String()
+                    .ifEmpty { (placeholder.getOrNull(0) ?: "").ifEmpty { "请选择$label" } },
                 color = Text.copy(alpha = if (enable) 1f else 0.6f),
                 fontSize = 14.sp,
                 lineHeight = 18.sp,
                 modifier = Modifier.weight(1f),
             )
             Icon(
-                painter = painterResource(R.drawable.back), contentDescription = "", modifier = Modifier
+                painter = painterResource(R.drawable.back),
+                contentDescription = "",
+                modifier = Modifier
                     .rotate(if (expanded) -90f else 180f)
-                    .size(12.dp), tint = Text.copy(alpha = if (enable) 1f else 0.6f)
+                    .size(12.dp),
+                tint = Text.copy(alpha = if (enable) 1f else 0.6f)
             )
         }
         val widthDp = with(density) { width.toDp() }
@@ -332,11 +417,25 @@ fun FormSelect(
                 options.forEach { option ->
                     DropdownMenuItem(
                         modifier = Modifier.fillMaxWidth(),
-                        text = { Text(option.label) },
+                        text = {
+                            Text(
+                                option.label,
+                                color = if (values.contains(option.value)) MaterialTheme.colorScheme.primary else Text
+                            )
+                        },
                         onClick = {
-                            opt = option
-                            onSelectChange(listOf(option.value))
-                            expanded = false
+                            if (multiSelect) {
+                                if (values.contains(option.value)) {
+                                    values.remove(option.value)
+                                } else {
+                                    values.add(option.value)
+                                }
+                            } else {
+                                values.clear()
+                                values.add(option.value)
+                            }
+                            onSelectChange(values)
+                            if (!multiSelect) expanded = false
                         }
                     )
                 }
@@ -374,7 +473,12 @@ fun FormRadio(
                 .fillMaxWidth()
                 .height(40.dp), verticalAlignment = Alignment.CenterVertically
         ) {
-            Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = Text.copy(alpha = if (enable) 1f else 0.6f))
+            Text(
+                label,
+                fontSize = 14.sp,
+                fontWeight = FontWeight.Bold,
+                color = Text.copy(alpha = if (enable) 1f else 0.6f)
+            )
             if (required) Text(
                 "*",
                 fontSize = 14.sp,
@@ -398,7 +502,94 @@ fun FormRadio(
                         .padding(horizontal = 10.dp, vertical = 5.dp),
                     verticalAlignment = Alignment.CenterVertically
                 ) {
-                    RadioButton(selected = text == item.value, onClick = null, modifier = Modifier.size(14.dp), enabled = enable)
+                    RadioButton(
+                        selected = text == item.value,
+                        onClick = null,
+                        modifier = Modifier.size(14.dp),
+                        enabled = enable
+                    )
+                    Text(
+                        text = item.label,
+                        fontSize = 15.sp,
+                        modifier = Modifier.padding(start = 10.dp),
+                        color = Text.copy(alpha = if (enable) 1f else 0.6f)
+                    )
+                }
+            }
+        }
+    }
+}
+
+/**
+ * 表单单选组件
+ *
+ * @param label             标题
+ * @param value             当前选中
+ * @param options           可选列表
+ * @param onSelectChange    当前选中
+ */
+@OptIn(ExperimentalLayoutApi::class)
+@Composable
+fun FormCheckbox(
+    label: String,
+    value: List<String>,
+    options: List<FormOption>,
+    onSelectChange: (List<String>) -> Unit,
+    required: Boolean = false,
+    enable: Boolean = true,
+) {
+    val values = remember { mutableStateListOf<String>() }
+    LaunchedEffect(Unit) {
+        values.clear()
+        values.addAll(value)
+    }
+    Column(
+        Modifier
+            .fillMaxWidth()
+            .heightIn(max = 160.dp)
+    ) {
+        Row(
+            Modifier
+                .fillMaxWidth()
+                .height(40.dp), verticalAlignment = Alignment.CenterVertically
+        ) {
+            Text(
+                label,
+                fontSize = 14.sp,
+                fontWeight = FontWeight.Bold,
+                color = Text.copy(alpha = if (enable) 1f else 0.6f)
+            )
+            if (required) Text(
+                "*",
+                fontSize = 14.sp,
+                color = Color(0xFFFF4D4F).copy(alpha = if (enable) 1f else 0.6f),
+                modifier = Modifier.padding(start = 3.dp)
+            )
+        }
+        FlowRow(
+            Modifier
+                .fillMaxWidth()
+                .heightIn(max = 120.dp)
+        ) {
+            options.forEach { item ->
+                Row(
+                    modifier = Modifier
+                        .clip(RoundedCornerShape(12.dp))
+                        .clickable(onClick = {
+                            if (values.contains(item.value))
+                                values.remove(item.value)
+                            else values.add(item.value)
+                            onSelectChange(values)
+                        }, enabled = enable)
+                        .padding(horizontal = 10.dp, vertical = 5.dp),
+                    verticalAlignment = Alignment.CenterVertically
+                ) {
+                    Checkbox(
+                        checked = values.contains(item.value),
+                        onCheckedChange = null,
+                        modifier = Modifier.size(14.dp),
+                        enabled = enable
+                    )
                     Text(
                         text = item.label,
                         fontSize = 15.sp,
@@ -419,12 +610,30 @@ fun FormRadio(
  * @param onSelectChange    当前选中
  */
 @Composable
-fun FormDateSelect(label: String, value: List<String>, onSelectChange: (List<String>) -> Unit, required: Boolean = false, enable: Boolean = true) {
-    var date by remember { mutableStateOf((value.getOrNull(0) ?: "").dateToTimestamp("yyyy/MM/dd HH:mm")) }
+fun FormDateSelect(
+    label: String,
+    value: List<String>,
+    onSelectChange: (List<String>) -> Unit,
+    required: Boolean = false,
+    enable: Boolean = true
+) {
+    var date by remember {
+        mutableStateOf(
+            (value.getOrNull(0) ?: "").dateToTimestamp("yyyy/MM/dd HH:mm")
+        )
+    }
     val picker = CardDatePickerDialog.builder(LocalContext.current)
         .setTitle("请选择日期")
         .setDefaultTime(date)
-        .setDisplayType(mutableListOf(DateTimeConfig.YEAR, DateTimeConfig.MONTH, DateTimeConfig.DAY, DateTimeConfig.HOUR, DateTimeConfig.MIN))
+        .setDisplayType(
+            mutableListOf(
+                DateTimeConfig.YEAR,
+                DateTimeConfig.MONTH,
+                DateTimeConfig.DAY,
+                DateTimeConfig.HOUR,
+                DateTimeConfig.MIN
+            )
+        )
         .setPickerLayout(R.layout.date_picker)
         .showBackNow(false)
         .showFocusDateInfo(false)
@@ -445,7 +654,12 @@ fun FormDateSelect(label: String, value: List<String>, onSelectChange: (List<Str
                 .height(40.dp),
             verticalAlignment = Alignment.CenterVertically
         ) {
-            Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = Text.copy(alpha = if (enable) 1f else 0.6f))
+            Text(
+                label,
+                fontSize = 14.sp,
+                fontWeight = FontWeight.Bold,
+                color = Text.copy(alpha = if (enable) 1f else 0.6f)
+            )
             if (required) Text(
                 "*",
                 fontSize = 14.sp,
@@ -507,7 +721,12 @@ fun FormDateRangeSelect(
                 .height(40.dp),
             verticalAlignment = Alignment.CenterVertically
         ) {
-            Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = Text.copy(alpha = if (enable) 1f else 0.6f))
+            Text(
+                label,
+                fontSize = 14.sp,
+                fontWeight = FontWeight.Bold,
+                color = Text.copy(alpha = if (enable) 1f else 0.6f)
+            )
             if (required) Text(
                 "*",
                 fontSize = 14.sp,
@@ -533,7 +752,13 @@ fun FormDateRangeSelect(
                         CardDatePickerDialog.builder(ctx)
                             .setTitle("请选择开始日期")
                             .setDefaultTime(start)
-                            .setDisplayType(mutableListOf(DateTimeConfig.YEAR, DateTimeConfig.MONTH, DateTimeConfig.DAY))
+                            .setDisplayType(
+                                mutableListOf(
+                                    DateTimeConfig.YEAR,
+                                    DateTimeConfig.MONTH,
+                                    DateTimeConfig.DAY
+                                )
+                            )
                             .setPickerLayout(R.layout.date_range_picker)
                             .showBackNow(false)
                             .showFocusDateInfo(false)
@@ -548,7 +773,11 @@ fun FormDateRangeSelect(
                 lineHeight = 26.sp,
                 color = Text.copy(alpha = if (enable) 1f else 0.6f)
             )
-            Text("-", modifier = Modifier.padding(horizontal = 5.dp), color = Text.copy(alpha = if (enable) 1f else 0.6f))
+            Text(
+                "-",
+                modifier = Modifier.padding(horizontal = 5.dp),
+                color = Text.copy(alpha = if (enable) 1f else 0.6f)
+            )
             Text(
                 end.format("yyyy/MM/dd"),
                 modifier = Modifier
@@ -560,7 +789,13 @@ fun FormDateRangeSelect(
                         CardDatePickerDialog.builder(ctx)
                             .setTitle("请选择结束日期")
                             .setDefaultTime(end)
-                            .setDisplayType(mutableListOf(DateTimeConfig.YEAR, DateTimeConfig.MONTH, DateTimeConfig.DAY))
+                            .setDisplayType(
+                                mutableListOf(
+                                    DateTimeConfig.YEAR,
+                                    DateTimeConfig.MONTH,
+                                    DateTimeConfig.DAY
+                                )
+                            )
                             .setPickerLayout(R.layout.date_range_picker)
                             .showBackNow(false)
                             .showFocusDateInfo(false)
@@ -580,6 +815,81 @@ fun FormDateRangeSelect(
     }
 }
 
+/**
+ * 表单时间选择组件
+ *
+ * @param label             标题
+ * @param value             当前选中
+ * @param onSelectChange    当前选中
+ */
+@Composable
+fun FormTimeSelect(
+    label: String,
+    value: List<String>,
+    onSelectChange: (List<String>) -> Unit,
+    required: Boolean = false,
+    enable: Boolean = true
+) {
+    var date by remember {
+        mutableStateOf((value.getOrNull(0) ?: "").dateToTimestamp("yyyy/MM/dd HH:mm:ss"))
+    }
+    val picker = CardDatePickerDialog.builder(LocalContext.current)
+        .setTitle("请选择时间")
+        .setDefaultTime(date)
+        .setDisplayType(
+            mutableListOf(DateTimeConfig.HOUR, DateTimeConfig.MIN)
+        )
+        .setPickerLayout(R.layout.date_picker)
+        .showBackNow(false)
+        .showFocusDateInfo(false)
+        .showDateLabel(false)
+        .setOnChoose { timestamp ->
+            date = timestamp
+            onSelectChange(listOf(timestamp.toString()))
+        }.build()
+
+    Column(
+        Modifier
+            .fillMaxWidth()
+            .heightIn(max = 90.dp)
+    ) {
+        Row(
+            Modifier
+                .fillMaxWidth()
+                .height(40.dp),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            Text(
+                label,
+                fontSize = 14.sp,
+                fontWeight = FontWeight.Bold,
+                color = Text.copy(alpha = if (enable) 1f else 0.6f)
+            )
+            if (required) Text(
+                "*",
+                fontSize = 14.sp,
+                color = Color(0xFFFF4D4F).copy(alpha = if (enable) 1f else 0.6f),
+                modifier = Modifier.padding(start = 3.dp)
+            )
+        }
+
+        Text(
+            date.format("HH:mm"),
+            modifier = Modifier
+                .fillMaxWidth()
+                .height(46.dp)
+                .border(1.dp, shape = RoundedCornerShape(6.dp), color = Color(0xFFE5E6EB))
+                .clip(RoundedCornerShape(6.dp))
+                .clickable(onClick = { picker.show() }, enabled = enable)
+                .padding(10.dp),
+            fontSize = 14.sp,
+            lineHeight = 26.sp,
+            color = Text.copy(alpha = if (enable) 1f else 0.6f)
+        )
+    }
+}
+
+
 /**
  * 将服务端返回的表单String JSON数组转化为可用的表单数据
  */

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

@@ -68,7 +68,7 @@ class PageCreateJob : PageBase() {
                 // 表单内容
                 if (state.forms.isNotEmpty()) CardContainer(Modifier.padding(horizontal = 16.dp, vertical = 16.dp)) {
                     FormContainer(
-                        state.forms, modifier = Modifier
+                        state.forms, {}, modifier = Modifier
                             .padding(horizontal = 10.dp)
                             .padding(top = 5.dp, bottom = 16.dp)
                     )

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

@@ -27,8 +27,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateListOf
-import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
@@ -39,18 +37,11 @@ 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.api.FormField
 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.pages.compose.CardContainer
-import com.iscs.bozzys.ui.pages.compose.FormDateRangeSelect
-import com.iscs.bozzys.ui.pages.compose.FormDateSelect
-import com.iscs.bozzys.ui.pages.compose.FormInput
-import com.iscs.bozzys.ui.pages.compose.FormRadio
-import com.iscs.bozzys.ui.pages.compose.FormSelect
-import com.iscs.bozzys.ui.pages.compose.FormTextarea
-import com.iscs.bozzys.ui.pages.compose.getFormListByJsonList
+import com.iscs.bozzys.ui.pages.compose.FormContainer
 import com.iscs.bozzys.ui.pages.compose.getTaskStatusName
 import com.iscs.bozzys.ui.pages.vm.VMDetailTask
 import com.iscs.bozzys.ui.theme.Text
@@ -104,11 +95,11 @@ class PageDetailTask : PageBase() {
                     .padding(16.dp)
             ) {
                 TaskInfo(task)
-                // 处理动态内容的条件
+                // 表单数据存在即显示
+                if (state.forms.isNotEmpty()) TaskForm(vm)
+                // 处理动态内容的条件,其实业务逻辑不应该在这里处理的
                 when (state.node.type) {
-                    "review",                       // 审核
-                    "inputInfo",                    // 录入信息
-                    "complete" -> TaskForm(vm)      // 完成操作,录入表单信息
+                    "isolation",                    // 隔离
                     "releaseIsolation",             // 解除隔离
                     "returnLock" -> TaskDevice(vm)  // 还锁
                 }
@@ -247,10 +238,6 @@ class PageDetailTask : PageBase() {
     @Composable
     fun TaskForm(vm: VMDetailTask) {
         val state by vm.state.collectAsState()
-        val stateFormList = remember { mutableStateListOf<FormField>() }
-        LaunchedEffect(state.formInfo.fields) {
-            stateFormList.addAll(state.formInfo.fields.getFormListByJsonList())
-        }
         CardContainer(Modifier.padding(top = 10.dp)) {
             Column(
                 Modifier
@@ -268,57 +255,7 @@ class PageDetailTask : PageBase() {
                     )
                     Text("录入信息", fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Text)
                 }
-                stateFormList.forEach { form ->
-                    when (form.type) {
-                        // 单行文本输入
-                        "input" -> FormInput(
-                            form.label,
-                            form.value,
-                            { form.value = it },
-                            form.placeholder,
-                            required = form.required,
-                            enable = state.node.approvalStatus != "approved"
-                        )
-                        // 多行文本输入
-                        "textarea" -> FormTextarea(
-                            form.label,
-                            form.value,
-                            { form.value = it },
-                            form.placeholder,
-                            required = form.required,
-                            enable = state.node.approvalStatus != "approved"
-                        )
-                        // 选择器
-                        "select" -> FormSelect(
-                            form.label,
-                            form.value,
-                            form.options,
-                            { form.value = it }, required = form.required, enable = state.node.approvalStatus != "approved"
-                        )
-                        // 日期选择
-                        "date" -> FormDateSelect(
-                            form.label,
-                            form.value,
-                            { form.value = it },
-                            required = form.required, enable = state.node.approvalStatus != "approved"
-                        )
-                        // 起止日期选择
-                        "daterange" -> FormDateRangeSelect(
-                            form.label,
-                            form.value,
-                            { form.value = it },
-                            placeholder = form.placeholder,
-                            enable = state.node.approvalStatus != "approved"
-                        )
-                        // 单选
-                        "radio" -> FormRadio(
-                            form.label,
-                            form.value,
-                            form.options,
-                            { form.value = it }, required = form.required, enable = state.node.approvalStatus != "approved"
-                        )
-                    }
-                }
+                FormContainer(state.forms, {}, enabled = state.node.approvalStatus != "approved")
             }
         }
     }
@@ -370,7 +307,7 @@ class PageDetailTask : PageBase() {
                     .padding(horizontal = 16.dp)
                     .fillMaxWidth()
             ) {
-                if (listOf("complete", "inputInfo").contains(state.node.type)) {
+                if (listOf("complete", "inputInfo", "confirm").contains(state.node.type)) {
                     Button(
                         { destroy() }, modifier = Modifier
                             .padding(horizontal = 8.dp)
@@ -393,7 +330,9 @@ class PageDetailTask : PageBase() {
                         }
                     }
                     Button(
-                        {}, modifier = Modifier
+                        { vm.onSubmit() },
+                        enabled = state.node.approvalStatus != "approved",
+                        modifier = Modifier
                             .padding(horizontal = 8.dp)
                             .weight(1f)
                             .height(50.dp)

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

@@ -89,7 +89,11 @@ class PageEditStep : PageBase() {
         Column(Modifier.fillMaxSize()) {
             Title(pv, "作业流程管理")
             Box(Modifier.weight(1f)) {
-                ZoomPanContainer(state.nodes, state.connections, modifier = Modifier.fillMaxSize()) { scale, toTopCenter, toCenter, toLast ->
+                ZoomPanContainer(
+                    state.nodes,
+                    state.connections,
+                    modifier = Modifier.fillMaxSize()
+                ) { scale, toTopCenter, toCenter, toLast ->
                     // 控件显示
                     Box(
                         modifier = Modifier
@@ -116,7 +120,7 @@ class PageEditStep : PageBase() {
                         // 回到移动中心位置前的位置,默认不添加 等反馈
                         toLast()
                         vm.formDialogShow(false)
-                    }, pv) {
+                    }, pv, title = state.node.value.node?.nodeName ?: "") {
 
                         // 监听键盘弹出事件
                         val density = LocalDensity.current
@@ -138,13 +142,13 @@ class PageEditStep : PageBase() {
                                         .imePadding()
                                         .padding(vertical = 5.dp)
                                 ) {
-                                    item { FormContainer(state.nodeForms) }
+                                    item { FormContainer(state.nodeForms, { vm.onNodeFormChanged(it) }) }
                                 }
                             }
                             if (imeBottom <= 0) {
                                 Spacer(Modifier.height(10.dp))
                                 Button(
-                                    {},
+                                    { vm.onNodeSave() },
                                     modifier = Modifier
                                         .fillMaxWidth()
                                         .height(50.dp)
@@ -198,6 +202,7 @@ fun FormDialog(
     show: Boolean,
     onDismiss: () -> Unit,
     paddingValues: PaddingValues = PaddingValues(0.dp),
+    title: String = "",
     content: @Composable () -> Unit
 ) {
     val ctx = LocalContext.current
@@ -211,10 +216,16 @@ fun FormDialog(
     LaunchedEffect(Unit) {
         ctx.setNavigationLight(false)
         // 底部弹出动画处理
-        offsetY.animateTo(targetValue = 0f, animationSpec = spring(dampingRatio = 0.85f, stiffness = 300f))
+        offsetY.animateTo(
+            targetValue = 0f,
+            animationSpec = spring(dampingRatio = 0.85f, stiffness = 300f)
+        )
     }
 
-    Dialog(onDismissRequest = onDismiss, properties = DialogProperties(decorFitsSystemWindows = false)) {
+    Dialog(
+        onDismissRequest = onDismiss,
+        properties = DialogProperties(decorFitsSystemWindows = false)
+    ) {
         val activityWindow = ctx.window
         val dialogWindow = ctx.getDialogWindow()
         SideEffect {
@@ -227,7 +238,10 @@ fun FormDialog(
                 // 更新窗口属性
                 dialogWindow.attributes = attributes
                 // 设置窗口的宽度和高度,这段代码Dialog源码中就有哦,可以自己去查看
-                dialogWindow.setLayout(activityWindow.decorView.width, activityWindow.decorView.height)
+                dialogWindow.setLayout(
+                    activityWindow.decorView.width,
+                    activityWindow.decorView.height
+                )
             }
         }
         Box(
@@ -251,7 +265,10 @@ fun FormDialog(
                     .graphicsLayer { translationY = offsetY.value * size.height }
                     .fillMaxWidth()
                     // 防止点击穿透
-                    .clickable(indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = {})
+                    .clickable(
+                        indication = null,
+                        interactionSource = remember { MutableInteractionSource() },
+                        onClick = {})
                     .background(Color.White, RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp))
             ) {
                 Column(
@@ -268,7 +285,7 @@ fun FormDialog(
                     ) {
                         Spacer(Modifier.size(40.dp))
                         Text(
-                            "我是标题",
+                            title,
                             fontSize = 16.sp,
                             fontWeight = FontWeight.Bold,
                             color = Text,

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

@@ -38,6 +38,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.text.font.FontWeight
@@ -62,6 +63,7 @@ fun HomeCompose(pv: PaddingValues, zIndex: Float, vmHome: VMHome) {
             .fillMaxSize()
             .zIndex(zIndex)
             .background(MaterialTheme.colorScheme.background)
+            .pointerInput(Unit) {},
     ) {
         // 顶部工具栏
         TopToolBar(pv, vmHome)

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

@@ -35,6 +35,7 @@ import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.text.font.FontWeight
@@ -55,6 +56,7 @@ fun JobsCompose(pv: PaddingValues, zIndex: Float, vm: VMHome) {
             .fillMaxSize()
             .zIndex(zIndex)
             .background(Color.White)
+            .pointerInput(Unit) {},
     ) {
         // 顶部工具栏
         Column(

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

@@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.zIndex
 
 @Composable
@@ -18,7 +19,8 @@ fun MyCompose(pv: PaddingValues, zIndex: Float) {
         modifier = Modifier
             .fillMaxSize()
             .zIndex(zIndex)
-            .background(Color.White),
+            .background(Color.White)
+            .pointerInput(Unit) {},
         verticalArrangement = Arrangement.Center,
         horizontalAlignment = Alignment.CenterHorizontally
     ) {

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

@@ -33,6 +33,7 @@ import androidx.compose.ui.draw.clip
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
@@ -50,6 +51,7 @@ fun TasksCompose(pv: PaddingValues, zIndex: Float, vm: VMHome) {
             .fillMaxSize()
             .zIndex(zIndex)
             .background(Color.White)
+            .pointerInput(Unit) {},
     ) {
 
         // 顶部工具栏

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

@@ -3,13 +3,18 @@ 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.FormField
+import com.iscs.bozzys.api.Node
 import com.iscs.bozzys.api.Task
 import com.iscs.bozzys.api.TaskFormInfo
-import com.iscs.bozzys.api.Node
 import com.iscs.bozzys.ui.base.VMBase
+import com.iscs.bozzys.ui.pages.compose.getFormListByJsonList
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.launch
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
 
 /**
  * 任务详情页面vm
@@ -22,10 +27,17 @@ class VMDetailTask : VMBase() {
     // 对Compose暴露
     val state = _state.asStateFlow()
 
+    // 任务信息
+    private lateinit var task: Task
+
+    // 任务表单信息
+    private lateinit var formInfo: TaskFormInfo
+
     /**
      * 获取任务表单信息
      */
     fun getTaskFormInfo(task: Task) {
+        this.task = task
         viewModelScope.launch {
             loading.emit(StateLoading(show = true))
             ApiRequest.getTaskInfoByNodeId(task.nodeId).onSuccess {
@@ -35,27 +47,49 @@ class VMDetailTask : VMBase() {
                     toast.emit("任务数据异常")
                     return@launch
                 }
-                ApiRequest.getTaskFormInfoByFormId(it.data?.formId ?: 0).onSuccess { formInfo ->
-                    loading.emit(StateLoading(show = false))
-                    val formInfo = formInfo.data
-                    if (formInfo == null) {
-                        toast.emit("表单数据异常")
-                        return@launch
+                if (!taskInfo.formData.isNullOrEmpty()) {
+                    val json = Json { ignoreUnknownKeys = true }
+                    formInfo = json.decodeFromString<TaskFormInfo>(taskInfo.formData)
+                    // 有些表单不需要显示的逻辑处理
+                    if (listOf("isolation").contains(taskInfo.type)) formInfo = TaskFormInfo()
+                    _state.value = _state.value.copy(forms = formInfo.fields.getFormListByJsonList(), node = taskInfo)
+                    delay(500)
+                    loading.emit(StateLoading())
+                } else {
+                    ApiRequest.getTaskFormInfoByFormId(it.data?.formId ?: 0).onSuccess { formInfo ->
+                        this@VMDetailTask.formInfo = formInfo.data ?: TaskFormInfo()
+                        _state.value = _state.value.copy(forms = this@VMDetailTask.formInfo.fields.getFormListByJsonList(), node = taskInfo)
+                        delay(500)
+                        loading.emit(StateLoading())
+                    }.onFailure { err ->
+                        delay(500)
+                        loading.emit(StateLoading())
+                        toast.emit("获取表单异常:${err.getResponse().msg}")
                     }
-                    _state.value = _state.value.copy(formInfo = formInfo, node = taskInfo)
-                }.onFailure { err ->
-                    loading.emit(StateLoading(show = false))
-                    toast.emit("获取表单异常:${err.getResponse().msg}")
                 }
             }.onFailure {
+                delay(500)
                 loading.emit(StateLoading(show = false))
                 toast.emit("获取任务详情异常:${it.getResponse().msg}")
             }
         }
     }
+
+    /**
+     * 提交按钮被点击
+     */
+    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)
+    }
 }
 
 /**
  * 页面状态类
  */
-data class StateDetailTask(val formInfo: TaskFormInfo = TaskFormInfo(), val node: Node = Node())
+data class StateDetailTask(val forms: List<FormField> = listOf(), val node: Node = Node())

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

@@ -1,5 +1,6 @@
 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
@@ -7,10 +8,16 @@ import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.geometry.Offset
 import androidx.lifecycle.viewModelScope
 import com.iscs.bozzys.api.ApiRequest
+import com.iscs.bozzys.api.ApiRequest.getResponse
+import com.iscs.bozzys.api.Dict
 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.pages.edit.step.compose.Connection
 import com.iscs.bozzys.ui.pages.edit.step.compose.NodeUI
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.launch
@@ -23,7 +30,99 @@ class VMEditStep : VMBase() {
     private val _state = MutableStateFlow(StateEditStep())
     val state = _state.asStateFlow()
 
+    // 当前工作id
+    private var id = 0
+
+    // 负责人列表
+    private val workerUserList = ArrayList<User>()
+
+    // 上锁/解锁人列表
+    private val lockerUserList = ArrayList<User>()
+
+    // 共锁人列表
+    private val groupUserList = ArrayList<User>()
+
+    // 业务表单列表
+    private val workFormList = ArrayList<TaskFormInfo>()
+
+    // 隔离方式列表
+    private val isolationMethodList = ArrayList<Dict>()
+
+    // 点位列表
+    private val isolationList = ArrayList<IsolationPoint>()
+
     fun init(id: Int) {
+        this.id = id
+        viewModelScope.launch {
+            loading.emit(StateLoading(show = true))
+            // 获取负责人列表
+            val workerRsp = ApiRequest.getUsersByRoleCode("jtdrawer").getOrNull()
+            if ((workerRsp?.code ?: -1) == 0) {
+                workerUserList.clear()
+                workerUserList.addAll(workerRsp?.data ?: emptyList())
+            } else {
+                loading.emit(StateLoading())
+                toast.emit("获取负责人异常")
+                return@launch
+            }
+            // 获取上锁人列表
+            val lockerRsp = ApiRequest.getUsersByRoleCode("jtlocker").getOrNull()
+            if ((lockerRsp?.code ?: -1) == 0) {
+                lockerUserList.clear()
+                lockerUserList.addAll(lockerRsp?.data ?: emptyList())
+            } else {
+                loading.emit(StateLoading())
+                toast.emit("获取上锁人异常")
+                return@launch
+            }
+            // 获取共锁人列表
+            val groupRsp = ApiRequest.getUsersByRoleCode("jtcolocker").getOrNull()
+            if ((groupRsp?.code ?: -1) == 0) {
+                groupUserList.clear()
+                groupUserList.addAll(groupRsp?.data ?: emptyList())
+            } else {
+                loading.emit(StateLoading())
+                toast.emit("获取共锁人异常")
+                return@launch
+            }
+            // 获取业务表单列表
+            val workFormRsp = ApiRequest.getBpmFormList(mutableMapOf("pageNo" to 1, "pageSize" to -1)).getOrNull()
+            if ((workFormRsp?.code ?: -1) == 0) {
+                workFormList.clear()
+                workFormList.addAll(workFormRsp?.data?.list ?: emptyList())
+            } else {
+                loading.emit(StateLoading())
+                toast.emit("获取业务表单异常")
+                return@launch
+            }
+            // 获取隔离方式
+            val isolationTypeRsp = ApiRequest.getDictData(mutableMapOf("pageNo" to 1, "pageSize" to -1, "dictType" to "isolation_method")).getOrNull()
+            if ((isolationTypeRsp?.code ?: -1) == 0) {
+                isolationMethodList.clear()
+                isolationMethodList.addAll(isolationTypeRsp?.data?.list ?: emptyList())
+            } else {
+                loading.emit(StateLoading())
+                toast.emit("获取隔离方式异常")
+                return@launch
+            }
+            // 获取点位列表
+            val isolationRsp = ApiRequest.getIsolationPointList(mutableMapOf("pageNo" to 1, "pageSize" to -1)).getOrNull()
+            if ((isolationRsp?.code ?: -1) == 0) {
+                isolationList.clear()
+                isolationList.addAll(isolationRsp?.data?.list ?: emptyList())
+            } else {
+                loading.emit(StateLoading())
+                toast.emit("获取隔离点位异常")
+                return@launch
+            }
+            getJobInfo()
+        }
+    }
+
+    /**
+     * 获取作业信息
+     */
+    fun getJobInfo() {
         viewModelScope.launch {
             ApiRequest.getJobById(id).onSuccess {
                 val nodes = it.data?.toUINodeMap() ?: mutableStateMapOf()
@@ -35,7 +134,11 @@ class VMEditStep : VMBase() {
                     connections = connections,
                     node = if (node == null) _state.value.node else mutableStateOf(node)
                 )
-            }.onFailure { }
+                loading.emit(StateLoading())
+            }.onFailure {
+                loading.emit(StateLoading())
+                toast.emit(it.getResponse().msg)
+            }
         }
     }
 
@@ -45,7 +148,20 @@ class VMEditStep : VMBase() {
      * @param nodeUI
      */
     fun updateNode(nodeUI: NodeUI) {
-        _state.value = _state.value.copy(node = mutableStateOf(nodeUI))
+        // 这里不得不在这里进行校验,隔离方式,因为这个隔离方式变化会影响表单结构变化
+        val isLocker = (nodeUI.node?.isolationType ?: "") == "1"
+        _state.value = _state.value.copy(
+            node = mutableStateOf(nodeUI),
+            nodeForms = nodeUI.node?.buildFormList(
+                workerUserList,
+                lockerUserList,
+                groupUserList,
+                workFormList,
+                isolationMethodList,
+                isolationList,
+                isLocker = isLocker
+            ) ?: emptyList()
+        )
     }
 
     /**
@@ -54,6 +170,77 @@ class VMEditStep : VMBase() {
     fun formDialogShow(show: Boolean) {
         _state.value = _state.value.copy(showFormDialog = show)
     }
+
+    /**
+     * 节点表单变化监听
+     *
+     * @param form  变化的表单
+     */
+    fun onNodeFormChanged(form: FormField) {
+        // 如果是隔离方式变化
+        if (form.name == "isolationType") {
+            val value = form.value.getOrNull(0) ?: "-1"
+            val nodeUI = _state.value.node.value
+            if (value == "1") {
+                // 上锁人,这里要变换表单数据
+                _state.value = _state.value.copy(
+                    nodeForms = nodeUI.node?.buildFormList(
+                        workerUserList,
+                        lockerUserList,
+                        groupUserList,
+                        workFormList,
+                        isolationMethodList,
+                        isolationList,
+                        isLocker = true
+                    ) ?: emptyList()
+                )
+            } else {
+                // 盲板和拆除
+                _state.value = _state.value.copy(
+                    nodeForms = nodeUI.node?.buildFormList(
+                        workerUserList,
+                        lockerUserList,
+                        groupUserList,
+                        workFormList,
+                        isolationMethodList,
+                        isolationList,
+                        isLocker = false
+                    ) ?: emptyList()
+                )
+            }
+        }
+    }
+
+    /**
+     * 节点保存按钮点击
+     */
+    fun onNodeSave() {
+        viewModelScope.launch {
+            val node = _state.value.node.value.node ?: return@launch
+            val forms = _state.value.nodeForms
+            val tips = node.checkSubmitParams(forms)
+            if (tips.isNotEmpty()) {
+                toast.emit(tips)
+                return@launch
+            }
+            loading.emit(StateLoading(show = true))
+            val params = node.buildSubmitParams(forms, workFormList)
+            ApiRequest.updateJobNodeInfo(params).onSuccess {
+                // 增加视觉停留效果
+                delay(500)
+                // 更新当前编辑的节点
+                _state.value.nodes[node.id.toString()] = _state.value.node.value.copy(node = node.updateNodeInfo(forms))
+                // 关闭Dialog
+                loading.emit(StateLoading())
+                _state.value = _state.value.copy(showFormDialog = false)
+                toast.emit("保存成功")
+            }.onFailure {
+                delay(500)
+                loading.emit(StateLoading())
+                toast.emit(it.getResponse().msg)
+            }
+        }
+    }
 }
 
 /**

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

@@ -68,14 +68,16 @@ class VMHome : VMBase() {
             // approved     已审核
             // "approvalStatus" to "unaudited"
             ApiRequest.getTasks(hashMapOf("pageNo" to 1, "pageSize" to 3)).onSuccess {
-                loading.emit(StateLoading(show = false))
                 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)
+                delay(500)
+                loading.emit(StateLoading())
             }.onFailure {
                 _state.value = _state.value.copy(isHomeTabRefresh = false)
-                loading.emit(StateLoading(show = false))
+                delay(500)
+                loading.emit(StateLoading())
                 toast.emit(it.getResponse().msg)
             }
         }

+ 23 - 1
app/src/main/java/com/iscs/bozzys/utils/DateUtil.kt

@@ -1,9 +1,13 @@
 package com.iscs.bozzys.utils
 
+import android.os.Build
+import java.text.SimpleDateFormat
 import java.time.Instant
 import java.time.LocalDate
 import java.time.ZoneId
 import java.time.format.DateTimeFormatter
+import java.util.Locale
+import java.util.TimeZone
 
 object DateUtil {
 
@@ -20,7 +24,11 @@ object DateUtil {
      * 将传入的时间String格式化为pattern格式的时间戳
      */
     fun String.dateToTimestamp(pattern: String): Long {
-        val date = this.ifEmpty { System.currentTimeMillis().format(pattern) }
+        val date = if (this.contains("T") && this.contains("Z")) {
+            this.iso8601ToTimestamp().format(pattern)
+        } else {
+            this.ifEmpty { System.currentTimeMillis().format(pattern) }
+        }
         val formatter = DateTimeFormatter.ofPattern(pattern)
         val localDate = LocalDate.parse(date, formatter)
         return localDate
@@ -29,4 +37,18 @@ object DateUtil {
             .toEpochMilli()
     }
 
+    /**
+     * 将ISO-8601时间格式化为时间戳
+     */
+    fun String.iso8601ToTimestamp(): Long {
+        return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+            val sdf = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US)
+            sdf.timeZone = TimeZone.getTimeZone("UTC")
+            sdf.parse(this)?.time ?: 0L
+        } else {
+            val formatter = DateTimeFormatter.ISO_INSTANT
+            Instant.from(formatter.parse(this)).toEpochMilli()
+        }
+    }
+
 }

+ 12 - 14
app/src/main/java/com/iscs/bozzys/utils/SerializerUtil.kt

@@ -3,14 +3,16 @@ 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.buildSerialDescriptor
 import kotlinx.serialization.encoding.Decoder
 import kotlinx.serialization.encoding.Encoder
+import kotlinx.serialization.json.Json
 import kotlinx.serialization.json.JsonArray
 import kotlinx.serialization.json.JsonDecoder
-import kotlinx.serialization.json.JsonEncoder
 import kotlinx.serialization.json.JsonNull
 import kotlinx.serialization.json.JsonPrimitive
 import kotlinx.serialization.json.contentOrNull
@@ -19,18 +21,15 @@ import kotlinx.serialization.json.jsonPrimitive
 /**
  * 解析多类型字段
  */
-object PlaceholderSerializer : KSerializer<List<String>> {
+object StringToListSerializer : KSerializer<List<String>> {
 
     @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
     override val descriptor: SerialDescriptor = buildSerialDescriptor("Placeholder", SerialKind.CONTEXTUAL)
 
     override fun deserialize(decoder: Decoder): List<String> {
-        val jsonDecoder = decoder as? JsonDecoder
-            ?: error("This serializer can be used only with Json")
+        val jsonDecoder = decoder as? JsonDecoder ?: error("This serializer can be used only with Json")
 
-        val element = jsonDecoder.decodeJsonElement()
-
-        return when (element) {
+        return when (val element = jsonDecoder.decodeJsonElement()) {
             is JsonPrimitive -> {
                 if (element.isString) {
                     listOf(element.content)
@@ -46,17 +45,16 @@ object PlaceholderSerializer : KSerializer<List<String>> {
             }
 
             JsonNull -> emptyList()
-
             else -> emptyList()
         }
     }
 
     override fun serialize(encoder: Encoder, value: List<String>) {
-        val jsonEncoder = encoder as? JsonEncoder
-            ?: error("This serializer can be used only with Json")
-
-        jsonEncoder.encodeJsonElement(
-            JsonArray(value.map { JsonPrimitive(it) })
-        )
+        if (value.size <= 1) {
+            encoder.encodeString(value.getOrNull(0) ?: "")
+        } else {
+            val jsonStr = Json.encodeToString(ListSerializer(String.serializer()), value)
+            encoder.encodeString(jsonStr)
+        }
     }
 }

+ 6 - 1
app/src/main/java/com/iscs/bozzys/utils/network/Request.kt

@@ -1,5 +1,6 @@
 package com.iscs.bozzys.utils.network
 
+import android.annotation.SuppressLint
 import okhttp3.OkHttpClient
 import okhttp3.logging.HttpLoggingInterceptor
 import retrofit2.Retrofit
@@ -17,7 +18,9 @@ import javax.net.ssl.X509TrustManager
 object Request {
 
     // 网络请求的基地址
-    private const val BASE_URL = "http://192.168.0.10:48080"
+    // 外网IP "http://120.27.232.27:48080"
+    // 本地IP "http://192.168.0.10:48080"
+    private const val BASE_URL = "http://120.27.232.27:48080"
 
     // 构建请求客户端
     private val okClient = OkHttpClient.Builder()
@@ -37,6 +40,7 @@ object Request {
     /**
      * 创建一个证书工厂
      */
+    @SuppressLint("TrustAllX509TrustManager", "CustomX509TrustManager")
     private fun getSocketFactory(): SSLSocketFactory {
         // 创建一个不验证证书链的 TrustManager
         val trustAllCerts = arrayOf<TrustManager>(
@@ -56,6 +60,7 @@ object Request {
     /**
      * 创建一个不验证证书链的 TrustManager
      */
+    @SuppressLint("TrustAllX509TrustManager", "CustomX509TrustManager")
     private fun createTrustManager(): X509TrustManager {
         val trustAllCerts = arrayOf<TrustManager>(
             object : X509TrustManager {