flower_mr 2 ماه پیش
والد
کامیت
badc43c9f0

+ 11 - 0
.idea/inspectionProfiles/Project_Default.xml

@@ -3,15 +3,19 @@
     <option name="myName" value="Project Default" />
     <inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
       <option name="composableFile" value="true" />
@@ -27,24 +31,31 @@
     </inspection_tool>
     <inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
     <inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
       <option name="composableFile" value="true" />
+      <option name="previewFile" value="true" />
     </inspection_tool>
   </profile>
 </component>

+ 3 - 1
app/src/main/AndroidManifest.xml

@@ -12,8 +12,10 @@
         android:roundIcon="@drawable/logo"
         android:supportsRtl="true"
         android:theme="@style/Theme.PDA"
-        tools:targetApi="31">
+        tools:targetApi="31"
+        android:name=".InitPDA">
         <activity
+
             android:name=".MainActivity"
             android:exported="true"
             android:label="@string/app_name"

+ 11 - 0
app/src/main/java/com/example/pda/InitPDA.kt

@@ -0,0 +1,11 @@
+package com.example.pda
+
+import android.app.Application
+import com.example.pda.model.AppPrefs
+
+class InitPDA : Application() {
+    override fun onCreate() {
+        super.onCreate()
+        AppPrefs.init(this)
+    }
+}

+ 2 - 0
app/src/main/java/com/example/pda/MainActivity.kt

@@ -7,12 +7,14 @@ import androidx.navigation.compose.rememberNavController
 import androidx.navigation.compose.NavHost
 import com.example.pda.navigation.appNavGraph
 import com.example.pda.ui.theme.PDATheme
+import com.example.pda.model.AppPrefs
 
 // 主活动类,继承自 ComponentActivity,作为应用的入口点
 class MainActivity : ComponentActivity() {
     // 重写 onCreate 方法,用于设置应用的内容视图
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+
         setContent {
             // 应用的主题设置
             PDATheme {

+ 65 - 0
app/src/main/java/com/example/pda/model/AppPrefs.kt

@@ -0,0 +1,65 @@
+package com.example.pda.model
+
+import android.content.Context
+import android.content.SharedPreferences
+import kotlin.reflect.KProperty
+
+
+object AppPrefs {
+    private lateinit var prefs: SharedPreferences
+
+    fun init(context: Context) {
+        prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
+    }
+
+    // 使用 Lambda 延迟访问 prefs
+    var username by StringPreference({ prefs }, "username", "")
+
+    var token by StringPreference({ prefs }, "token", "")
+    var ip_server by StringPreference({ prefs }, "ip_server", "")
+    var base_url by StringPreference({ prefs }, "base_url", "")
+    var rememberMe by BooleanPreference({ prefs }, "remember_me", false)
+    var isDarkMode by BooleanPreference({ prefs }, "dark_mode", false)
+    var islogin by BooleanPreference({ prefs }, "islogin", false)
+
+    // 修改委托类,接受 Lambda 提供 prefs
+    private class StringPreference(
+        private val prefsProvider: () -> SharedPreferences, // 通过 Lambda 获取 prefs
+        private val key: String,
+        private val defaultValue: String
+    ) {
+        operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
+            return prefsProvider().getString(key, defaultValue) ?: defaultValue
+        }
+
+        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
+            prefsProvider().edit { putString(key, value) }
+        }
+    }
+
+    private class BooleanPreference(
+        private val prefsProvider: () -> SharedPreferences, // 通过 Lambda 获取 prefs
+        private val key: String,
+        private val defaultValue: Boolean
+    ) {
+        operator fun getValue(thisRef: Any?, property: KProperty<*>): Boolean {
+            return prefsProvider().getBoolean(key, defaultValue)
+        }
+
+        operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Boolean) {
+            prefsProvider().edit { putBoolean(key, value) }
+        }
+    }
+
+    // 清空数据(退出登录时调用)
+    fun clearAll() {
+        prefs.edit { clear() }
+    }
+
+    // 扩展函数:简化 SharedPreferences.Editor 操作
+    private inline fun SharedPreferences.edit(action: SharedPreferences.Editor.() -> Unit) {
+        val editor = edit()
+        action(editor)
+        editor.apply()
+    }
+}

app/src/main/java/com/example/pda/model/BaseResponse3.kt → app/src/main/java/com/example/pda/model/BaseResponse.kt


+ 56 - 0
app/src/main/java/com/example/pda/model/loginItem.kt

@@ -0,0 +1,56 @@
+package com.example.pda.model
+
+import com.google.gson.annotations.SerializedName
+
+data class loginItem(
+    @SerializedName("name")
+    var username: String, // 明确表示用户名
+
+    @SerializedName("password")
+    var password: String // 明确表示密码
+)
+data class successItem(
+    @SerializedName("name")
+    val message: String ,// 状态信息
+
+    @SerializedName("openid")
+    val openid: String ,// 用户唯一标识
+
+    @SerializedName("user_id")
+    val user_id: String ,// 用户id
+
+    @SerializedName("appid")
+    val appid: String ,// 应用id
+
+    @SerializedName("staff_type")
+    val staff_type: String // 员工类型
+
+
+)
+data class loginfailResult(
+    @SerializedName("code")
+    val code: Int, // 状态码
+
+    @SerializedName("msg")
+    val message: String ,// 状态信息
+
+    @SerializedName("data")
+    val data: loginItem ,// 返回数据
+
+    @SerializedName("ip")
+    val ip: String ,// 登录ip地址
+)   
+
+class loginSuccessResult(
+    @SerializedName("code")
+    val code: Int, // 状态码
+
+    @SerializedName("msg")
+    val message: String ,// 状态信息
+
+    @SerializedName("data")
+    val data: successItem ,// 返回数据
+
+    @SerializedName("ip")
+    val ip: String // 登录ip地址        
+)

+ 37 - 11
app/src/main/java/com/example/pda/navigation/NavGraph.kt

@@ -10,45 +10,71 @@ import com.example.pda.ui.DetailsScreen
 import com.example.pda.ui.MainScreen
 import com.example.pda.ui.ScanScreen
 import com.example.pda.ui.PingScreen
+import com.example.pda.ui.ContainerScreen
+import com.example.pda.ui.LoginScreen
+import com.example.pda.ui.SettingScreen
 
 // 定义应用程序的导航图
 fun NavGraphBuilder.appNavGraph(navController: NavController) {
     // 创建一个导航图,起始目标为 "main",路由为 "root"
-    navigation(startDestination = "main", route = "root") {
+    navigation(startDestination = "login", route = "root") {
+        composable("login"){
+            LoginScreen (
+                onSetScreen = { navController.navigate("set")},
+                onMainScreen = {navController.navigate("main")}
+            )
+        }
+        composable ("set"){
+            SettingScreen (
+                onPingScreen = { navController.navigate("ping") },
+
+            )
+
+
+        }
         // 定义 "main" 路由,显示主屏幕
         composable("main") {
             MainScreen(
                 onInventoryScan = { navController.navigate("scan") }, // 点击扫描按钮时导航到 "scan" 路由
-                onShelfScan = { navController.navigate("scan") }, // 点击扫描按钮时导航到 "scan" 路由
-                onPingScreen = { navController.navigate("ping") }, // 点击扫描按钮时导航到 "scan" 路由
-
+                onShelfScan = { navController.navigate("container") }, // 点击扫描按钮时导航到 "scan" 路由
+                onSetScreen = { navController.navigate("set") }, //
             )
         }
         composable("ping") {
             PingScreen(
-                onMainScreen = {navController.navigate("main")}, // 点击返回按钮时导航到 "main" 路由
+                onMainScreen =  { navController.popBackStack() } // 点击返回按钮时弹出当前栈顶
             )
         }
         // 定义 "scan" 路由,显示扫描屏幕
         composable("scan") {
             ScanScreen(
                 onBack = { navController.popBackStack() }, // 点击返回按钮时弹出当前栈顶
-                onResult = { result -> 
-                    navController.navigate("details/$result") // 导航到 "details" 路由,传递结果参数
+                onResult = { container, result ->
+                    navController.navigate("details/$container/$result") // 导航到 "details" 路由,传递结果参数
                 }
             )
         }
+        composable("container") {
+            ContainerScreen (
+                onBack = { navController.popBackStack() } // 点击返回按钮时弹出当前栈顶
+            )
+
+        }
         // 定义 "details" 路由,显示详情屏幕,需要传递结果参数
         composable(
-            route = "details/{result}",
-            arguments = listOf(navArgument("result") { type = NavType.StringType }) // 定义参数类型为字符串
+            route = "details/{container}/{result}",
+            arguments = listOf(
+                navArgument("container") { type = NavType.StringType }, // 定义参数类型为字符串
+                navArgument("result") { type = NavType.StringType } // 定义参数类型为字符串
+            )
         ) { backStackEntry ->
             DetailsScreen(
+                container = backStackEntry.arguments?.getString("container") ?: "",
                 result = backStackEntry.arguments?.getString("result") ?: "", // 获取并传递结果参数
-                onMainScreen = {navController.navigate("main")}, // 点击返回按钮时导航到 "main" 路由
+                onMainScreen = { navController.navigate("main") }, // 点击返回按钮时导航到 "main" 路由
                 onShelfScan = { navController.navigate("scan") }, // 点击上架按钮时导航到 "scan" 路由
                 onBack = { navController.popBackStack() } // 点击返回按钮时弹出当前栈顶
             )
         }
     }
-}
+}

+ 12 - 2
app/src/main/java/com/example/pda/network/ApiService.kt

@@ -2,6 +2,8 @@ package com.example.pda.network
 
 import com.example.pda.model.BaseResponse
 import com.example.pda.model.InventoryItem
+import com.example.pda.model.loginItem
+import com.example.pda.model.loginSuccessResult
 import retrofit2.Response
 import retrofit2.http.Body
 import retrofit2.http.GET
@@ -10,15 +12,23 @@ import retrofit2.http.QueryMap
 
 // 定义了一个接口 ApiService,用于与服务器进行库存相关的网络请求
 interface ApiService {
+
+    @POST("login/")
+    suspend fun login(
+        @Body data: loginItem
+    ): Response<loginSuccessResult>
+    
     // 提交库存数据到服务器的接口
-    @POST("asn/list")
+    @POST("asn/list/")
     suspend fun submitInventory(
         @Body data: List<InventoryItem>
     ): Response<BaseResponse<Unit>>
 
     // 从服务器获取库存列表的接口
-    @GET("asn/list")
+    @GET("asn/list/")
     suspend fun getInventoryList(
         @QueryMap params: Map<String, String>
     ): Response<BaseResponse<List<InventoryItem>>>
+
+
 }

+ 20 - 7
app/src/main/java/com/example/pda/network/HttpClient.kt

@@ -1,20 +1,33 @@
 package com.example.pda.network
 
+import okhttp3.Interceptor
 import okhttp3.OkHttpClient
 import okhttp3.logging.HttpLoggingInterceptor
 import java.util.concurrent.TimeUnit
 
-// 提供OkHttpClient实例的单例对象
 object HttpClient {
-    // 懒加载方式初始化OkHttpClient实例
+    // 动态 Token 存储(volatile 保证多线程可见性)
+    @Volatile
+    var token: String? = null
+
+    // 认证拦截器(私有保证内部管理)
+    private val authInterceptor = Interceptor { chain ->
+        chain.request().newBuilder().apply {
+            // 仅在 token 存在时添加 Header
+            token?.let { addHeader("token", it) }
+        }.build().let(chain::proceed)
+    }
+
+    // 带动态 Header 的 OkHttpClient
     val instance: OkHttpClient by lazy {
         OkHttpClient.Builder()
-            .connectTimeout(30, TimeUnit.SECONDS) // 设置连接超时时间为30秒
-            .readTimeout(30, TimeUnit.SECONDS) // 设置读取超时时间为30秒
-            .writeTimeout(30, TimeUnit.SECONDS) // 设置写入超时时间为30秒
+            .connectTimeout(1, TimeUnit.SECONDS)
+            .readTimeout(1, TimeUnit.SECONDS)
+            .writeTimeout(1, TimeUnit.SECONDS)
+            .addInterceptor(authInterceptor) // 先添加认证拦截器
             .addInterceptor(HttpLoggingInterceptor().apply {
-                level = HttpLoggingInterceptor.Level.BODY // 设置日志级别为BODY,记录请求和响应的完整内容
+                level = HttpLoggingInterceptor.Level.BODY // 后添加日志拦截器
             })
-            .build() // 构建OkHttpClient实例
+            .build()
     }
 }

+ 68 - 1
app/src/main/java/com/example/pda/network/NetworkHelper.kt

@@ -2,9 +2,12 @@ package com.example.pda.network
 
 import android.util.Log
 import com.example.pda.model.InventoryItem
+import com.example.pda.model.loginItem
+import com.example.pda.model.successItem
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import kotlin.collections.orEmpty
 
 // 网络请求助手对象,用于处理与服务器的交互
 object NetworkHelper {
@@ -43,4 +46,68 @@ object NetworkHelper {
             }
         }
     }
-}
+
+    // 获取库存列表的方法
+    fun getIdList(
+        container_code: String,
+
+        onSuccess: (List<InventoryItem>) -> Unit, // 成功后的回调函数,接受一个InventoryItem列表作为参数
+        onError: (String) -> Unit // 失败后的回调函数
+    ) {
+        val params = mapOf("code" to container_code)
+        // 在IO线程中启动协程以执行网络请求
+        CoroutineScope(Dispatchers.IO).launch {
+            try {
+                val response = RetrofitClient.instance.getInventoryList(params)
+                // 检查HTTP响应是否成功
+                if (response.isSuccessful) {
+                    // 如果响应体存在且code为200,则调用成功回调,并将数据传递给回调函数
+
+                    response.body()?.let {
+                        if (it.code == 200) {
+                            onSuccess(it.data.orEmpty())
+                        } else {
+                            // 否则调用错误回调并传递错误信息
+                            onError(it.message ?: "Unknown error")
+                        }
+                    }
+                } else {
+                    // 如果HTTP响应不成功,则调用错误回调并传递HTTP错误代码
+                    onError("HTTP Error: ${response.code()}")
+                }
+            } catch (e: Exception) {
+                // 捕获异常并记录日志,之后调用错误回调并传递异常本地化信息
+                Log.e(TAG, "Get failed", e)
+                onError("Network error: ${e.localizedMessage}")
+            }
+        }
+    }
+
+    fun userlogin(
+        data : loginItem,
+        onSuccess: (successItem) -> Unit,
+        onError: (String) -> Unit
+    )
+    {
+        CoroutineScope(Dispatchers.IO).launch {
+            try {
+                val response = RetrofitClient.instance.login(data)
+                if (response.isSuccessful) {
+                    response.body()?.let {
+                        if (it.code == 200) {
+                            onSuccess(it.data)
+                        } else {
+                            onError(it.message ?: "Unknown error")
+                        }
+                    }
+                } else {
+                    onError("HTTP Error: ${response.code()} ${response.message()}")
+                }
+            } catch (e: Exception) {
+                Log.e(TAG, "Login failed", e)
+                onError("Network error: ${e.localizedMessage}")
+            }
+            }
+
+    }
+}

+ 5 - 3
app/src/main/java/com/example/pda/network/RetrofitClient.kt

@@ -2,11 +2,12 @@ package com.example.pda.network
 
 import retrofit2.Retrofit
 import retrofit2.converter.gson.GsonConverterFactory
-
+import com.example.pda.model.AppPrefs
 // RetrofitClient 对象用于创建和管理 Retrofit 实例
 object RetrofitClient {
     // 私有常量 BASE_URL 定义了 API 的基础 URL
-    private const val BASE_URL = "http://192.168.196.51:8008/"
+    private const val BASE_URL = "http://192.168.196.182:8008/"
+
 
     // val instance 通过 lazy 委托实现懒加载,创建 ApiService 的单例实例
     val instance: ApiService by lazy {
@@ -17,4 +18,5 @@ object RetrofitClient {
             .build()
             .create(ApiService::class.java)
     }
-}
+}
+

+ 251 - 0
app/src/main/java/com/example/pda/ui/ContainerScreen.kt

@@ -0,0 +1,251 @@
+package com.example.pda.ui
+
+import android.content.Context
+import androidx.compose.foundation.clickable
+import androidx.compose.runtime.*
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.Modifier
+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.material3.*
+import androidx.compose.ui.graphics.Color
+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.Search
+import com.example.pda.R
+import com.example.pda.function.UBXScan
+import com.example.pda.ui.viewmodel.InventoryViewModel
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.lifecycle.viewmodel.compose.viewModel
+import kotlinx.coroutines.launch
+
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ContainerScreen(
+    onBack: () -> Unit,
+) {
+    val viewModel: InventoryViewModel = viewModel()
+    val uiState = viewModel.uiState.collectAsState()
+    val snackbarHostState = remember { SnackbarHostState() }
+    val coroutineScope = rememberCoroutineScope()
+    // 新增输入状态
+    val inputIp = remember { mutableStateOf("12345678") }
+
+    val context = LocalContext.current
+    val ubxScan = remember { UBXScan() }
+    var isScanning by remember { mutableStateOf(true) }
+    val scannedItems = remember { mutableStateListOf<String>() }
+    val container = remember { mutableStateOf("") }
+    var showinput by remember { mutableStateOf(false) }
+    var showContainer by remember { mutableStateOf(true) }
+    val configuration = LocalConfiguration.current
+    val screenHeight = configuration.screenHeightDp.dp
+    val boxHeight = screenHeight * 0.15f
+
+    // 创建 ToneGenerator
+    val toneGenerator = remember { ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100) }
+
+    LaunchedEffect(uiState.value) {
+        when (val currentState = uiState.value) {
+            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 -> {}
+        }
+    }
+
+    DisposableEffect(Unit) {
+        startScanning(context, ubxScan) { result ->
+            if (container.value.isEmpty()) {
+                toneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP)
+                container.value = result
+                showContainer = false
+            }
+        }
+
+        onDispose {
+            stopScanning(ubxScan)
+            isScanning = false
+            toneGenerator.release()
+        }
+    }
+    
+    Scaffold(
+        snackbarHost = { SnackbarHost(snackbarHostState) },
+        topBar = {
+            TopAppBar(
+                navigationIcon = {
+                    IconButton(onClick = onBack) {
+                        Icon(
+                            imageVector = Icons.Default.ArrowBack,
+                            contentDescription = "返回"
+                        )
+                    }
+                },
+                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("信泰PDA—托盘扫描")
+                    }
+                },
+                colors = TopAppBarDefaults.topAppBarColors(
+                    containerColor = Color(0xFFBCD0C5), // 自定义颜色
+                    titleContentColor = MaterialTheme.colorScheme.onPrimary,
+                    actionIconContentColor = MaterialTheme.colorScheme.onPrimary
+                )
+            )
+        },
+
+    ) { innerPadding ->
+        Column(
+            modifier = Modifier
+                .padding(innerPadding)
+                .fillMaxSize()
+                .padding(24.dp)
+        ) {
+            // 扫描提示区域
+            Box(
+                modifier = Modifier
+                    .height(boxHeight)
+                    .fillMaxWidth()
+                    .clickable(enabled = isScanning) {
+                        container.value = ""
+                        showinput = true
+                        showContainer = false
+                    },
+                contentAlignment = Alignment.Center
+            ) {
+                if (isScanning && showContainer) {
+                    Text(text = "托盘条码扫描")
+                } else if (isScanning && showinput) {
+                    Text(text = "输入条形码")
+                }
+                else{
+                    Text(text = "物料信息")
+                }
+            }
+            if (showinput) {
+                Row (
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(8.dp),
+                    verticalAlignment = Alignment.CenterVertically
+
+                ){
+                    // 新增输入框
+                    TextField(
+                        value = inputIp.value,
+                        onValueChange = { inputIp.value = it },
+                        label = { Text("请输入托盘编码") },
+                    )
+
+                    IconButton(
+                        onClick = {
+                            container.value = inputIp.value;
+                            viewModel.getData(container.value)
+                                  },
+                    ) {
+                        Icon(
+                            imageVector = Icons.Default.Search,
+                            contentDescription = "确定"
+                        )
+                    }
+
+                }
+
+            }
+
+            // 添加托盘扫描
+            Row(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(8.dp),
+                verticalAlignment = Alignment.CenterVertically
+            ) {
+                Text(
+                    text = "托盘编码:${container.value}",
+                    modifier = Modifier.weight(1f)
+                )
+
+                IconButton(
+                    onClick = { container.value = ""; showContainer = true;showinput=false },
+                ) {
+                    Icon(
+                        imageVector = Icons.Default.Delete,
+                        contentDescription = "删除"
+                    )
+                }
+            }
+
+            // 已扫描列表
+            LazyColumn(
+                modifier = Modifier
+                    .weight(1f)
+                    .padding(8.dp)
+            ) {
+                itemsIndexed(scannedItems) { index, item ->
+                    Row(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .padding(8.dp),
+                        verticalAlignment = Alignment.CenterVertically
+                    ) {
+                        Text(
+                            text = "${index + 1}. $item",
+                            modifier = Modifier.weight(1f)
+                        )
+
+                    }
+                    Divider()
+                }
+            }
+        }
+    }
+}
+
+// (不再直接调用onResult)
+private fun startScanning(
+    context: Context,
+    ubxScan: UBXScan,
+    onScanned: (String) -> Unit
+) {
+    ubxScan.setOnScanListener(context) { result ->
+        onScanned(result)
+    }
+}
+
+private fun stopScanning(ubxScan: UBXScan) {
+    ubxScan.destroy()
+}

+ 3 - 4
app/src/main/java/com/example/pda/ui/DetailsScreen.kt

@@ -40,12 +40,13 @@ import kotlinx.coroutines.launch
 @Composable
 
 fun DetailsScreen(
+    container: String,
     result: String,
     onBack: () -> Unit,
     onMainScreen: () -> Unit,
     onShelfScan: () -> Unit,
 
-) {
+    ) {
     val snackbarHostState = remember { SnackbarHostState() }
     val scope = rememberCoroutineScope()
     val resultCounter = ResultCounter()
@@ -187,7 +188,7 @@ fun DetailsScreen(
                 modifier = Modifier.fillMaxWidth()
             ) {
                 Text(
-                    text = result,
+                    text = container,
                     modifier = Modifier.padding(16.dp),
                     style = MaterialTheme.typography.bodyLarge,
                     overflow = TextOverflow.Ellipsis,
@@ -196,8 +197,6 @@ fun DetailsScreen(
             }
             // 观察状态变化
 
-
-
             // 新增统计列表
             Spacer(modifier = Modifier.height(24.dp))
             Row(

+ 151 - 0
app/src/main/java/com/example/pda/ui/LoginScreen.kt

@@ -0,0 +1,151 @@
+package com.example.pda.ui
+
+import android.util.Log
+import androidx.compose.foundation.layout.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowLeft
+import androidx.compose.material.icons.filled.KeyboardArrowRight
+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.text.input.PasswordVisualTransformation
+import androidx.compose.ui.text.input.VisualTransformation
+import androidx.compose.ui.unit.dp
+import com.example.pda.model.loginItem
+import androidx.compose.ui.res.stringResource
+import com.example.pda.R
+import com.example.pda.model.AppPrefs
+import com.example.pda.model.successItem
+import com.example.pda.network.HttpClient
+import com.example.pda.network.NetworkHelper
+
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun LoginScreen(
+    onSetScreen: () -> Unit,
+    onMainScreen: () -> Unit
+) {
+    var username by remember { mutableStateOf("") }
+    var password by remember { mutableStateOf("") }
+    var showPassword by remember { mutableStateOf(false) }
+    var useritem = loginItem(username = username, password = password)
+
+
+
+
+    var loginStatus by remember { mutableStateOf<Boolean?>(null) }
+
+    // 监听登录状态变化
+    LaunchedEffect(loginStatus) {
+        loginStatus?.takeIf { it }?.let {
+            onMainScreen() // 状态为true时跳转
+            // 在登录成功后调用
+            HttpClient.token = AppPrefs.username
+        }
+
+    }
+
+    Scaffold(
+
+        topBar = {
+            TopAppBar(
+                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("信泰PDA扫描系统")
+                    }
+                },
+                actions = {
+                    IconButton(onClick = { onSetScreen() }) {
+                        Icon(
+                            painter = painterResource(id = R.drawable.ic_settings),
+                            contentDescription = "Settings",
+                            tint = MaterialTheme.colorScheme.onSurface
+                        )
+                    }
+                },
+                colors = TopAppBarDefaults.topAppBarColors(
+                    containerColor = Color(0xFFBCD0C5), // 自定义颜色
+                    titleContentColor = MaterialTheme.colorScheme.onPrimary,
+                    actionIconContentColor = MaterialTheme.colorScheme.onPrimary
+                )
+                
+            )
+
+        }
+    ) { padding ->
+        Column(
+            modifier = Modifier
+                .padding(padding)
+                .fillMaxSize(),
+            verticalArrangement = Arrangement.Center,
+            horizontalAlignment = Alignment.CenterHorizontally
+        ) {
+            OutlinedTextField(
+                value = username,
+                onValueChange = { username = it },
+                label = { Text(stringResource(id = R.string.username_label)) },
+                modifier = Modifier.width(280.dp)
+            )
+            Spacer(modifier = Modifier.height(16.dp))
+            OutlinedTextField(
+                value = password,
+                onValueChange = { password = it },
+                label = { Text(stringResource(id = R.string.password_label)) },
+                visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
+                trailingIcon = {
+                    val image = if (showPassword) Icons.Default.KeyboardArrowRight else Icons.Default.KeyboardArrowLeft
+                    IconButton(onClick = { showPassword = !showPassword }) {
+                        Icon(imageVector = image, contentDescription = null)
+                    }
+                },
+                modifier = Modifier.width(280.dp)
+            )
+            Spacer(modifier = Modifier.height(16.dp))
+            Button(
+                onClick = {
+                    useritem.username=username
+                    useritem.password=password
+                    onlogin (useritem){ success ->
+                        loginStatus = success // 回调中更新状态
+                    }
+                     },
+                modifier = Modifier.width(280.dp) ,
+                 colors = ButtonDefaults.buttonColors(
+                             containerColor = Color(0xFF91BFBF)
+                             )
+            ) {
+                Text(stringResource(id = R.string.login_button))
+            }
+        }
+    }
+}
+
+private fun onlogin(
+    useritem: loginItem,
+    onResult: (Boolean) -> Unit // 新增结果回调
+) {
+    NetworkHelper.userlogin(
+        data = useritem,
+        onSuccess = { successItem ->
+            AppPrefs.username = successItem.openid
+            AppPrefs.islogin = true
+            onResult(true) // 传递成功状态
+        },
+        onError = { msg ->
+            Log.e("login", msg)
+            AppPrefs.islogin = false
+            onResult(false) // 传递失败状态
+        }
+    )
+}

+ 18 - 32
app/src/main/java/com/example/pda/ui/MainScreen.kt

@@ -7,19 +7,25 @@ import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.foundation.background
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
 import com.example.pda.R
+import com.example.pda.network.HttpClient
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun MainScreen(
     onInventoryScan: () -> Unit,
     onShelfScan: () -> Unit,
-    onPingScreen: () -> Unit
+    onSetScreen: () -> Unit
 ) {
+    LaunchedEffect(Unit) {
+        // 在登录成功后调用
+        HttpClient.token = "PDA1"
+    }
     Scaffold(
         topBar = {
             TopAppBar(
@@ -39,37 +45,17 @@ fun MainScreen(
                     containerColor = Color(0xFFBCD0C5), // 自定义颜色
                     titleContentColor = MaterialTheme.colorScheme.onPrimary,
                     actionIconContentColor = MaterialTheme.colorScheme.onPrimary
-                )
-            )
-        },
-        floatingActionButton = {
-            Box(contentAlignment = Alignment.Center) {
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(60.dp)
-            ) {
-                
-                ExtendedFloatingActionButton(
-                    onClick = onInventoryScan,
-                    icon = {
+                ),
+                actions = {
+                    IconButton(onClick = { onSetScreen() }) {
                         Icon(
-                            painter = painterResource(id = R.drawable.ic_scan),
-                            contentDescription = "入库扫描"
+                            painter = painterResource(id = R.drawable.ic_settings),
+                            contentDescription = "Settings",
+                            tint = MaterialTheme.colorScheme.onSurface
                         )
-                    },
-                    text = { Text("入库扫描") }
-                )
-                ExtendedFloatingActionButton(
-                    onClick = onPingScreen,
-                    icon = {
-                        Icon(
-                            painter = painterResource(id = R.drawable.ic_scan),
-                            contentDescription = "货架扫描"
-                        )
-                    },
-                    text = { Text("ping") }
-                )
-            }
-            }
+                    }
+                },
+            )
         }
     ) { innerPadding ->
         Box(
@@ -111,12 +97,12 @@ fun MainScreen(
                     icon = {
                         Icon(
                             painter = painterResource(id = R.drawable.ic_scan),
-                            contentDescription = "货架扫描"
+                            contentDescription = "托盘扫描"
                         )
                     },
                     text = {
                         Text(
-                            text = "货架扫描",
+                            text = "托盘扫描",
                             fontSize = 20.sp,
                             color = MaterialTheme.colorScheme.onSurface
                         )

+ 5 - 3
app/src/main/java/com/example/pda/ui/PingScreen.kt

@@ -20,6 +20,7 @@ import androidx.compose.ui.unit.dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.example.pda.ui.viewmodel.PingViewmodel
 import kotlinx.coroutines.launch
+import com.example.pda.model.AppPrefs
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
@@ -28,8 +29,9 @@ fun PingScreen(onMainScreen: () -> Unit) {
     val uiState = viewModel.uiState.collectAsState()
     val snackbarHostState = remember { SnackbarHostState() }
     val coroutineScope = rememberCoroutineScope()
-    // 新增输入状态
-    val inputIp = remember { mutableStateOf("192.168.1.1") }
+    val inputIp = remember {
+        mutableStateOf(AppPrefs.ip_server.ifEmpty { "192.168.1.1" })
+    }
 
     LaunchedEffect(uiState.value) {
         when (val currentState = uiState.value) {
@@ -94,7 +96,7 @@ fun PingScreen(onMainScreen: () -> Unit) {
                     .align(Alignment.CenterHorizontally) // 修正对齐方式
                     .padding(top = 16.dp),
                 
-                onClick = { viewModel.ping(inputIp.value) } // 使用输入值
+                onClick = { viewModel.ping(inputIp.value);AppPrefs.ip_server=inputIp.value.toString() } // 使用输入值
             ) { Text("Ping测试") }
         }
     }

+ 48 - 18
app/src/main/java/com/example/pda/ui/ScanScreen.kt

@@ -27,13 +27,15 @@ import com.example.pda.function.UBXScan
 @Composable
 fun ScanScreen(
     onBack: () -> Unit,
-    onResult: (String) -> Unit
+    onResult: (container:String, result:String) -> Unit
 ) {
     val context = LocalContext.current
     val ubxScan = remember { UBXScan() }
     var isScanning by remember { mutableStateOf(true) }
     val scannedItems = remember { mutableStateListOf<String>() }
+    val container = remember { mutableStateOf("") }
     var showConfirm by remember { mutableStateOf(false) }
+    var showContainer by remember { mutableStateOf(true) }
     val configuration = LocalConfiguration.current
     val screenHeight = configuration.screenHeightDp.dp
     val boxHeight = screenHeight * 0.15f
@@ -44,8 +46,13 @@ fun ScanScreen(
 
     DisposableEffect(Unit) {
         startScanning(context, ubxScan) { result ->
-            // 添加扫描结果到列表
-            if (isScanning) {
+            if (container.value.isEmpty()) {
+                toneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP)
+                container.value = result
+                showContainer = false
+            }
+
+            else if (isScanning && !showContainer) {
                 scannedItems.add(result)
                 showConfirm = true
                 toneGenerator.startTone(ToneGenerator.TONE_PROP_BEEP)
@@ -79,7 +86,7 @@ fun ScanScreen(
                             tint = MaterialTheme.colorScheme.surfaceTint
                         )
                         Spacer(Modifier.width(8.dp))
-                        Text("信泰PDA—入库扫描")
+                        Text("信泰PDA—入库扫描-组盘扫描")
                     }
                 },
                 colors = TopAppBarDefaults.topAppBarColors(
@@ -87,16 +94,17 @@ fun ScanScreen(
                     titleContentColor = MaterialTheme.colorScheme.onPrimary,
                     actionIconContentColor = MaterialTheme.colorScheme.onPrimary
                 )
-          
-         
             )
         },
         floatingActionButton = {
             if (showConfirm) {
                 ExtendedFloatingActionButton(
+                    modifier = Modifier.size(220.dp, 70.dp),
                     onClick = {
                         // 将列表转换为字符串传递(例如用逗号分隔)
-                        onResult(scannedItems.joinToString(","))
+                        onResult(
+                             container.value,
+                            scannedItems.joinToString(","))
                         scannedItems.clear()
                         showConfirm = false
                     },
@@ -116,16 +124,38 @@ fun ScanScreen(
                 modifier = Modifier
                     .height(boxHeight)
                     .fillMaxWidth()
-                    .clickable(enabled = isScanning) { },
+                    .clickable(enabled = isScanning) {
+                        container.value = ""
+                        showContainer = true
+                    },
                 contentAlignment = Alignment.Center
             ) {
-            if (isScanning) {
-                Column(
-                    modifier = Modifier.fillMaxWidth(),
-                     horizontalAlignment = Alignment.CenterHorizontally
-                    ){
-                     Text(text = "对准条码扫描")
-                    }   
+                if (isScanning && showContainer) {
+                    Text(text = "托盘条码扫描")
+                } else {
+                    Text(text = "物料扫描")
+                }
+            }
+
+            // 添加托盘扫描
+            Row(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(8.dp),
+                verticalAlignment = Alignment.CenterVertically
+            ) {
+                Text(
+                    text = "托盘编码:${container.value}",
+                    modifier = Modifier.weight(1f)
+                )
+
+                IconButton(
+                    onClick = { container.value = ""; showContainer = true },
+                ) {
+                    Icon(
+                        imageVector = Icons.Default.Delete,
+                        contentDescription = "删除"
+                    )
                 }
             }
 
@@ -162,17 +192,17 @@ fun ScanScreen(
     }
 }
 
-// 修改后的startScanning函数(不再直接调用onResult)
+// (不再直接调用onResult)
 private fun startScanning(
     context: Context,
     ubxScan: UBXScan,
     onScanned: (String) -> Unit
 ) {
-    
     ubxScan.setOnScanListener(context) { result ->
         onScanned(result)
     }
 }
+
 private fun stopScanning(ubxScan: UBXScan) {
     ubxScan.destroy()
-}
+}

+ 69 - 0
app/src/main/java/com/example/pda/ui/SettingScreen.kt

@@ -0,0 +1,69 @@
+package com.example.pda.ui
+
+import androidx.compose.foundation.layout.*
+import androidx.compose.material3.*
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.foundation.clickable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+
+
+import com.example.pda.R
+import com.example.pda.model.AppPrefs
+import com.example.pda.network.HttpClient
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SettingScreen(
+    onPingScreen: () -> Unit
+) {
+
+    Scaffold(
+        topBar = {
+            TopAppBar(
+                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("信泰PDA扫描系统")
+                    }
+                },
+                colors = TopAppBarDefaults.topAppBarColors(
+                    containerColor = Color(0xFFBCD0C5), // 自定义定义
+                    titleContentColor = MaterialTheme.colorScheme.onPrimary,
+                    actionIconContentColor = MaterialTheme.colorScheme.onPrimary
+                )
+            )
+        },
+    ) { innerPadding ->
+        Column(
+            modifier = Modifier
+                .padding(innerPadding)
+                .fillMaxSize()
+        ) {
+            // 扫描提示区域
+            Box(
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .clickable(enabled = true, onClick = { onPingScreen() }),
+                contentAlignment = Alignment.Center
+            ) {
+                Text(text = "ping监测")
+            }
+            Text(
+
+                text = "ping监测${AppPrefs.username}"
+            )
+
+        }
+    }
+}

+ 26 - 0
app/src/main/java/com/example/pda/ui/viewmodel/InventoryViewModel.kt

@@ -52,6 +52,32 @@ class InventoryViewModel : ViewModel() {
             )
         }
     }
+    fun getData(container: String)
+    {
+        // 在 viewModelScope 中启动协程以提交数据
+        viewModelScope.launch {
+            // 设置UI状态为 Loading 表示正在加载
+            _uiState.value = UiState.Loading
+            // 调用 NetworkHelper 提交数据
+            NetworkHelper.getIdList (
+                container_code = container,
+                // 成功回调,设置UI状态为 Success 并包含成功消息
+                onSuccess = {
+                    _uiState.value = UiState.Success("返回成功")
+                    
+                    // 打印成功日志
+                    Log.d("getcontainerdata", "返回成功")
+                },
+                // 错误回调,设置UI状态为 Error 并包含错误消息
+                onError = { error ->
+                    _uiState.value = UiState.Error(error)
+                    // 打印错误日志
+                    Log.e("InventoryViewModel", error)
+                }
+            )
+        }
+
+    }
    
    fun resetState() {
         // 重置 UI 状态

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 5 - 0
app/src/main/res/drawable/ic_settings.xml


+ 4 - 0
app/src/main/res/values/strings.xml

@@ -1,3 +1,7 @@
 <resources>
     <string name="app_name">PDA</string>
+    <string name="login_title">登录</string>
+    <string name="username_label">用户名</string>
+    <string name="password_label">密码</string>
+    <string name="login_button">登录</string>
 </resources>