cloudroam
10 天以前 04b138d3836e03c9adbcbd367fd71d92905c5206
add: 重复登录处理
已修改8个文件
已添加1个文件
469 ■■■■■ 文件已修改
app/src/main/java/com/example/firstapp/adapter/ExpressAdapter.kt 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/FinanceAdapter.kt 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/FlightAdapter.kt 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/IncomeAdapter.kt 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/adapter/TrainAdapter.kt 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/database/service/ApiService.kt 27 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/network/ResponseInterceptor.kt 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/network/TokenExpiredInterceptor.kt 45 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/src/main/java/com/example/firstapp/ui/home/HomeViewModel.kt 108 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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)
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val pack = getItem(position)
                onPackageClick(pack)
                }
            }
        }
@@ -125,14 +159,22 @@
        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)
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val pack = getItem(position)
                onPackagePickup(pack)
            }
            }
            
            binding.root.setOnClickListener(null)
        }
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) {
@@ -82,13 +108,21 @@
        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)
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val pack = getItem(position)
                onPackageClick(pack)
                }
            }
        }
@@ -127,14 +161,22 @@
        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)
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val pack = getItem(position)
                onPackagePickup(pack)
            }
            }
            binding.root.setOnClickListener(null)
        }
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)
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val pack = getItem(position)
                onPackageClick(pack)
                }
            }
        }
@@ -127,14 +153,21 @@
        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)
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val pack = getItem(position)
                onPackagePickup(pack)
            }
            }
            binding.root.setOnClickListener(null)
        }
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,15 +90,22 @@
        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)
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val pack = getItem(position)
                onPackageClick(pack)
            }
        }
        }
        fun bind(pack: IncomePackage) {
            binding.tvCompany.text = pack.company
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)
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val pack = getItem(position)
                onPackageClick(pack)
                }
            }
        }
@@ -127,14 +153,21 @@
        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)
                val position = adapterPosition
                if (position != RecyclerView.NO_POSITION) {
                    val pack = getItem(position)
                onPackagePickup(pack)
            }
            }
            binding.root.setOnClickListener(null)
        }
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()
    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
    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) }
}
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
    }
}
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
        }
    }
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)
        // 只在首次加载或者用户修改分类后加载分类数据
        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)
                )
                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 localCategories = secureStorage.getCategories(currentUserId)
                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
                            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 get categories from server: ${e.message}")
                        val defaultCategories = if (isMember) fullCategories else basicCategories
                        _categories.value = defaultCategories
                        secureStorage.saveCategories(currentUserId, defaultCategories)
                        // 同步到服务器
                        try {
                            syncCategoriesToServer(defaultCategories)
                        } catch (e: Exception) {
                            Log.e("HomeViewModel", "Failed to sync categories: ${e.message}")
                        }
                        }
                    } 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,12 +286,19 @@
                
            } 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 {
    private suspend fun syncCategoriesToServer(categories: List<CategoryConfig>) {
            try {
                RetrofitClient.apiService.saveUserCategories(
                    CategoryConfigSync(currentUserId, categories)
@@ -254,16 +308,23 @@
                Log.e("CategorySync", "Failed to sync categories: ${e.message}")
            }
        }
    }
    fun saveCategories(categories: List<CategoryConfig>) {
        _categories.value = categories
        // 保存到本地存储
        secureStorage.saveCategories(currentUserId, 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
    }
}