From 04b138d3836e03c9adbcbd367fd71d92905c5206 Mon Sep 17 00:00:00 2001
From: cloudroam <cloudroam>
Date: 星期四, 17 四月 2025 13:17:45 +0800
Subject: [PATCH] add: 重复登录处理

---
 app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt            |   43 ++++
 app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt          |   52 +++++
 app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt           |   32 +++
 app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt           |   43 ++++
 app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt |   45 ++++
 app/src/main/java/com/example/firstapp/database/service/ApiService.kt     |   47 +++-
 app/src/main/java/com/example/firstapp/network/ResponseInterceptor.kt     |   85 +++++++++
 app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt          |   52 +++++
 app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt           |  138 +++++++++++---
 9 files changed, 459 insertions(+), 78 deletions(-)

diff --git a/app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt
index 6bffe16..0c74eb3 100644
--- a/app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt
+++ b/app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt
@@ -44,9 +44,29 @@
         private var currentGroup: ExpressGroup? = null
 
         init {
+            // 设置固定高度和禁用嵌套滚动来解决滑动问题
             binding.rvPackages.apply {
-                layoutManager = LinearLayoutManager(context)
+                layoutManager = object : LinearLayoutManager(context) {
+                    override fun canScrollVertically(): Boolean {
+                        // 禁用内部RecyclerView的垂直滚动
+                        return false
+                    }
+                    
+                    // 确保测量所有子项,防止部分内容不可见
+                    override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
+                        try {
+                            super.onLayoutChildren(recycler, state)
+                        } catch (e: IndexOutOfBoundsException) {
+                            // 捕获可能的异常,防止崩溃
+                        }
+                    }
+                }
                 adapter = packagesAdapter
+                // 禁用嵌套滚动,让外部RecyclerView处理所有滚动
+                isNestedScrollingEnabled = false
+                // 启用回收视图缓存
+                setItemViewCacheSize(20)
+                setHasFixedSize(true)
             }
         }
 
@@ -54,7 +74,13 @@
             currentGroup = group
             binding.tvStationName.text = group.stationName
             binding.tvPackageCount.text = "共${group.packages.size}个包裹"
+            
+            // 确保所有数据都被更新
+            packagesAdapter.submitList(null)
             packagesAdapter.submitList(group.packages)
+            
+            // 请求布局刷新
+            binding.rvPackages.requestLayout()
         }
 
         fun setOnPackageClickListener(listener: (ExpressGroup, ExpressPackage) -> Unit) {
@@ -80,13 +106,21 @@
         holder.bind(pack)
     }
 
+    // 防止部分内容不显示
+    override fun getItemCount(): Int {
+        return currentList.size
+    }
+
     inner class ViewHolder(private val binding: ItemExpressPackageHomeBinding) : 
         RecyclerView.ViewHolder(binding.root) {
         
         init {
             binding.root.setOnClickListener {
-                val pack = getItem(adapterPosition)
-                onPackageClick(pack)
+                val position = adapterPosition
+                if (position != RecyclerView.NO_POSITION) {
+                    val pack = getItem(position)
+                    onPackageClick(pack)
+                }
             }
         }
 
@@ -125,13 +159,21 @@
         holder.bind(pack)
     }
 
+    // 防止部分内容不显示
+    override fun getItemCount(): Int {
+        return currentList.size
+    }
+
     inner class ViewHolder(private val binding: ItemPackageBinding) : 
         RecyclerView.ViewHolder(binding.root) {
         
         init {
             binding.ivPackageStatus.setOnClickListener {
-                val pack = getItem(adapterPosition)
-                onPackagePickup(pack)
+                val position = adapterPosition
+                if (position != RecyclerView.NO_POSITION) {
+                    val pack = getItem(position)
+                    onPackagePickup(pack)
+                }
             }
             
             binding.root.setOnClickListener(null)
diff --git a/app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt
index b0e39a4..16bbf8d 100644
--- a/app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt
+++ b/app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt
@@ -46,9 +46,29 @@
         private var currentGroup: FinanceGroup? = null
 
         init {
+            // 设置固定高度和禁用嵌套滚动来解决滑动问题
             binding.rvPackages.apply {
-                layoutManager = LinearLayoutManager(context)
+                layoutManager = object : LinearLayoutManager(context) {
+                    override fun canScrollVertically(): Boolean {
+                        // 禁用内部RecyclerView的垂直滚动
+                        return false
+                    }
+                    
+                    // 确保测量所有子项,防止部分内容不可见
+                    override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
+                        try {
+                            super.onLayoutChildren(recycler, state)
+                        } catch (e: IndexOutOfBoundsException) {
+                            // 捕获可能的异常,防止崩溃
+                        }
+                    }
+                }
                 adapter = packagesAdapter
+                // 禁用嵌套滚动,让外部RecyclerView处理所有滚动
+                isNestedScrollingEnabled = false
+                // 启用回收视图缓存
+                setItemViewCacheSize(20)
+                setHasFixedSize(true)
             }
         }
 
@@ -56,7 +76,13 @@
             currentGroup = group
             binding.tvStationName.text = group.stationName
             binding.tvPackageCount.text = "共${group.packages.size}笔账单"
+            
+            // 确保所有数据都被更新
+            packagesAdapter.submitList(null)
             packagesAdapter.submitList(group.packages)
+            
+            // 请求布局刷新
+            binding.rvPackages.requestLayout()
         }
 
         fun setOnPackageClickListener(listener: (FinanceGroup, FinancePackage) -> Unit) {
@@ -81,14 +107,22 @@
         val pack = getItem(position)
         holder.bind(pack)
     }
+    
+    // 防止部分内容不显示
+    override fun getItemCount(): Int {
+        return currentList.size
+    }
 
     inner class ViewHolder(private val binding: ItemFinancePackageHomeBinding) :
         RecyclerView.ViewHolder(binding.root) {
 
         init {
             binding.root.setOnClickListener {
-                val pack = getItem(adapterPosition)
-                onPackageClick(pack)
+                val position = adapterPosition
+                if (position != RecyclerView.NO_POSITION) {
+                    val pack = getItem(position)
+                    onPackageClick(pack)
+                }
             }
         }
 
@@ -126,14 +160,22 @@
         val pack = getItem(position)
         holder.bind(pack)
     }
+    
+    // 防止部分内容不显示
+    override fun getItemCount(): Int {
+        return currentList.size
+    }
 
     inner class ViewHolder(private val binding: ItemFinanceBinding) :
         RecyclerView.ViewHolder(binding.root) {
 
         init {
             binding.ivPackageStatus.setOnClickListener {
-                val pack = getItem(adapterPosition)
-                onPackagePickup(pack)
+                val position = adapterPosition
+                if (position != RecyclerView.NO_POSITION) {
+                    val pack = getItem(position)
+                    onPackagePickup(pack)
+                }
             }
 
             binding.root.setOnClickListener(null)
diff --git a/app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt
index 2c1365f..e39c845 100644
--- a/app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt
+++ b/app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt
@@ -47,8 +47,23 @@
 
         init {
             binding.rvPackages.apply {
-                layoutManager = LinearLayoutManager(context)
+                layoutManager = object : LinearLayoutManager(context) {
+                    override fun canScrollVertically(): Boolean {
+                        return false
+                    }
+                    
+                    override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
+                        try {
+                            super.onLayoutChildren(recycler, state)
+                        } catch (e: IndexOutOfBoundsException) {
+                            // 捕获可能的异常,防止崩溃
+                        }
+                    }
+                }
                 adapter = packagesAdapter
+                isNestedScrollingEnabled = false
+                setItemViewCacheSize(20)
+                setHasFixedSize(true)
             }
         }
 
@@ -56,7 +71,11 @@
             currentGroup = group
             binding.tvStationName.text = group.stationName
             binding.tvPackageCount.text = "共${group.packages.size}张机票"
+            
+            packagesAdapter.submitList(null)
             packagesAdapter.submitList(group.packages)
+            
+            binding.rvPackages.requestLayout()
         }
 
         fun setOnPackageClickListener(listener: (FlightGroup, FlightPackage) -> Unit) {
@@ -82,13 +101,20 @@
         holder.bind(pack)
     }
 
+    override fun getItemCount(): Int {
+        return currentList.size
+    }
+
     inner class ViewHolder(private val binding: ItemFlightPackageHomeBinding) :
         RecyclerView.ViewHolder(binding.root) {
 
         init {
             binding.root.setOnClickListener {
-                val pack = getItem(adapterPosition)
-                onPackageClick(pack)
+                val position = adapterPosition
+                if (position != RecyclerView.NO_POSITION) {
+                    val pack = getItem(position)
+                    onPackageClick(pack)
+                }
             }
         }
 
@@ -127,13 +153,20 @@
         holder.bind(pack)
     }
 
+    override fun getItemCount(): Int {
+        return currentList.size
+    }
+
     inner class ViewHolder(private val binding: ItemFlightBinding) :
         RecyclerView.ViewHolder(binding.root) {
 
         init {
             binding.ivPackageStatus.setOnClickListener {
-                val pack = getItem(adapterPosition)
-                onPackagePickup(pack)
+                val position = adapterPosition
+                if (position != RecyclerView.NO_POSITION) {
+                    val pack = getItem(position)
+                    onPackagePickup(pack)
+                }
             }
 
             binding.root.setOnClickListener(null)
diff --git a/app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt
index 856ba45..67ba7d7 100644
--- a/app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt
+++ b/app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt
@@ -42,8 +42,23 @@
 
         init {
             binding.rvPackages.apply {
-                layoutManager = LinearLayoutManager(context)
+                layoutManager = object : LinearLayoutManager(context) {
+                    override fun canScrollVertically(): Boolean {
+                        return false
+                    }
+                    
+                    override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
+                        try {
+                            super.onLayoutChildren(recycler, state)
+                        } catch (e: IndexOutOfBoundsException) {
+                            // 捕获可能的异常,防止崩溃
+                        }
+                    }
+                }
                 adapter = packagesAdapter
+                isNestedScrollingEnabled = false
+                setItemViewCacheSize(20)
+                setHasFixedSize(true)
             }
         }
 
@@ -51,7 +66,11 @@
             currentGroup = group
             binding.tvStationName.text = group.stationName
             binding.tvPackageCount.text = "共${group.packages.size}笔收入"
+            
+            packagesAdapter.submitList(null)
             packagesAdapter.submitList(group.packages)
+            
+            binding.rvPackages.requestLayout()
         }
     }
 }
@@ -71,13 +90,20 @@
         holder.bind(pack)
     }
 
+    override fun getItemCount(): Int {
+        return currentList.size
+    }
+
     inner class ViewHolder(private val binding: ItemIncomePackageHomeBinding) :
         RecyclerView.ViewHolder(binding.root) {
 
         init {
             binding.root.setOnClickListener {
-                val pack = getItem(adapterPosition)
-                onPackageClick(pack)
+                val position = adapterPosition
+                if (position != RecyclerView.NO_POSITION) {
+                    val pack = getItem(position)
+                    onPackageClick(pack)
+                }
             }
         }
 
diff --git a/app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt b/app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt
index 0efd02f..fb7490a 100644
--- a/app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt
+++ b/app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt
@@ -47,8 +47,23 @@
 
         init {
             binding.rvPackages.apply {
-                layoutManager = LinearLayoutManager(context)
+                layoutManager = object : LinearLayoutManager(context) {
+                    override fun canScrollVertically(): Boolean {
+                        return false
+                    }
+                    
+                    override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
+                        try {
+                            super.onLayoutChildren(recycler, state)
+                        } catch (e: IndexOutOfBoundsException) {
+                            // 捕获可能的异常,防止崩溃
+                        }
+                    }
+                }
                 adapter = packagesAdapter
+                isNestedScrollingEnabled = false
+                setItemViewCacheSize(20)
+                setHasFixedSize(true)
             }
         }
 
@@ -56,7 +71,11 @@
             currentGroup = group
             binding.tvStationName.text = group.stationName
             binding.tvPackageCount.text = "共${group.packages.size}张车票"
+            
+            packagesAdapter.submitList(null)
             packagesAdapter.submitList(group.packages)
+            
+            binding.rvPackages.requestLayout()
         }
 
         fun setOnPackageClickListener(listener: (TrainGroup, TrainPackage) -> Unit) {
@@ -82,13 +101,20 @@
         holder.bind(pack)
     }
 
+    override fun getItemCount(): Int {
+        return currentList.size
+    }
+
     inner class ViewHolder(private val binding: ItemTrainPackageHomeBinding) :
         RecyclerView.ViewHolder(binding.root) {
 
         init {
             binding.root.setOnClickListener {
-                val pack = getItem(adapterPosition)
-                onPackageClick(pack)
+                val position = adapterPosition
+                if (position != RecyclerView.NO_POSITION) {
+                    val pack = getItem(position)
+                    onPackageClick(pack)
+                }
             }
         }
 
@@ -127,13 +153,20 @@
         holder.bind(pack)
     }
 
+    override fun getItemCount(): Int {
+        return currentList.size
+    }
+
     inner class ViewHolder(private val binding: ItemTrainBinding) :
         RecyclerView.ViewHolder(binding.root) {
 
         init {
             binding.ivPackageStatus.setOnClickListener {
-                val pack = getItem(adapterPosition)
-                onPackagePickup(pack)
+                val position = adapterPosition
+                if (position != RecyclerView.NO_POSITION) {
+                    val pack = getItem(position)
+                    onPackagePickup(pack)
+                }
             }
 
             binding.root.setOnClickListener(null)
diff --git a/app/src/main/java/com/example/firstapp/database/service/ApiService.kt b/app/src/main/java/com/example/firstapp/database/service/ApiService.kt
index 41c83c2..989348b 100644
--- a/app/src/main/java/com/example/firstapp/database/service/ApiService.kt
+++ b/app/src/main/java/com/example/firstapp/database/service/ApiService.kt
@@ -1,6 +1,7 @@
 package com.example.firstapp.database.service
 
 import TokenResponse
+import android.content.Context
 import com.example.firstapp.database.entity.ApiResponse
 import com.example.firstapp.database.entity.KeywordConfig
 import com.example.firstapp.database.request.ProductOrdersRequest
@@ -16,6 +17,8 @@
 import com.example.firstapp.model.CategoryConfig
 import com.example.firstapp.model.CategoryConfigSync
 import com.example.firstapp.network.AuthInterceptor
+import com.example.firstapp.network.ResponseInterceptor
+import com.example.firstapp.network.TokenExpiredInterceptor
 import okhttp3.MultipartBody
 import okhttp3.OkHttpClient
 import okhttp3.RequestBody
@@ -72,9 +75,11 @@
     @POST("api/account/close")
     suspend fun closeAccount(): AccountCloseResponse
 
-    fun getUserCategories(currentUserId: String): List<CategoryConfig>
+    @GET("api/categoryConfig/getByUserId/{userId}")
+    suspend fun getUserCategories(@Path("userId") currentUserId: String): List<CategoryConfig>
 
-    fun saveUserCategories(categoryConfigSync: CategoryConfigSync)
+    @POST("api/categoryConfig/saveOrUpdate/")
+    suspend fun saveUserCategories(@Body categoryConfigSync: CategoryConfigSync)
 
 }
 
@@ -84,25 +89,37 @@
  //   private const val BASE_URL ="http://192.168.1.213:8080/flower/"
     private const val BASE_URL ="http://14.103.144.28:8080/flower/"
 
+    private lateinit var appContext: Context
+
+    // 初始化方法,需要在Application中调用
+    fun init(context: Context) {
+        appContext = context.applicationContext
+    }
 
     // 创建OkHttpClient,配置拦截器和超时时间
-    private val okHttpClient = OkHttpClient.Builder()
-        .addInterceptor(AuthInterceptor())
-        .connectTimeout(30, TimeUnit.SECONDS)
-        .readTimeout(30, TimeUnit.SECONDS)
-        .writeTimeout(30, TimeUnit.SECONDS)
-        .build()
+    private val okHttpClient by lazy {
+        OkHttpClient.Builder()
+            .addInterceptor(AuthInterceptor())
+            .addInterceptor(TokenExpiredInterceptor(appContext))
+            .addInterceptor(ResponseInterceptor(appContext))
+            .connectTimeout(30, TimeUnit.SECONDS)
+            .readTimeout(30, TimeUnit.SECONDS)
+            .writeTimeout(30, TimeUnit.SECONDS)
+            .build()
+    }
 
 
     //添加Gson解析器,用于自动将JSON响应转换为Kotlin/Java对象
-    private val retrofit = Retrofit
-        .Builder()
-        .client(okHttpClient)
-        .baseUrl(BASE_URL)
-        .addConverterFactory(GsonConverterFactory.create())
-        .build()
+    private val retrofit by lazy {
+        Retrofit
+            .Builder()
+            .client(okHttpClient)
+            .baseUrl(BASE_URL)
+            .addConverterFactory(GsonConverterFactory.create())
+            .build()
+    }
 
     //通过动态代理技术创建ApiService接口的具体实现类
-    val apiService:ApiService = retrofit.create(ApiService::class.java)
+    val apiService by lazy { retrofit.create(ApiService::class.java) }
 
 }
diff --git a/app/src/main/java/com/example/firstapp/network/ResponseInterceptor.kt b/app/src/main/java/com/example/firstapp/network/ResponseInterceptor.kt
new file mode 100644
index 0000000..4c8954d
--- /dev/null
+++ b/app/src/main/java/com/example/firstapp/network/ResponseInterceptor.kt
@@ -0,0 +1,85 @@
+package com.example.firstapp.network
+
+import android.content.Context
+import android.content.Intent
+import android.widget.Toast
+import com.example.firstapp.activity.LoginActivity
+import com.example.firstapp.utils.PreferencesManager
+import com.google.gson.JsonParser
+import okhttp3.Interceptor
+import okhttp3.Response
+import okhttp3.ResponseBody
+import okio.Buffer
+import kotlin.concurrent.thread
+
+/**
+ * 响应拦截器 - 处理业务错误码
+ * 根据后端提供的接口规范,检测token失效的情况
+ */
+class ResponseInterceptor(private val context: Context) : Interceptor {
+    override fun intercept(chain: Interceptor.Chain): Response {
+        val request = chain.request()
+        val response = chain.proceed(request)
+        
+        try {
+            // 只处理成功的响应
+            if (response.isSuccessful) {
+                val responseBody = response.body
+                if (responseBody != null) {
+                    val source = responseBody.source()
+                    source.request(Long.MAX_VALUE)
+                    val buffer = source.buffer.clone()
+                    val responseBodyString = buffer.readUtf8()
+                    
+                    try {
+                        // 解析JSON
+                        val jsonObject = JsonParser.parseString(responseBodyString).asJsonObject
+                        
+                        // 检查业务状态码
+                        if (jsonObject.has("code")) {
+                            val code = jsonObject.get("code").asString
+                            
+                            // 如果状态码表示token失效或登录过期
+                            if (code == "401" || code == "-1" || code == "1001") {
+                                // 检查返回的错误消息
+                                val message = if (jsonObject.has("msg")) jsonObject.get("msg").asString else "登录已失效,请重新登录"
+                                
+                                if (message.contains("token") || 
+                                    message.contains("登录") || 
+                                    message.contains("认证") || 
+                                    message.contains("授权")) {
+                                    
+                                    // 清除本地token
+                                    PreferencesManager.clearUserData()
+                                    
+                                    // 在主线程中显示提示并跳转到登录页面
+                                    thread {
+                                        android.os.Handler(context.mainLooper).post {
+                                            Toast.makeText(context, message, Toast.LENGTH_LONG).show()
+                                            
+                                            // 创建跳转到登录页面的Intent,并添加清除任务栈的标志
+                                            val intent = Intent(context, LoginActivity::class.java).apply {
+                                                flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+                                            }
+                                            context.startActivity(intent)
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    } catch (e: Exception) {
+                        e.printStackTrace()
+                    }
+                    
+                    // 因为我们已经读取了响应体,需要重新创建一个新的响应体
+                    val newResponseBody = ResponseBody.create(responseBody.contentType(), responseBodyString)
+                    return response.newBuilder().body(newResponseBody).build()
+                }
+            }
+        } catch (e: Exception) {
+            e.printStackTrace()
+        }
+        
+        return response
+    }
+} 
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt b/app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt
index 1c73d50..dcc4a2f 100644
--- a/app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt
+++ b/app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt
@@ -1,19 +1,54 @@
 package com.example.firstapp.network
 
+import android.content.Context
+import android.content.Intent
+import android.widget.Toast
+import com.example.firstapp.activity.LoginActivity
 import com.example.firstapp.utils.PreferencesManager
 import okhttp3.Interceptor
 import okhttp3.Response
+import kotlin.concurrent.thread
 
-class TokenExpiredInterceptor : Interceptor {
+class TokenExpiredInterceptor(private val context: Context) : Interceptor {
     override fun intercept(chain: Interceptor.Chain): Response {
         val response = chain.proceed(chain.request())
         
-        // 如果返回401,说明token可能过期
-        if (response.code == 401) {
-            PreferencesManager.clearUserData() // 清除本地token
-            // TODO: 处理token过期,例如跳转到登录页面
+        // 如果返回401或后端自定义的token失效状态码,说明token可能过期
+        if (response.code == 401 || isTokenInvalid(response)) {
+            // 清除本地token
+            PreferencesManager.clearUserData()
+            
+            // 在主线程中显示提示并跳转到登录页面
+            thread {
+                android.os.Handler(context.mainLooper).post {
+                    Toast.makeText(context, "登录已失效,请重新登录", Toast.LENGTH_LONG).show()
+                    
+                    // 创建跳转到登录页面的Intent,并添加清除任务栈的标志
+                    val intent = Intent(context, LoginActivity::class.java).apply {
+                        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+                    }
+                    context.startActivity(intent)
+                }
+            }
         }
         
         return response
     }
+    
+    // 检查响应是否表示token失效
+    private fun isTokenInvalid(response: Response): Boolean {
+        try {
+            // 尝试读取响应体,检查自定义的错误码
+            // 注意:这会消耗响应体,如果需要在后续处理中使用响应体,需要克隆
+            val responseBody = response.peekBody(4096).string()
+            
+            // 根据您的后端逻辑,检查是否包含token失效的提示
+            // 这里假设后端在返回JSON中包含了错误码和消息
+            return responseBody.contains("\"code\":\"401\"") || 
+                   responseBody.contains("token失效") ||
+                   responseBody.contains("请重新登录")
+        } catch (e: Exception) {
+            return false
+        }
+    }
 } 
\ No newline at end of file
diff --git a/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt b/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt
index 45b5b0a..b367f26 100644
--- a/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt
+++ b/app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt
@@ -50,8 +50,9 @@
     val unreadReminderCount: LiveData<Int> = _unreadReminderCount
 
     private lateinit var secureStorage: SecureStorage
-    private lateinit var currentUserId: String
+    private var currentUserId: String = ""
     private lateinit var reminderRecordRepository: ReminderRecordRepository
+    private var categoriesLoaded = false
 
     init {
         // 初始化时加载包裹列表数据
@@ -64,7 +65,13 @@
         secureStorage = SecureStorage(context)
         currentUserId = userId
         reminderRecordRepository = ReminderRecordRepository(context)
-        loadCategories()
+        
+        // 只在首次加载或者用户修改分类后加载分类数据
+        if (!categoriesLoaded) {
+            loadCategories()
+            categoriesLoaded = true
+        }
+        
         // 初始化时更新可见分类
         _categories.value?.let { updateVisibleCategories(it) }
         // 加载未读提醒数量
@@ -188,9 +195,6 @@
     private fun loadCategories() {
         viewModelScope.launch {
             try {
-                // 先尝试从本地获取配置
-                val localCategories = secureStorage.getCategories(currentUserId)
-                
                 // 默认完整分类列表
                 val fullCategories = listOf(
                     CategoryConfig(1, "快递", 0, true),
@@ -206,30 +210,73 @@
                     CategoryConfig(2, "还款", 1, true)
                 )
 
-                if (localCategories.isNotEmpty()) {
-                    // 如果本地有配置,直接使用本地配置
-                    _categories.value = localCategories
-                } else {
-                    try {
-                        // 尝试从服务器获取用户信息判断是否是会员
-                        val savedPhone = PreferencesManager.getPhone()
-                        val response = RetrofitClient.apiService.getUserInfo(savedPhone ?: "")
-                        val isMember = response.code == "0" && response.data?.isMember == true
+                try {
+                    // 获取会员状态
+                    val savedPhone = PreferencesManager.getPhone()
+                    val userResponse = RetrofitClient.apiService.getUserInfo(savedPhone ?: "")
+                    val isMember = userResponse.code == "0" && userResponse.data?.isMember == true
+                    
+                    // 从用户信息中获取正确的userId
+                    if (userResponse.code == "0" && userResponse.data != null) {
+                        currentUserId = userResponse.data?.id.toString()
+                    }
 
-                        // 根据会员状态设置默认分类
-                        val defaultCategories = if (isMember) fullCategories else basicCategories
-                        _categories.value = defaultCategories
-                        secureStorage.saveCategories(currentUserId, defaultCategories)
-                        
-                        // 同步到服务器
+                    // 首先检查本地是否有缓存的分类配置
+                    val localCategories = secureStorage.getCategories(currentUserId)
+                    
+                    if (localCategories.isNotEmpty()) {
+                        // 使用本地缓存的配置
+                        _categories.value = localCategories
+                    } else {
+                        // 本地无缓存,尝试从服务器获取
                         try {
-                            syncCategoriesToServer(defaultCategories)
+                            val serverCategories = RetrofitClient.apiService.getUserCategories(currentUserId)
+                            
+                            if (serverCategories.isNotEmpty()) {
+                                // 服务器有配置,使用服务器配置
+                                // 如果不是会员,需要过滤掉会员专属分类
+                                val filteredCategories = if (isMember) {
+                                    serverCategories
+                                } else {
+                                    serverCategories.filter { it.name == "快递" || it.name == "还款" }
+                                }
+                                
+                                _categories.value = filteredCategories
+                                // 同时更新本地缓存
+                                secureStorage.saveCategories(currentUserId, filteredCategories)
+                                // 同步回服务器(如果有变化)
+                                if (filteredCategories.size != serverCategories.size) {
+                                    syncCategoriesToServer(filteredCategories)
+                                }
+                            } else {
+                                // 服务器返回空,根据会员状态设置默认分类
+                                val defaultCategories = if (isMember) fullCategories else basicCategories
+                                _categories.value = defaultCategories
+                                // 更新本地缓存
+                                secureStorage.saveCategories(currentUserId, defaultCategories)
+                                // 同步到服务器
+                                syncCategoriesToServer(defaultCategories)
+                            }
                         } catch (e: Exception) {
-                            Log.e("HomeViewModel", "Failed to sync categories: ${e.message}")
+                            // 服务器获取失败,使用默认分类
+                            Log.e("HomeViewModel", "Failed to get categories from server: ${e.message}")
+                            val defaultCategories = if (isMember) fullCategories else basicCategories
+                            _categories.value = defaultCategories
+                            secureStorage.saveCategories(currentUserId, defaultCategories)
                         }
-                    } catch (e: Exception) {
-                        // 如果获取用户信息失败,使用基础分类
+                    }
+                } catch (e: Exception) {
+                    // 网络连接失败,尝试从本地获取配置
+                    Log.e("HomeViewModel", "Failed to get user info: ${e.message}")
+                    val localCategories = secureStorage.getCategories(currentUserId)
+                    
+                    if (localCategories.isNotEmpty()) {
+                        // 使用本地缓存的配置
+                        _categories.value = localCategories
+                    } else {
+                        // 本地也没有配置,使用基础分类
                         _categories.value = basicCategories
+                        // 更新本地缓存
                         secureStorage.saveCategories(currentUserId, basicCategories)
                     }
                 }
@@ -239,20 +286,26 @@
                 
             } catch (e: Exception) {
                 Log.e("HomeViewModel", "Failed to load categories: ${e.message}")
+                // 出现异常时,使用基础分类
+                val basicCategories = listOf(
+                    CategoryConfig(1, "快递", 0, true),
+                    CategoryConfig(2, "还款", 1, true)
+                )
+                _categories.value = basicCategories
+                // 更新可见分类
+                updateVisibleCategories(basicCategories)
             }
         }
     }
 
-    private fun syncCategoriesToServer(categories: List<CategoryConfig>) {
-        viewModelScope.launch {
-            try {
-                RetrofitClient.apiService.saveUserCategories(
-                    CategoryConfigSync(currentUserId, categories)
-                )
-            } catch (e: Exception) {
-                // 同步失败,可以稍后重试或者显示提示
-                Log.e("CategorySync", "Failed to sync categories: ${e.message}")
-            }
+    private suspend fun syncCategoriesToServer(categories: List<CategoryConfig>) {
+        try {
+            RetrofitClient.apiService.saveUserCategories(
+                CategoryConfigSync(currentUserId, categories)
+            )
+        } catch (e: Exception) {
+            // 同步失败,可以稍后重试或者显示提示
+            Log.e("CategorySync", "Failed to sync categories: ${e.message}")
         }
     }
 
@@ -261,9 +314,17 @@
         // 保存到本地存储
         secureStorage.saveCategories(currentUserId, categories)
         // 同步到服务器
-        syncCategoriesToServer(categories)
+        viewModelScope.launch {
+            try {
+                syncCategoriesToServer(categories)
+            } catch (e: Exception) {
+                Log.e("HomeViewModel", "Failed to sync categories: ${e.message}")
+            }
+        }
         // 更新可见分类
         updateVisibleCategories(categories)
+        // 标记分类已被修改
+        categoriesLoaded = true
     }
 
     private fun updateVisibleCategories(categories: List<CategoryConfig>) {
@@ -287,10 +348,17 @@
         }
     }
 
+    // 添加方法以强制刷新分类
+    fun refreshCategories() {
+        categoriesLoaded = false
+        loadCategories()
+    }
+
     // 登出时不再清除本地数据
     fun logout() {
         // 只清除内存中的数据
         _categories.value = emptyList()
+        categoriesLoaded = false
     }
 
 }
\ No newline at end of file

--
Gitblit v1.9.3