Jelajahi Sumber

1. 新增作业创建界面
2. 表单组件封装(输入、多行输入、下拉选择、单选)
3. 隔离方式列表选择

bjb 4 hari lalu
induk
melakukan
26c72191cc

+ 1 - 1
.idea/deploymentTargetSelector.xml

@@ -4,7 +4,7 @@
     <selectionStates>
       <SelectionState runConfigName="app">
         <option name="selectionMode" value="DROPDOWN" />
-        <DropdownSelection timestamp="2025-12-17T03:53:45.407760100Z">
+        <DropdownSelection timestamp="2025-12-19T09:39:06.338545300Z">
           <Target type="DEFAULT_BOOT">
             <handle>
               <DeviceId pluginId="PhysicalDevice" identifier="serial=32923c41" />

+ 1 - 0
app/build.gradle.kts

@@ -64,6 +64,7 @@ dependencies {
     implementation(libs.push.third)
     implementation(libs.push.third.xiaomi)
     implementation(libs.androidx.ui)
+    implementation(libs.androidx.ui.graphics)
 
     testImplementation(libs.junit)
     androidTestImplementation(libs.androidx.junit)

+ 11 - 0
app/src/main/AndroidManifest.xml

@@ -38,6 +38,17 @@
             android:exported="true"
             android:launchMode="singleTask"
             android:screenOrientation="portrait" />
+        <!--  选择流程类型  -->
+        <activity
+            android:name=".ui.pages.select.job.PageSelectJobType"
+            android:exported="true"
+            android:screenOrientation="portrait" />
+        <!--  创建作业  -->
+        <activity
+            android:name=".ui.pages.create.job.PageCreateJob"
+            android:exported="true"
+            android:screenOrientation="portrait"
+            android:windowSoftInputMode="adjustNothing" />
         <!--  编辑流程  -->
         <activity
             android:name=".ui.pages.edit.step.PageEditStep"

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

@@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
+import com.iscs.bozzys.ui.pages.vm.VMLoading
 
 /**
  * 全局Loading弹窗

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

@@ -19,6 +19,8 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.WindowInsetsControllerCompat
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.viewmodel.compose.viewModel
+import com.iscs.bozzys.ui.pages.vm.StateLoading
+import com.iscs.bozzys.ui.pages.vm.VMLoading
 import com.iscs.bozzys.ui.theme.BozzysTheme
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay

+ 72 - 0
app/src/main/java/com/iscs/bozzys/ui/base/Title.kt

@@ -0,0 +1,72 @@
+package com.iscs.bozzys.ui.base
+
+import android.app.Activity
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+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.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+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.draw.clip
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+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.unit.dp
+import androidx.compose.ui.unit.sp
+import com.iscs.bozzys.R
+
+@Composable
+fun Title(pv: PaddingValues, title: String) {
+    val ctx = LocalContext.current
+    Column(
+        Modifier
+            .fillMaxWidth()
+            .background(brush = Brush.horizontalGradient(listOf(Color(0xFFFF8C00), Color(0xFFFFA500))))
+            .padding(top = pv.calculateTopPadding())
+    ) {
+        Row(
+            Modifier
+                .fillMaxWidth()
+                .height(50.dp)
+                .padding(horizontal = 16.dp),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            Icon(
+                painter = painterResource(R.drawable.back),
+                contentDescription = "",
+                modifier = Modifier
+                    .size(32.dp)
+                    .clip(RoundedCornerShape(50))
+                    .background(Color(0xFFFFA126))
+                    .clickable(onClick = {
+                        if (ctx is Activity) ctx.finish()
+                    })
+                    .padding(8.dp),
+                tint = Color.White
+            )
+            Text(
+                title,
+                Modifier.weight(1f),
+                fontSize = 18.sp,
+                lineHeight = 50.sp,
+                fontWeight = FontWeight.Bold,
+                textAlign = TextAlign.Center,
+                color = Color.White
+            )
+            Spacer(Modifier.size(40.dp))
+        }
+    }
+}

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

@@ -1,6 +1,7 @@
 package com.iscs.bozzys.ui.base
 
 import androidx.lifecycle.ViewModel
+import com.iscs.bozzys.ui.pages.vm.StateLoading
 import kotlinx.coroutines.flow.MutableSharedFlow
 
 /**

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

@@ -26,7 +26,7 @@ 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.pages.edit.step.openPageEditStep
+import com.iscs.bozzys.ui.pages.home.openPageHome
 import com.iscs.bozzys.ui.pages.login.openPageLogin
 import com.iscs.bozzys.utils.Storage
 import kotlinx.coroutines.delay
@@ -60,7 +60,7 @@ class PageSplash : PageBase() {
             // 做SDK的初始化操作,有一些没必要在Application中初始化操作的,在这里进行初始化操作
             initSDK()
             delay(3000)
-            if (Storage.isLogin()) openPageEditStep() else openPageLogin()
+            if (Storage.isLogin()) openPageHome() else openPageLogin()
             destroyDelay(1000)
         }
     }

+ 63 - 0
app/src/main/java/com/iscs/bozzys/ui/pages/compose/CardContainer.kt

@@ -0,0 +1,63 @@
+package com.iscs.bozzys.ui.pages.compose
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.clipToBounds
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Paint
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.nativeCanvas
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+
+/**
+ * 在部分老设备上没有效果
+ */
+@Composable
+fun CardContainer(
+    modifier: Modifier = Modifier,
+    cornerRadius: Dp = 12.dp,
+    shadowColor: Color = Color.Black.copy(alpha = 0.15f),
+    shadowBlur: Dp = 10.dp,
+    backgroundColor: Color = Color.White,
+    content: @Composable BoxScope.() -> Unit
+) {
+    val density = LocalDensity.current
+
+    Box(
+        modifier = modifier
+            // 强制软件渲染
+            .graphicsLayer {
+                this.renderEffect = null
+                this.shadowElevation = 0f
+            }
+            .drawBehind {
+                val radius = with(density) { cornerRadius.toPx() }
+                val blur = with(density) { shadowBlur.toPx() }
+
+                drawIntoCanvas { canvas ->
+                    val paint = Paint().asFrameworkPaint().apply {
+                        isAntiAlias = true
+                        color = backgroundColor.toArgb()
+                        setShadowLayer(blur, 0f, 0f, shadowColor.toArgb())
+                    }
+                    canvas.nativeCanvas.drawRoundRect(0f, 0f, size.width, size.height, radius, radius, paint)
+                }
+            }
+            .clip(RoundedCornerShape(cornerRadius))
+            // 内容背景
+            .background(backgroundColor, RoundedCornerShape(cornerRadius))
+    ) {
+        content()
+    }
+}
+

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

@@ -0,0 +1,239 @@
+package com.iscs.bozzys.ui.pages.compose
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.DropdownMenu
+import androidx.compose.material3.DropdownMenuItem
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.RadioButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.iscs.bozzys.R
+import com.iscs.bozzys.ui.theme.Main
+
+/**
+ * 表单输入框
+ *
+ * @param label         表单的标题
+ * @param value         表单的内容
+ * @param onValueChange 表单内容发生变化
+ */
+@Composable
+fun FormInput(label: String, value: String, onValueChange: (String) -> Unit) {
+    Column(Modifier.fillMaxWidth()) {
+        Row(
+            Modifier
+                .fillMaxWidth()
+                .height(40.dp), verticalAlignment = Alignment.CenterVertically
+        ) {
+            Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold)
+            Text("*", fontSize = 14.sp, color = Color(0xFFFF4D4F), modifier = Modifier.padding(start = 3.dp))
+        }
+        BasicTextField(
+            value,
+            onValueChange = onValueChange,
+            Modifier
+                .fillMaxWidth()
+                .height(46.dp)
+                .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),
+            decorationBox = { innerTextField ->
+                Box(contentAlignment = Alignment.CenterStart) {
+                    innerTextField()
+                    if (value.isEmpty()) {
+                        val text = "请输入$label"
+                        Text(text, color = Color(0xFF9CA3AF), fontSize = 14.sp, lineHeight = 18.sp)
+                    }
+                }
+            },
+            cursorBrush = SolidColor(Main),
+            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
+        )
+    }
+}
+
+/**
+ * 表单选择器
+ *
+ * @param label             标题
+ * @param value             当前选中
+ * @param options           选择列表
+ * @param onSelectChange    选择变化监听
+ */
+@Composable
+fun <T> FormSelect(label: String, value: Pair<String, T>, options: List<Pair<String, T>>, onSelectChange: (Pair<String, T>) -> Unit) {
+    var expanded by remember { mutableStateOf(false) }
+    var width by remember { mutableStateOf(0) }
+    val density = LocalDensity.current
+    Column(
+        Modifier
+            .fillMaxWidth()
+            .onSizeChanged({ width = it.width })
+    ) {
+        Row(
+            Modifier
+                .fillMaxWidth()
+                .height(40.dp), verticalAlignment = Alignment.CenterVertically
+        ) {
+            Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold)
+            Text("*", fontSize = 14.sp, color = Color(0xFFFF4D4F), modifier = Modifier.padding(start = 3.dp))
+        }
+        Row(
+            Modifier
+                .fillMaxWidth()
+                .height(46.dp)
+                .border(1.dp, shape = RoundedCornerShape(6.dp), color = Color(0xFFE5E6EB))
+                .clip(RoundedCornerShape(6.dp))
+                .clickable(onClick = { expanded = true })
+                .padding(horizontal = 10.dp),
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            Text(
+                value.first,
+                color = Color.Black,
+                fontSize = 14.sp,
+                lineHeight = 18.sp,
+                modifier = Modifier.weight(1f)
+            )
+            Icon(
+                painter = painterResource(R.drawable.back), contentDescription = "", modifier = Modifier
+                    .rotate(if (expanded) -90f else 180f)
+                    .size(12.dp), tint = Color.Black
+            )
+        }
+        val widthDp = with(density) { width.toDp() }
+        DropdownMenu(
+            expanded = expanded, onDismissRequest = { expanded = false },
+            modifier = Modifier.width(widthDp),
+            offset = DpOffset(x = 0.dp, y = 5.dp)
+        ) {
+            options.forEach { option ->
+                DropdownMenuItem(
+                    modifier = Modifier.fillMaxWidth(),
+                    text = { Text(option.first) },
+                    onClick = {
+                        onSelectChange(option)
+                        expanded = false
+                    }
+                )
+            }
+        }
+    }
+}
+
+/**
+ * 表单多行文本输入
+ *
+ * @param label         标题
+ * @param value         输入的内容
+ * @param onValueChange 输入内容发生变化
+ * @param placeholder   占位内容
+ */
+@Composable
+fun FormTextarea(label: String, value: String, onValueChange: (String) -> Unit, placeholder: String = "") {
+    Column(Modifier.fillMaxWidth()) {
+        Row(
+            Modifier
+                .fillMaxWidth()
+                .height(40.dp), verticalAlignment = Alignment.CenterVertically
+        ) {
+            Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold)
+            Text("*", fontSize = 14.sp, color = Color(0xFFFF4D4F), modifier = Modifier.padding(start = 3.dp))
+        }
+        BasicTextField(
+            value,
+            onValueChange = onValueChange,
+            Modifier
+                .fillMaxWidth()
+                .heightIn(min = 120.dp)
+                .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),
+            decorationBox = { innerTextField ->
+                Box(contentAlignment = Alignment.TopStart) {
+                    innerTextField()
+                    if (value.isEmpty()) {
+                        val text = placeholder.ifEmpty { "请输入$label" }
+                        Text(text, color = Color(0xFF9CA3AF), fontSize = 14.sp, lineHeight = 18.sp)
+                    }
+                }
+            },
+            cursorBrush = SolidColor(Main),
+            keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text)
+        )
+    }
+}
+
+/**
+ * 表单单选组件
+ *
+ * @param label             标题
+ * @param value             当前选中
+ * @param options           可选列表
+ * @param onSelectChange    当前选中
+ */
+@Composable
+fun <T> FormRadio(label: String, value: Pair<String, T>, options: List<Pair<String, T>>, onSelectChange: (Pair<String, T>) -> Unit) {
+    Column(Modifier.fillMaxWidth()) {
+        Row(
+            Modifier
+                .fillMaxWidth()
+                .height(40.dp), verticalAlignment = Alignment.CenterVertically
+        ) {
+            Text(label, fontSize = 14.sp, fontWeight = FontWeight.Bold)
+            Text("*", fontSize = 14.sp, color = Color(0xFFFF4D4F), modifier = Modifier.padding(start = 3.dp))
+        }
+        FlowRow(Modifier.fillMaxWidth()) {
+            options.forEach { item ->
+                Row(
+                    modifier = Modifier
+                        .clip(RoundedCornerShape(12.dp))
+                        .clickable { onSelectChange(item) }
+                        .padding(horizontal = 10.dp, vertical = 5.dp),
+                    verticalAlignment = Alignment.CenterVertically
+                ) {
+                    RadioButton(selected = value == item, onClick = null, modifier = Modifier.size(14.dp))
+                    Text(text = item.first, fontSize = 15.sp, modifier = Modifier.padding(start = 10.dp))
+                }
+            }
+        }
+    }
+}

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

@@ -0,0 +1,108 @@
+package com.iscs.bozzys.ui.pages.create.job
+
+import android.content.Context
+import android.content.Intent
+import androidx.compose.foundation.background
+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.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+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.res.painterResource
+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.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.edit.step.openPageEditStep
+
+fun Context.openPageCreateJob() {
+    startActivity(Intent(this, PageCreateJob::class.java))
+}
+
+class PageCreateJob : PageBase() {
+
+    @Composable
+    override fun GetViews(pv: PaddingValues) {
+        // 作业名称
+        var jobName by remember { mutableStateOf("") }
+        // 作业分类
+        val typeOptions = mutableStateListOf("请选择分类" to "", "维修" to "repair", "维保" to "check", "其他" to "other")
+        var typeSelect by remember { mutableStateOf("请选择分类" to "") }
+        // 作业内容
+        var jobContent by remember { mutableStateOf("") }
+        // 作业紧急程度
+        val levelOptions = mutableStateListOf("普通" to "Normal", "紧急" to "Middle", "非常紧急" to "High")
+        var jobLevel by remember { mutableStateOf(levelOptions[0]) }
+        Column(
+            Modifier
+                .fillMaxSize()
+                .background(Color(0xFFF8F9FA))
+        ) {
+            Title(pv, "新建作业-电气隔离")
+            // 表单内容
+            Column(Modifier.padding(horizontal = 16.dp, vertical = 5.dp)) {
+                // 作业名称
+                FormInput("作业名称", jobName, { jobName = it })
+                // 作业分类
+                FormSelect("作业分类", typeSelect, typeOptions, { typeSelect = it })
+                // 作业内容
+                FormTextarea("作业内容", jobContent, { jobContent = it })
+                // 作业紧急程度
+                FormRadio("紧急程度", jobLevel, levelOptions, { jobLevel = it })
+            }
+            Spacer(Modifier.weight(1f))
+            // 底部下一步按钮
+            Column(
+                Modifier
+                    .fillMaxWidth()
+                    .background(MaterialTheme.colorScheme.background)
+                    .padding(top = 15.dp, bottom = (15f + pv.calculateBottomPadding().value).dp)
+            ) {
+                Button(
+                    { openPageEditStep() }, Modifier
+                        .padding(horizontal = 16.dp)
+                        .fillMaxWidth()
+                        .height(56.dp)
+                        .clip(RoundedCornerShape(12.dp))
+                        .background(MaterialTheme.colorScheme.primary)
+                ) {
+                    Row(verticalAlignment = Alignment.CenterVertically) {
+                        Text("下一步", fontSize = 16.sp, fontWeight = FontWeight.Bold)
+                        Icon(
+                            painter = painterResource(R.drawable.next),
+                            contentDescription = "",
+                            modifier = Modifier
+                                .padding(start = 5.dp)
+                                .size(16.dp)
+                        )
+                    }
+                }
+            }
+        }
+    }
+}

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

@@ -3,9 +3,9 @@ package com.iscs.bozzys.ui.pages.edit.step
 import android.content.Context
 import android.content.Intent
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.offset
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.mutableStateMapOf
@@ -13,10 +13,9 @@ import androidx.compose.runtime.remember
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.unit.dp
 import androidx.compose.ui.zIndex
 import com.iscs.bozzys.ui.base.PageBase
+import com.iscs.bozzys.ui.base.Title
 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.Node
@@ -40,23 +39,23 @@ class PageEditStep : PageBase() {
             Connection("B", "C")
         )
         LaunchedEffect(Unit) {
-            nodes["A"] = Node("A", Offset(50f, 0f), Size(80))
-            nodes["B"] = Node("B", Offset(250f, 0f), Size(80))
-            nodes["B"] = Node("D", Offset(250f, 0f), Size(80))
-            nodes["C"] = Node("C", Offset(450f, 0f), Size(80))
+            nodes["A"] = Node("A", Offset(50f, 0f))
+            nodes["B"] = Node("B", Offset(250f, 0f))
+            nodes["D"] = Node("D", Offset(250f, 100f))
+            nodes["C"] = Node("C", Offset(450f, 0f))
         }
-        ZoomPanContainer(nodes, lines, modifier = Modifier.fillMaxSize()) {
-            // 控件显示
-            Box(
-                modifier = Modifier
-                    .fillMaxSize()
-                    .zIndex(1f),
-                contentAlignment = Alignment.CenterStart
-            ) {
-                NodeItem("A", nodes, Modifier.offset(50.dp, 0.dp))
-                NodeItem("B", nodes, Modifier.offset(250.dp, 0.dp))
-                NodeItem("D", nodes, Modifier.offset(250.dp, 100.dp))
-                NodeItem("C", nodes, Modifier.offset(450.dp, 0.dp))
+        Column(Modifier.fillMaxSize()) {
+            Title(pv, "作业流程管理")
+            ZoomPanContainer(nodes, lines, modifier = Modifier.fillMaxSize()) {
+                // 控件显示
+                Box(
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .zIndex(3f),
+                    contentAlignment = Alignment.CenterStart
+                ) {
+                    nodes.forEach { NodeItem(it.key, nodes) }
+                }
             }
         }
     }

+ 19 - 14
app/src/main/java/com/iscs/bozzys/ui/pages/edit/step/compose/Node.kt

@@ -5,6 +5,7 @@ import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.material3.Text
@@ -34,7 +35,7 @@ fun ConnectionLayer(nodes: Map<String, Node>, connections: List<Connection>, sca
     Canvas(
         modifier = Modifier
             .fillMaxSize()
-            .zIndex(0f)
+            .zIndex(2f)
     ) {
         connections.forEach { conn ->
             val from = nodes[conn.fromId]
@@ -57,7 +58,7 @@ fun ConnectionLayer(nodes: Map<String, Node>, connections: List<Connection>, sca
                 }
 
                 drawPath(
-                    path = path, color = Main, style = Stroke(width = 6f / scale, cap = StrokeCap.Round, join = StrokeJoin.Round)
+                    path = path, color = Main, style = Stroke(width = 4f / scale, cap = StrokeCap.Round, join = StrokeJoin.Round)
                 )
             }
         }
@@ -68,22 +69,20 @@ fun ConnectionLayer(nodes: Map<String, Node>, connections: List<Connection>, sca
  * 各节点
  */
 @Composable
-fun NodeItem(
-    id: String,
-    nodes: SnapshotStateMap<String, Node>,
-    modifier: Modifier = Modifier
-) {
+fun NodeItem(id: String, nodes: SnapshotStateMap<String, Node>, modifier: Modifier = Modifier) {
+    val node = nodes[id] ?: return
     Box(
         modifier = modifier
             .size(80.dp)
+            .offset(node.position.x.dp, node.position.y.dp)
+            .onGloballyPositioned { coordinates ->
+                nodes[id] = Node(id = id, position = node.position, offset = coordinates.positionInParent(), size = coordinates.size.toSize())
+            }
             .background(Color.White, RoundedCornerShape(8.dp))
-            .border(1.dp, Color.Gray)
-            .onGloballyPositioned { coords ->
-                nodes[id] = Node(id = id, offset = coords.positionInParent(), size = coords.size.toSize())
-            },
+            .border(1.dp, Color.Gray),
         contentAlignment = Alignment.Center
     ) {
-        Text(id)
+        Text(node.id)
     }
 }
 
@@ -171,11 +170,17 @@ private fun orthogonalPath(fromNode: Node, fromAnchor: Anchor, toNode: Node, toA
 
 /**
  * 节点定义
+ *
+ * @param id        用于记录当前节点唯一标记,用于后续连线或者offset和size在缩放布局中的绝对定位
+ * @param position  当前控件的绝对定位
+ * @param offset    在父控件中的位置
+ * @param size      在父控件中的尺寸
  */
 data class Node(
     val id: String,
-    val offset: Offset,
-    val size: Size
+    val position: Offset,
+    val offset: Offset = Offset(0f, 0f),
+    val size: Size = Size(80f, 80f)
 )
 
 /**

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

@@ -19,6 +19,7 @@ import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.TransformOrigin
 import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.zIndex
 import kotlin.math.ceil
 import kotlin.math.floor
 
@@ -77,7 +78,11 @@ fun InfiniteDotBackground(
     radius: Float = 4f,
     color: Color = Color(0xFFD1D1D1)
 ) {
-    Canvas(modifier = Modifier.fillMaxSize()) {
+    Canvas(
+        modifier = Modifier
+            .fillMaxSize()
+            .zIndex(1f)
+    ) {
 
         // 屏幕 → 画布坐标
         val left = -offset.x / scale

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

@@ -1,16 +1,12 @@
 package com.iscs.bozzys.ui.pages.home
 
 import androidx.compose.foundation.background
-import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.ExperimentalLayoutApi
-import androidx.compose.foundation.layout.FlowRow
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
@@ -30,14 +26,15 @@ 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.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.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.compose.ui.zIndex
 import com.iscs.bozzys.R
 import com.iscs.bozzys.ui.pages.compose.JobListItem
+import com.iscs.bozzys.ui.pages.select.job.openPageSelectJobType
 import com.iscs.bozzys.ui.pages.vm.VMHome
 import com.iscs.bozzys.ui.theme.Main
 import com.iscs.bozzys.ui.theme.Text
@@ -67,6 +64,7 @@ fun JobsCompose(pv: PaddingValues, zIndex: Float, vmHome: VMHome) {
  */
 @Composable
 private fun TopToolBar(pv: PaddingValues, vmHome: VMHome) {
+    val ctx = LocalContext.current
     Column(
         modifier = Modifier
             .fillMaxWidth()
@@ -88,7 +86,7 @@ private fun TopToolBar(pv: PaddingValues, vmHome: VMHome) {
                 modifier = Modifier
                     .size(36.dp)
                     .clip(RoundedCornerShape(12.dp))
-                    .clickable(onClick = {})
+                    .clickable(onClick = { ctx.openPageSelectJobType() })
                     .padding(6.dp),
                 tint = Color.White
             )

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

@@ -0,0 +1,174 @@
+package com.iscs.bozzys.ui.pages.select.job
+
+import android.content.Context
+import android.content.Intent
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LocalTextStyle
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+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.graphics.SolidColor
+import androidx.compose.ui.res.painterResource
+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.pages.compose.CardContainer
+import com.iscs.bozzys.ui.pages.create.job.openPageCreateJob
+import com.iscs.bozzys.ui.theme.Main
+
+fun Context.openPageSelectJobType() {
+    startActivity(Intent(this, PageSelectJobType::class.java))
+}
+
+class PageSelectJobType : PageBase() {
+    @Composable
+    override fun GetViews(pv: PaddingValues) {
+        var keywords by remember { mutableStateOf("") }
+        Column(
+            Modifier
+                .fillMaxSize()
+                .background(Color(0xFFF8F9FA))
+        ) {
+            Title(pv, "新建作业")
+            // 搜索框
+            Row(
+                Modifier
+                    .background(brush = Brush.horizontalGradient(listOf(Color(0xFFFF8C00), Color(0xFFFFA500))))
+                    .padding(horizontal = 16.dp, vertical = 8.dp)
+            ) {
+                BasicTextField(
+                    keywords,
+                    onValueChange = { keywords = it },
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .height(40.dp)
+                        .clip(RoundedCornerShape(50))
+                        .background(brush = Brush.horizontalGradient(listOf(Color(0xFFFFA126), Color(0xFFFFB126))))
+                        .padding(horizontal = 16.dp),
+                    singleLine = true,
+                    textStyle = LocalTextStyle.current.copy(fontSize = 16.sp, lineHeight = 18.sp, color = Color.White),
+                    decorationBox = { innerTextField ->
+                        Box(contentAlignment = Alignment.CenterStart) {
+                            innerTextField()
+                            if (keywords.isEmpty()) {
+                                val text = "搜索作业模板名称..."
+                                Text(
+                                    text,
+                                    color = Color.White.copy(alpha = 0.8f),
+                                    fontSize = 16.sp,
+                                    lineHeight = 18.sp
+                                )
+                            }
+                        }
+                    },
+                    cursorBrush = SolidColor(Color.White),
+                )
+            }
+            // 底部显示内容
+            LazyColumn(
+                Modifier
+                    .fillMaxSize()
+                    .padding(vertical = 8.dp)
+            ) {
+                item {
+                    CardContainer(Modifier.padding(horizontal = 16.dp, vertical = 8.dp)) {
+                        // 确定数据后,这里的代码应该抽离为一个组件
+                        Column(Modifier.fillMaxWidth()) {
+                            Row(
+                                Modifier
+                                    .fillMaxWidth()
+                                    .height(40.dp)
+                                    .padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically
+                            ) {
+                                Icon(
+                                    painter = painterResource(R.drawable.code),
+                                    contentDescription = "",
+                                    tint = MaterialTheme.colorScheme.primary,
+                                    modifier = Modifier
+                                        .padding(end = 8.dp)
+                                        .size(14.dp)
+                                )
+                                Text("能量隔离(LOTO)", fontSize = 14.sp, fontWeight = FontWeight.Medium, color = MaterialTheme.colorScheme.primary)
+                            }
+                            Spacer(
+                                Modifier
+                                    .fillMaxWidth()
+                                    .height(1.dp)
+                                    .background(Color(0xFFF0F0F0))
+                            )
+                            val list = listOf(1, 2, 3, 4)
+                            FlowRow(
+                                modifier = Modifier.fillMaxWidth(),
+                                maxItemsInEachRow = 3
+                            ) {
+                                list.forEachIndexed { idx, i ->
+                                    Box(
+                                        Modifier
+                                            .fillMaxWidth((1 / 3f) - 0.0005f) // 这里是为了部分手机显示的问题导致只能显示俩个
+                                            .aspectRatio(1f)
+                                            .background(Color(0xFFF0F0F0))
+                                            .padding(
+                                                end = if ((idx + 1) % 3 == 0) 0.dp else 1.dp,
+                                                bottom = if ((idx + 1) > ((list.size / 3) * 3)) 0.dp else 1.dp
+                                            )
+                                    ) {
+                                        Column(
+                                            Modifier
+                                                .fillMaxSize()
+                                                .background(Color.White)
+                                                .clickable(onClick = { openPageCreateJob() }),
+                                            horizontalAlignment = Alignment.CenterHorizontally,
+                                            verticalArrangement = Arrangement.Center
+                                        ) {
+                                            Icon(
+                                                painter = painterResource(R.drawable.location),
+                                                contentDescription = "",
+                                                modifier = Modifier
+                                                    .fillMaxSize(1 / 3f)
+                                                    .clip(RoundedCornerShape(12.dp))
+                                                    .background(Main)
+                                                    .padding(10.dp),
+                                                tint = Color.White
+                                            )
+                                            Text("能量隔离$i", fontSize = 12.sp, modifier = Modifier.padding(top = 5.dp))
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

+ 1 - 1
app/src/main/java/com/iscs/bozzys/ui/base/VMLoading.kt → app/src/main/java/com/iscs/bozzys/ui/pages/vm/VMLoading.kt

@@ -1,4 +1,4 @@
-package com.iscs.bozzys.ui.base
+package com.iscs.bozzys.ui.pages.vm
 
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf

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

@@ -7,7 +7,6 @@ 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.StateLoading
 import com.iscs.bozzys.ui.base.VMBase
 import com.iscs.bozzys.utils.Storage
 import com.iscs.bozzys.utils.Storage.saveToken

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

@@ -23,7 +23,8 @@ private val DarkColorScheme = darkColorScheme(
     primary = Main,
     secondary = Main,
     tertiary = Main,
-    background = Color.Black
+    background = Color.Black,
+    surfaceContainer = Color.White
 )
 
 // 标准主题配置
@@ -31,7 +32,8 @@ private val LightColorScheme = lightColorScheme(
     primary = Main,
     secondary = Main,
     tertiary = Main,
-    background = Color.White
+    background = Color.White,
+    surfaceContainer = Color.White
 )
 
 @OptIn(ExperimentalFoundationApi::class)

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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:pathData="M230,468.6c-23.8,24.4 -23.8,64 0,88.4l434.4,446c23.8,24.4 62.5,24.4 86.3,0 23.9,-24.4 23.9,-64 0,-88.4L359.4,512.8l391.3,-401.7c23.9,-24.4 23.9,-64 0,-88.4 -23.8,-24.4 -62.5,-24.4 -86.3,0L230,468.6z"
+      android:fillColor="#2c2c2c"/>
+</vector>

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

@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="32dp"
+    android:height="32dp"
+    android:viewportWidth="1024"
+    android:viewportHeight="1024">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M919.3,419L625.1,124.8c-27,-27 -70.8,-27 -97.8,0s-27,70.8 0,97.8l224.9,224.3H136.1c-38.2,0 -69.2,31 -69.2,69.2s31,69.2 69.2,69.2h616.3L527.3,810.2c-27,27 -27,70.8 0,97.8s70.8,27 97.8,0l294.2,-294.2c53.7,-53.8 53.7,-141 0,-194.8"/>
+</vector>

+ 2 - 0
gradle/libs.versions.toml

@@ -11,6 +11,7 @@ composeBom = "2024.09.00"
 coroutines = "1.6.4"
 push = "3.10.1"
 ui = "1.10.0"
+uiGraphics = "1.10.0"
 
 [libraries]
 androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -44,6 +45,7 @@ push = { group = "com.aliyun.ams", name = "alicloud-android-push", version.ref =
 push_third = { group = "com.aliyun.ams", name = "alicloud-android-third-push", version.ref = "push" }
 push_third_xiaomi = { group = "com.aliyun.ams", name = "alicloud-android-third-push-xiaomi", version.ref = "push" }
 androidx-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "ui" }
+androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics", version.ref = "uiGraphics" }
 
 [plugins]
 android-application = { id = "com.android.application", version.ref = "agp" }