浏览代码

完成拆盘

flower_mr 1 周之前
父节点
当前提交
0372759570

+ 7 - 0
app/src/main/java/com/example/pda/model/ContainerData.kt

@@ -14,3 +14,10 @@ data class ContainerData(
     val username:String,
 )
 
+data class ChangeContainerOutQtyData(
+    @SerializedName("container_code")
+    val containerCode: String,
+
+    @SerializedName("detail_list")
+    val detailList: Map<Int, Int>
+)

+ 26 - 3
app/src/main/java/com/example/pda/navigation/NavGraph.kt

@@ -57,7 +57,8 @@ fun NavGraphBuilder.appNavGraph(navController: NavController) {
                 }
             )
         }
-        // 这里使用 query 参数
+
+        // 修改的容器详情界面路由
         composable(
             route = "containerItems?container={container}",
             arguments = listOf(
@@ -70,7 +71,11 @@ fun NavGraphBuilder.appNavGraph(navController: NavController) {
             val container = backStackEntry.arguments?.getString("container") ?: ""
             ContainerItems(
                 container = container,
-                onBack = { navController.popBackStack() }
+                onBack = { navController.popBackStack() },
+                // 添加navToOutOperation实现
+                navToOutOperation = { containerCode ->
+                    navController.navigate("outOperation/$containerCode")
+                }
             )
         }
 
@@ -90,6 +95,24 @@ fun NavGraphBuilder.appNavGraph(navController: NavController) {
             )
         }
 
+        // 出库操作界面路由
+        composable(
+            route = "outOperation/{containerCode}",
+            arguments = listOf(
+                navArgument("containerCode") {
+                    type = NavType.StringType
+                    defaultValue = ""
+                }
+            )
+        ) { backStackEntry ->
+            val containerCode = backStackEntry.arguments?.getString("containerCode") ?: ""
+            OutOperationScreen(
+                containerCode = containerCode,
+                onBack = { navController.popBackStack() },
+                onShelfScan = { navController.navigate("container") },
+            )
+        }
+
         composable("outItems") {
             OutTaskScreen(
                 onBack = { navController.popBackStack() },
@@ -99,4 +122,4 @@ fun NavGraphBuilder.appNavGraph(navController: NavController) {
             )
         }
     }
-}
+}

+ 6 - 0
app/src/main/java/com/example/pda/network/ApiService.kt

@@ -2,6 +2,7 @@ package com.example.pda.network
 
 import com.example.pda.model.BaseResponse
 import com.example.pda.model.ApiResponse
+import com.example.pda.model.ChangeContainerOutQtyData
 import com.example.pda.model.loginItem
 import com.example.pda.model.loginSuccessResult
 import com.example.pda.model.ContainerData
@@ -27,6 +28,11 @@ interface ApiService {
         @Body data: ContainerData
     ): Response<BaseResponse<Unit>>
 
+    @POST("container/pda/change_container_out_qty/")
+    suspend fun changeContainerOutQty(
+        @Body data: ChangeContainerOutQtyData
+    ): Response<BaseResponse<Unit>>
+
     // 从服务器获取库存列表的接口
     @GET("container/pdadetail/")
     suspend fun getInventoryList(

+ 36 - 7
app/src/main/java/com/example/pda/network/NetworkHelper.kt

@@ -3,6 +3,7 @@ package com.example.pda.network
 import android.util.Log
 import com.example.pda.model.ApiResponse
 import com.example.pda.model.BaseResponse
+import com.example.pda.model.ChangeContainerOutQtyData
 import com.example.pda.model.ContainerData
 import com.example.pda.model.ContainerItemDetail
 import com.example.pda.model.loginItem
@@ -116,7 +117,7 @@ object NetworkHelper {
         container_code: String,
         onSuccess: (String) -> Unit,
         onError: (String) -> Unit
-    ){
+    ) {
         CoroutineScope(Dispatchers.IO).launch {
             try {
                 val response = RetrofitClient.instance.confirmOutItems(container_code)
@@ -147,18 +148,18 @@ object NetworkHelper {
         container_code: String,
         onSuccess: (String) -> Unit,
         onError: (String) -> Unit
-    ){
+    ) {
         CoroutineScope(Dispatchers.IO).launch {
             try {
                 val response = RetrofitClient.instance.cancelOutItems(container_code)
                 if (response.isSuccessful) {
                     response.body()?.let {
                         if (it.code == 200) {
-                            if (it.data != null) {
-                                onSuccess(it.message)
-                            } else {
-                                onError(it.message)
-                            }
+//                            if (it.data != null) {
+                            onSuccess(it.message)
+//                            } else {
+//                                onError(it.message)
+//                            }
                         } else {
                             onError(it.message)
                         }
@@ -231,4 +232,32 @@ object NetworkHelper {
         }
 
     }
+
+    fun changeContainerOutQty(
+        container_code: String,
+        items: Map<Int, Int>,
+        onSuccess: (String) -> Unit,
+        onError: (String) -> Unit
+    ) {
+        val data = ChangeContainerOutQtyData(container_code, items)
+        CoroutineScope(Dispatchers.IO).launch {
+            try {
+                val response = RetrofitClient.instance.changeContainerOutQty(data)
+                if (response.isSuccessful) {
+                    response.body()?.let {
+                        if (it.code == 200) {
+                            onSuccess(it.message)
+                        } else {
+                            onError(it.message)
+                        }
+                    }
+                } else {
+                    onError("提示: ${response.message()}")
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "Get failed", e)
+                onError("Network error: ${e.localizedMessage}")
+            }
+        }
+    }
 }

+ 165 - 86
app/src/main/java/com/example/pda/ui/ContainerItems.kt

@@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Clear
 import androidx.compose.material.icons.filled.Delete
 import androidx.compose.material3.*
 import androidx.compose.ui.graphics.Color
@@ -29,48 +30,49 @@ import androidx.compose.ui.Alignment
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.example.pda.model.ContainerItemDetail
 import kotlinx.coroutines.launch
-
-// 托盘屏幕,用于托盘扫描和物料信息显示
+// 托盘详情界面 - 修复关键问题
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
-
 fun ContainerItems(
-    container: String, // 新增参数
+    container: String,
     onBack: () -> Unit,
+    navToOutOperation: (String) -> Unit
 ) {
     val viewModel: InventoryViewModel = viewModel()
-    val uiState = viewModel.uiState.collectAsState()
+    val uiState by viewModel.uiState.collectAsState()
+    val containerDetails by viewModel.containerItemsDetails.collectAsState()
+
     val snackbarHostState = remember { SnackbarHostState() }
     val coroutineScope = rememberCoroutineScope()
-    val inputIp = remember { mutableStateOf("") }
-    val containerState = remember { mutableStateOf(container) }
-    var showInput by remember { mutableStateOf(container.isBlank()) }
 
+    val containerState = remember(container) { mutableStateOf(container) }
+    var showInput by remember {
+        mutableStateOf(container.isBlank())
+    }
+    val inputIp = remember { mutableStateOf("") }
 
-    // 自动加载数据(当传入的 container 非空时)
+    // 自动加载数据
     LaunchedEffect(container) {
         if (container.isNotBlank()) {
             viewModel.getContainerDetail(container)
         }
     }
 
-    // 根据 viewModel 状态显示 Snackbar(保持不变)
-    LaunchedEffect(uiState.value) {
-        when (val currentState = uiState.value) {
+    // 状态处理
+    LaunchedEffect(uiState) {
+        when (val currentState = uiState) {
             is InventoryViewModel.UiState.Success -> {
                 coroutineScope.launch {
                     snackbarHostState.showSnackbar(currentState.message)
                     viewModel.resetState()
                 }
             }
-
             is InventoryViewModel.UiState.Error -> {
                 coroutineScope.launch {
                     snackbarHostState.showSnackbar("错误:${currentState.message}")
                     viewModel.resetState()
                 }
             }
-
             else -> {}
         }
     }
@@ -96,6 +98,26 @@ fun ContainerItems(
                         Text("信泰PDA—托盘详情")
                     }
                 },
+                actions = {
+                    // 出库操作按钮
+                    IconButton(
+                        onClick = {
+                            if (containerDetails.isNotEmpty()) {
+                                navToOutOperation(containerState.value)
+                            } else {
+                                coroutineScope.launch {
+                                    snackbarHostState.showSnackbar("没有可出库的批次")
+                                }
+                            }
+                        }
+                    ) {
+                        Icon(
+                            painter = painterResource(id = R.drawable.ic_out),
+                            contentDescription = "出库操作",
+                            tint = Color.White
+                        )
+                    }
+                },
                 colors = TopAppBarDefaults.topAppBarColors(
                     containerColor = Color(0xFFBCD0C5),
                     titleContentColor = MaterialTheme.colorScheme.onPrimary,
@@ -108,118 +130,175 @@ fun ContainerItems(
             modifier = Modifier
                 .padding(innerPadding)
                 .fillMaxSize()
-                .padding(24.dp)
+                .padding(16.dp)
         ) {
-
-            // 输入区域(仅当需要时显示)
+            // 输入区域
             if (showInput || containerState.value.isBlank()) {
                 Row(
                     modifier = Modifier
                         .fillMaxWidth()
-                        .padding(8.dp),
+                        .padding(bottom = 16.dp),
                     verticalAlignment = Alignment.CenterVertically
                 ) {
-                    TextField(
+                    OutlinedTextField(
                         value = inputIp.value,
                         onValueChange = { inputIp.value = it },
                         label = { Text("请输入托盘编码") },
+                        modifier = Modifier.weight(1f),
+                        trailingIcon = {
+                            IconButton(
+                                onClick = {
+                                    if (inputIp.value.isNotBlank()) {
+                                        containerState.value = inputIp.value
+                                        viewModel.getContainerDetail(containerState.value)
+                                        showInput = false
+                                    }
+                                }
+                            ) {
+                                Icon(Icons.Default.Search, "搜索")
+                            }
+                        }
                     )
-                    IconButton(
-                        onClick = {
-                            containerState.value = inputIp.value
-                            viewModel.getContainerDetail(containerState.value)
-                            showInput = false
-                        },
-                    ) {
-                        Icon(Icons.Default.Search, "确定")
-                    }
-
                 }
             }
 
-            // 托盘编码显示与删除按钮
+            // 托盘编码显示与操作
             Row(
                 modifier = Modifier
                     .fillMaxWidth()
-                    .padding(8.dp),
+                    .padding(bottom = 16.dp),
                 verticalAlignment = Alignment.CenterVertically
             ) {
                 Text(
                     text = "托盘编码:${containerState.value}",
+                    style = MaterialTheme.typography.titleLarge,
                     modifier = Modifier.weight(1f)
                 )
-                IconButton(
-                    onClick = {
-                        containerState.value = ""
-                        showInput = true
-                    },
-                ) {
-                    Icon(Icons.Default.Delete, "删除")
+                if (!showInput) {
+                    IconButton(
+                        onClick = {
+                            containerState.value = ""
+                            showInput = true
+                        },
+                    ) {
+                        Icon(Icons.Default.Clear, "清除托盘编码")
+                    }
                 }
             }
 
             // 显示批次数据
-            ContainerItemList(data = viewModel.containerItemsDetails.collectAsState().value)
+            ContainerItemList(data = containerDetails)
         }
     }
 }
 
-
+// 批次列表显示 - 优化版
 @Composable
 private fun ContainerItemList(data: List<ContainerItemDetail>) {
-    LazyColumn(modifier = Modifier.fillMaxSize()) {
-        itemsIndexed(data) { index, item ->
-            Card(
-                modifier = Modifier
-                    .fillMaxWidth()
-                    .padding(2.dp),
-                elevation = CardDefaults.cardElevation(2.dp)
+    if (data.isEmpty()) {
+        Box(
+            modifier = Modifier
+                .fillMaxSize()
+                .padding(16.dp),
+            contentAlignment = Alignment.Center
+        ) {
+            Text("没有找到批次数据", style = MaterialTheme.typography.titleMedium)
+        }
+    } else {
+        LazyColumn(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.spacedBy(12.dp)
+        ) {
+            itemsIndexed(data) { index, item ->
+                OutOperationItemCard(
+                    index = index,
+                    item = item
+                )
+            }
+        }
+    }
+}
+
+// 批次项卡片 - 提取为独立组件
+@Composable
+private fun OutOperationItemCard(
+    index: Int,
+    item: ContainerItemDetail
+) {
+    val remaining = item.goods_in_qty - item.goods_out_qty
+
+    Card(
+        modifier = Modifier.fillMaxWidth(),
+        elevation = CardDefaults.cardElevation(4.dp)
+    ) {
+        Column(
+            modifier = Modifier.padding(16.dp)
+        ) {
+            Row(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalArrangement = Arrangement.SpaceBetween
             ) {
-                Column(modifier = Modifier.padding(4.dp)) {
-                    Text(
-                        "id:${index + 1}",
-                        style = MaterialTheme.typography.bodySmall
-                    )
-                    Spacer(Modifier.height(1.dp))
-                    Divider(thickness = 0.8.dp)
-                    Text(
-                        "物料批次:${item.batch_number}",
-                        style = MaterialTheme.typography.titleMedium
-                    )
-                    Spacer(Modifier.height(1.dp))
-                    Divider(thickness = 1.2.dp)
-                    Row(
-                        horizontalArrangement = Arrangement.SpaceBetween,
-                        modifier = Modifier.fillMaxWidth()
-                    ) {
-                        Text(
-                            "物料编码:${item.goods_code}",
-                            style = MaterialTheme.typography.bodyMedium
-                        )
-                        Text(
-                            "物料名称:${item.goods_desc}",
-                            style = MaterialTheme.typography.bodyMedium
-                        )
-                    }
-                    Spacer(Modifier.height(1.dp))
-                    Divider(thickness = 1.2.dp)
-                    Row(
-                        horizontalArrangement = Arrangement.SpaceBetween,
-                        modifier = Modifier.fillMaxWidth()
-                    ) {
-                        Text(
-                            "组盘数量:${item.goods_in_qty}",
+                Text("#${index + 1}", style = MaterialTheme.typography.labelMedium)
+                Text("items_: ${item.id}", style = MaterialTheme.typography.labelMedium)
+            }
 
-                            )
-                        Text(
-                            "已出库数量:${item.goods_out_qty}",
+            Spacer(Modifier.height(6.dp))
+            Divider()
+            Spacer(Modifier.height(6.dp))
 
-                            )
-                    }
+            Text(
+                "批次: ${item.batch_number}",
+                style = MaterialTheme.typography.titleMedium
+            )
 
+            Spacer(Modifier.height(6.dp))
+
+            Row(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalArrangement = Arrangement.SpaceBetween
+            ) {
+                Column {
+                    Text("物料编码", style = MaterialTheme.typography.labelSmall)
+                    Text(item.goods_code, style = MaterialTheme.typography.bodyMedium)
+                }
+                Column {
+                    Text("物料名称", style = MaterialTheme.typography.labelSmall)
+                    Text(item.goods_desc, style = MaterialTheme.typography.bodyMedium)
                 }
             }
+
+            Spacer(Modifier.height(2.dp))
+            Divider(thickness = 1.5.dp)
+
+
+            // 数量信息网格
+            Row(
+                modifier = Modifier.fillMaxWidth(),
+                horizontalArrangement = Arrangement.SpaceAround
+            ) {
+                QuantityInfoCard(title = "组盘", value = item.goods_in_qty.toString(), backgroundColor = Color(0xFFE3F2FD))
+                QuantityInfoCard(title = "出库", value = item.goods_out_qty.toString(), backgroundColor = Color(0xFFFFF8E1))
+                QuantityInfoCard(title = "剩余", value = remaining.toString(), backgroundColor = Color(0xFFE8F5E9))
+            }
         }
     }
 }
 
+// 数量信息卡片组件
+@Composable
+private fun QuantityInfoCard(title: String, value: String, backgroundColor: Color) {
+    Card(
+        modifier = Modifier.size(60.dp),
+        colors = CardDefaults.cardColors(containerColor = backgroundColor)
+    ) {
+        Column(
+            modifier = Modifier.padding(8.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center
+        ) {
+            Text(title, style = MaterialTheme.typography.labelMedium)
+            Spacer(Modifier.height(2.dp))
+            Text(value, style = MaterialTheme.typography.titleMedium)
+        }
+    }
+}

+ 24 - 5
app/src/main/java/com/example/pda/ui/ContainerScreen.kt

@@ -10,8 +10,7 @@ import androidx.compose.foundation.layout.*
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.itemsIndexed
 import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBack
-import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.*
 import androidx.compose.material3.*
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.res.painterResource
@@ -20,6 +19,7 @@ import android.media.AudioManager
 import android.media.ToneGenerator
 import androidx.compose.material.icons.filled.List
 import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.filled.Refresh // 添加刷新图标
 import com.example.pda.R
 import com.example.pda.function.UBXScan
 import com.example.pda.ui.viewmodel.InventoryViewModel
@@ -131,15 +131,34 @@ fun ContainerScreen(
                         Text("信泰PDA—托盘扫描")
                     }
                 },
+                // 添加刷新按钮到动作区域
+                actions = {
+                    // 刷新按钮
+                    IconButton(
+                        onClick = {
+                            if (container.value.isNotBlank()) {
+                                viewModel.getData(container.value)
+                                coroutineScope.launch {
+                                    snackbarHostState.showSnackbar("刷新数据...")
+                                }
+                            } else {
+                                coroutineScope.launch {
+                                    snackbarHostState.showSnackbar("请先扫描托盘")
+                                }
+                            }
+                        }
+                    ) {
+                        Icon(Icons.Default.Refresh, "刷新数据")
+                    }
+                },
                 colors = TopAppBarDefaults.topAppBarColors(
                     containerColor = Color(0xFFBCD0C5), // 自定义颜色
                     titleContentColor = MaterialTheme.colorScheme.onPrimary,
                     actionIconContentColor = MaterialTheme.colorScheme.onPrimary
                 )
             )
-        },
-
-        ) { innerPadding ->
+        }
+    ) { innerPadding ->
         Column(
             modifier = Modifier
                 .padding(innerPadding)

+ 277 - 0
app/src/main/java/com/example/pda/ui/OutOperationScreen.kt

@@ -0,0 +1,277 @@
+package com.example.pda.ui
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import com.example.pda.R
+import com.example.pda.model.ContainerItemDetail
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.lifecycle.viewmodel.compose.viewModel
+import com.example.pda.ui.viewmodel.InventoryViewModel
+import kotlinx.coroutines.launch
+
+// 出库操作界面 - 修复版
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun OutOperationScreen(
+    containerCode: String,
+    onBack: () -> Unit,
+    onShelfScan:()-> Unit
+) {
+    val snackbarHostState = remember { SnackbarHostState() }
+    val coroutineScope = rememberCoroutineScope()
+    val viewModel: InventoryViewModel = viewModel()
+    // 使用 viewModel 收集状态
+    val items by viewModel.containerItemsDetails.collectAsState()
+    // 修复:正确处理 outItems 的初始化
+    val outItems = remember(items) {
+        viewModel.getContainerDetail(containerCode)
+        items.map { item ->
+            OutOperationItem(
+                detail = item,
+                selected = false,
+                outQty = item.goods_in_qty - item.goods_out_qty
+            )
+        }.toMutableStateList()
+    }
+
+    Scaffold(
+        snackbarHost = { SnackbarHost(snackbarHostState) },
+        topBar = {
+            TopAppBar(
+                navigationIcon = {
+                    IconButton(onClick = onBack) {
+                        Icon(Icons.Default.ArrowBack, "返回")
+                    }
+                },
+                title = {
+                    Row(verticalAlignment = Alignment.CenterVertically) {
+                        Icon(
+                            painter = painterResource(id = R.drawable.logo),
+                            contentDescription = "PDA Logo",
+                            modifier = Modifier.size(40.dp),
+                            tint = MaterialTheme.colorScheme.surfaceTint
+                        )
+                        Spacer(Modifier.width(8.dp))
+                        Text("托盘出库操作")
+                    }
+                },
+                actions = {
+                    IconButton(
+                        onClick = {
+                            val selectedItems = outItems.filter { it.selected }
+                            if (selectedItems.isEmpty()) {
+                                coroutineScope.launch {
+                                    snackbarHostState.showSnackbar("请至少选择一个批次")
+                                }
+                                return@IconButton
+                            }
+
+                            val outData = selectedItems.associate {
+                                it.detail.id to it.outQty
+                            }
+                            viewModel.submitOutOperation(containerCode,outData)
+                            onShelfScan()
+                        }
+                    ) {
+                        Icon(Icons.Default.Check, "提交出库", tint = Color.White)
+                    }
+                },
+                colors = TopAppBarDefaults.topAppBarColors(
+                    containerColor = Color(0xFFBCD0C5),
+                    titleContentColor = MaterialTheme.colorScheme.onPrimary,
+                    actionIconContentColor = MaterialTheme.colorScheme.onPrimary
+                )
+            )
+        }
+    ) { innerPadding ->
+        Column(
+            modifier = Modifier
+                .padding(innerPadding)
+                .fillMaxSize()
+                .padding(16.dp)
+        ) {
+            // 托盘编码显示
+            Text(
+                text = "托盘编码:$containerCode",
+                style = MaterialTheme.typography.titleMedium,
+                modifier = Modifier.padding(bottom = 16.dp)
+            )
+
+            // 显示批次数据
+            OutOperationList(
+                items = outItems,
+                onItemSelected = { index, selected ->
+                    outItems[index] = outItems[index].copy(selected = selected)
+                },
+                onOutQtyChanged = { index, newQty ->
+                    val maxQty = outItems[index].detail.goods_in_qty - outItems[index].detail.goods_out_qty
+                    val clampedQty = newQty.coerceIn(0, maxQty)
+                    outItems[index] = outItems[index].copy(outQty = clampedQty)
+                }
+            )
+        }
+    }
+}
+
+// 出库操作项数据类
+data class OutOperationItem(
+    val detail: ContainerItemDetail,
+    val selected: Boolean,
+    val outQty: Int
+)
+
+// 出库操作列表 - 优化布局
+@Composable
+private fun OutOperationList(
+    items: List<OutOperationItem>,
+    onItemSelected: (Int, Boolean) -> Unit,
+    onOutQtyChanged: (Int, Int) -> Unit
+) {
+    if (items.isEmpty()) {
+        Box(
+            modifier = Modifier.fillMaxSize(),
+            contentAlignment = Alignment.Center
+        ) {
+            Text("没有可出库的批次", style = MaterialTheme.typography.bodyLarge)
+        }
+    } else {
+        LazyColumn(
+            modifier = Modifier.fillMaxSize(),
+            verticalArrangement = Arrangement.spacedBy(12.dp)
+        ) {
+            itemsIndexed(items) { index, item ->
+                val detail = item.detail
+                val remaining = detail.goods_in_qty - detail.goods_out_qty
+
+                Card(
+                    modifier = Modifier.fillMaxWidth(),
+                    elevation = CardDefaults.cardElevation(4.dp)
+                ) {
+                    Column(
+                        modifier = Modifier
+                            .padding(16.dp)
+                            .fillMaxWidth()
+                    ) {
+                        Row(
+                            verticalAlignment = Alignment.CenterVertically,
+                            modifier = Modifier.fillMaxWidth()
+                        ) {
+                            // 多选框
+                            Column ()
+                            {
+                                Text(
+                                    text = "${index+1}",
+                                    style = MaterialTheme.typography.bodySmall,
+                                )
+                                Checkbox(
+                                    checked = item.selected,
+                                    onCheckedChange = { selected ->
+                                        onItemSelected(index, selected)
+                                    }
+                                )
+                            }
+
+
+                            Spacer(Modifier.width(16.dp))
+
+                            // 批次信息
+                            Column (modifier = Modifier.weight(1f)) {
+
+                                Text(
+                                    text = "批次号: ${detail.batch_number}",
+                                    style = MaterialTheme.typography.titleMedium
+                                )
+
+                                Spacer(Modifier.height(2.dp))
+
+                                Text(
+                                    text = "物料: ${detail.goods_code} - ${detail.goods_desc}",
+                                    style = MaterialTheme.typography.bodyMedium
+                                )
+
+                                Spacer(Modifier.height(2.dp))
+
+                                Text(
+                                    text = "剩余: $remaining",
+                                    style = MaterialTheme.typography.bodyMedium.copy(
+                                        color = if (remaining > 0) Color.Green else Color.Red
+                                    )
+                                )
+                                // 出库数量输入
+                                OutQtyInput(
+                                    value = item.outQty,
+                                    maxValue = remaining,
+                                    onValueChange = { newValue ->
+                                        onOutQtyChanged(index, newValue)
+                                    }
+                                )
+                            }
+
+
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+// 出库数量输入组件 - 优化样式
+@Composable
+private fun OutQtyInput(
+    value: Int,
+    maxValue: Int,
+    onValueChange: (Int) -> Unit
+) {
+    Column(horizontalAlignment = Alignment.CenterHorizontally) {
+        Row(
+            verticalAlignment = Alignment.CenterVertically
+        ) {
+            // 减少按钮
+            OutlinedIconButton(
+                onClick = { onValueChange((value - 1).coerceAtLeast(0)) },
+                enabled = value > 0,
+                modifier = Modifier.size(40.dp)
+            ) {
+                Text("-", style = MaterialTheme.typography.titleLarge)
+            }
+
+            Spacer(Modifier.width(40.dp))
+
+            // 显示当前值
+            Column(horizontalAlignment = Alignment.CenterHorizontally) {
+                Text(
+                    text = value.toString(),
+                    style = MaterialTheme.typography.titleLarge,
+                    color = MaterialTheme.colorScheme.primary
+                )
+                Text(
+                    text = "出库",
+                    style = MaterialTheme.typography.labelSmall
+                )
+            }
+
+            Spacer(Modifier.width(40.dp))
+
+            // 增加按钮
+            OutlinedIconButton(
+                onClick = { onValueChange((value + 1).coerceAtMost(maxValue)) },
+                enabled = value < maxValue,
+                modifier = Modifier.size(40.dp)
+            ) {
+                Text("+", style = MaterialTheme.typography.titleLarge)
+            }
+        }
+    }
+}

+ 158 - 17
app/src/main/java/com/example/pda/ui/OutTaskScreen.kt

@@ -1,5 +1,6 @@
 package com.example.pda.ui
 
+import android.R.attr.text
 import android.content.Context
 import androidx.compose.foundation.clickable
 import androidx.compose.runtime.*
@@ -18,6 +19,8 @@ import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.unit.dp
 import android.media.AudioManager
 import android.media.ToneGenerator
+import androidx.compose.material.icons.filled.Close
+import androidx.compose.material.icons.filled.Done
 import androidx.compose.material.icons.filled.List
 import androidx.compose.material.icons.filled.Search
 import com.example.pda.R
@@ -34,6 +37,7 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Alignment
+import androidx.compose.ui.unit.sp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.example.pda.model.BatchTotal
 import com.example.pda.model.ContainerItemDetail
@@ -251,14 +255,16 @@ fun OutTaskScreen(
             when (selectedTab) {
                 0 -> BatchTotalList(
 
-                    data = OutItemsToBatchTotal(viewModel.containerItemsDetails.collectAsState().value)
+                    data = OutItemsToBatchTotal(viewModel.containerItemsDetails.collectAsState().value),
+                    viewModel = viewModel,
+                    container = container.value
 
-                    )
+                )
 
                 1 -> MaterialResultList(
                     data = OutItemsToMaterialResult(viewModel.containerItemsDetails.collectAsState().value)
 
-                    )
+                )
             }
         }
     }
@@ -286,7 +292,7 @@ private fun OutItemsToBatchTotal(input: List<ContainerItemDetail>): List<BatchTo
 
     input.forEach { item ->
         // 计算当前物料的数量(入库-出库)
-        val currentQty =item.goods_out_qty
+        val currentQty = item.goods_out_qty
 
         // 累加到批次总数
         batchMap[item.batch_number] = batchMap.getOrDefault(item.batch_number, 0) + currentQty
@@ -302,7 +308,6 @@ private fun OutItemsToBatchTotal(input: List<ContainerItemDetail>): List<BatchTo
 }
 
 
-
 private fun OutItemsToMaterialResult(input: List<ContainerItemDetail>): List<MaterialResult> {
     // 1. 按(批次号, 出库数量)分组并统计每个分组的记录数
     val materialGroupMap = mutableMapOf<Pair<String, Int>, Int>()
@@ -336,31 +341,167 @@ private fun OutItemsToMaterialResult(input: List<ContainerItemDetail>): List<Mat
 
 
 @Composable
-private fun BatchTotalList(data: List<BatchTotal>) {
-    LazyColumn(modifier = Modifier.fillMaxSize()) {
-        itemsIndexed(data) { index, item ->
-            Card(
+private fun BatchTotalList(
+    data: List<BatchTotal>,
+    viewModel: InventoryViewModel,
+    container: String
+) {
+    // 状态管理
+    var showConfirmDialog by remember { mutableStateOf(false) }
+
+    var actionType by remember { mutableStateOf("") } // 记录当前操作类型
+
+    // 显示按钮的条件:批次列表不为空
+    val showButtons = data.isNotEmpty()
+
+    // 确认弹窗
+    if (showConfirmDialog) {
+        ConfirmationDialog(
+            title = "确认操作",
+            message = "确定要${actionType}吗?",
+            onConfirm = {
+                showConfirmDialog = false
+                when (actionType) {
+                    "出库确认" -> {
+                        viewModel.confirmOutItems(container);
+                    }
+
+                    "取消确认" -> {
+                        viewModel.cancelOutItems(container);
+                    }
+                }
+            },
+            onDismiss = { showConfirmDialog = false }
+        )
+    }
+
+    Column {
+        // 按钮行(仅在批次列表不为空时显示)
+        if (showButtons) {
+            Row(
                 modifier = Modifier
                     .fillMaxWidth()
-                    .padding(8.dp),
-                elevation = CardDefaults.cardElevation(4.dp)
+                    .padding(vertical = 8.dp),
+                horizontalArrangement = Arrangement.SpaceEvenly
             ) {
-                Column(modifier = Modifier.padding(16.dp)) {
+                // 出库确认按钮
+                Button(
+                    onClick = {
+                        actionType = "出库确认"
+                        showConfirmDialog = true
+                    },
+                    colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4CAF50))
+                ) {
+                    Icon(
+                        imageVector = Icons.Default.Done,
+                        contentDescription = "确定"
+                    )
+                    Spacer(Modifier.width(8.dp))
                     Text(
-                        "批次号:${item.bound_number}",
-                        style = MaterialTheme.typography.titleMedium
+                        text = "出库确认",
+                        fontSize = 18.sp,
+                        color = Color.White
+                    )
+                }
+
+                // 取消确认按钮
+                Button(
+                    onClick = {
+                        actionType = "取消确认"
+                        showConfirmDialog = true
+                    },
+                    colors = ButtonDefaults.buttonColors(containerColor = Color(0xFFF44336))
+                ) {
+                    Icon(
+                        imageVector = Icons.Default.Close,
+                        contentDescription = "取消"
                     )
-                    Spacer(Modifier.height(8.dp))
+                    Spacer(Modifier.width(8.dp))
                     Text(
-                        "总数量:${item.total_batch_qty}",
-                        style = MaterialTheme.typography.bodyLarge
+                        text = "取消确认",
+                        fontSize = 18.sp,
+                        color = Color.White
                     )
                 }
             }
+
+            Divider(thickness = 1.5.dp, modifier = Modifier.padding(vertical = 8.dp))
+        }
+
+        // 批次列表
+        if (data.isEmpty()) {
+            // 空列表提示
+            Box(
+                modifier = Modifier
+                    .fillMaxSize()
+                    .padding(16.dp),
+                contentAlignment = Alignment.Center
+            ) {
+                Text(
+                    text = "没有批次数据",
+                    style = MaterialTheme.typography.titleMedium,
+                    color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
+                )
+            }
+        } else {
+            LazyColumn(modifier = Modifier.fillMaxSize()) {
+                itemsIndexed(data) { index, item ->
+                    Card(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .padding(8.dp),
+                        elevation = CardDefaults.cardElevation(4.dp)
+                    ) {
+                        Column(modifier = Modifier.padding(16.dp)) {
+                            Text(
+                                "批次号:${item.bound_number}",
+                                style = MaterialTheme.typography.titleMedium
+                            )
+                            Spacer(Modifier.height(8.dp))
+                            Text(
+                                "总数量:${item.total_batch_qty}",
+                                style = MaterialTheme.typography.bodyLarge
+                            )
+                        }
+                    }
+                }
+            }
         }
     }
 }
 
+// 确认弹窗组件
+@Composable
+private fun ConfirmationDialog(
+    title: String,
+    message: String,
+    onConfirm: () -> Unit,
+    onDismiss: () -> Unit
+) {
+    AlertDialog(
+        onDismissRequest = onDismiss,
+        title = { Text(text = title, style = MaterialTheme.typography.titleLarge) },
+        text = { Text(text = message, style = MaterialTheme.typography.bodyMedium) },
+        confirmButton = {
+            Button(
+                onClick = onConfirm,
+                colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4CAF50))
+            ) {
+                Text("确认")
+            }
+        },
+        dismissButton = {
+            Button(
+                onClick = onDismiss,
+                colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF9E9E9E))
+            ) {
+                Text("取消")
+            }
+        }
+    )
+}
+
+
 @Composable
 private fun MaterialResultList(data: List<MaterialResult>) {
     LazyColumn(modifier = Modifier.fillMaxSize()) {

+ 63 - 8
app/src/main/java/com/example/pda/ui/viewmodel/InventoryViewModel.kt

@@ -12,12 +12,15 @@ import kotlinx.coroutines.launch
 import com.example.pda.model.BatchTotal
 import com.example.pda.model.ContainerItemDetail
 import com.example.pda.model.MaterialResult
+
 // ViewModel 类用于管理库存相关的UI状态和数据操作
 class InventoryViewModel : ViewModel() {
     // 私有 MutableStateFlow 用于存储UI状态,初始状态为 Idle
     private val _uiState = MutableStateFlow<UiState>(UiState.Idle)
+
     // 公开的 StateFlow 提供给UI层观察当前UI状态
     val uiState: StateFlow<UiState> = _uiState
+
     // 批次统计数据
     private val _batchTotals = MutableStateFlow<List<BatchTotal>>(emptyList())
     val batchTotals: StateFlow<List<BatchTotal>> = _batchTotals
@@ -35,10 +38,13 @@ class InventoryViewModel : ViewModel() {
     sealed class UiState {
         // 空闲状态
         object Idle : UiState()
+
         // 加载状态
         object Loading : UiState()
+
         // 成功状态,包含成功消息
         data class Success(val message: String) : UiState()
+
         // 错误状态,包含错误消息
         data class Error(val message: String) : UiState()
     }
@@ -69,7 +75,6 @@ class InventoryViewModel : ViewModel() {
     }
 
 
-
     fun getData(container: String) {
         viewModelScope.launch {
             _uiState.value = UiState.Loading
@@ -113,7 +118,7 @@ class InventoryViewModel : ViewModel() {
             _uiState.value = UiState.Loading
             _containerItemsDetails.value = emptyList()
             NetworkHelper.getItemsDetail(
-                container_code =  container,
+                container_code = container,
                 onSuccess = { response ->
                     _containerItemsDetails.value = response
                     _uiState.value = UiState.Success("数据加载成功")
@@ -129,8 +134,8 @@ class InventoryViewModel : ViewModel() {
         viewModelScope.launch {
             _uiState.value = UiState.Loading
             _containerItemsDetails.value = emptyList()
-            NetworkHelper.getOutItemsDetail (
-                container_code =  container,
+            NetworkHelper.getOutItemsDetail(
+                container_code = container,
                 onSuccess = { response ->
                     _containerItemsDetails.value = response
                     _uiState.value = UiState.Success("数据加载成功")
@@ -141,10 +146,60 @@ class InventoryViewModel : ViewModel() {
             )
         }
     }
-   
-   fun resetState() {
+
+    fun confirmOutItems(container: String) {
+        viewModelScope.launch {
+            _uiState.value = UiState.Loading
+            NetworkHelper.confirmOutItems(
+                container_code = container,
+                onSuccess = {
+                    _uiState.value = UiState.Success("确认出库成功")
+                },
+                onError = { response ->
+                    _uiState.value = UiState.Success(response)
+                }
+            )
+
+        }
+    }
+
+    fun cancelOutItems(container: String) {
+        viewModelScope.launch {
+            _uiState.value = UiState.Loading
+            NetworkHelper.cancelOutItems(
+                container_code = container,
+                onSuccess = {
+                    _uiState.value = UiState.Success("取消出库成功,请手动拆盘出库")
+                },
+                onError = { response ->
+                    _uiState.value = UiState.Success(response)
+                }
+            )
+
+        }
+
+    }
+
+    // 提交出库操作
+    fun submitOutOperation(containerCode: String, outData: Map<Int, Int>) {
+        viewModelScope.launch {
+            _uiState.value = UiState.Loading
+            NetworkHelper.changeContainerOutQty(
+                container_code = containerCode,
+                onSuccess = {response ->
+                    _uiState.value = UiState.Success(response)
+                },
+                onError = { response ->
+                    _uiState.value = UiState.Success(response)
+                },
+                items = outData
+            )
+        }
+    }
+
+    fun resetState() {
         // 重置 UI 状态
         _uiState.value = UiState.Idle
-   }
-    
+    }
+
 }

+ 5 - 0
app/src/main/res/drawable/ic_out.xml

@@ -0,0 +1,5 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
+      
+    <path android:fillColor="@android:color/white" android:pathData="M22,21V7L12,3L2,7v14h5v-9h10v9H22zM11,19H9v2h2V19zM13,16h-2v2h2V16zM15,19h-2v2h2V19z"/>
+    
+</vector>