Parcourir la source

feat: 添加认证模块、初始化管理器和相关工具类

- 添加 TokenStore 和 AuthLocalDataSourceImpl(认证模块完整实现)
- 添加 AppInitializer 统一初始化管理(简化 Application 代码)
- 添加 SocketIOManager 自动管理连接(生命周期管理)
- 添加其他 base-common 工具类和 UI 组件
- 重构 Application,移除业务逻辑,只保留初始化调用
- 更新 ViewModel 使用 base-common 的 AuthRepository
wangmeng il y a 3 semaines
Parent
commit
00cc47814e
62 fichiers modifiés avec 9531 ajouts et 228 suppressions
  1. 4 170
      app/src/main/java/com/narutohuo/xindazhou/XinDaZhouApplication.kt
  2. 1 1
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/LoginViewModel.kt
  3. 7 2
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/LoginViewModelFactory.kt
  4. 1 1
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/RegisterViewModel.kt
  5. 7 2
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/RegisterViewModelFactory.kt
  6. 2 2
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/UserViewModel.kt
  7. 274 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/AuthManager.kt
  8. 99 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/LoginActivity.kt
  9. 135 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/RegisterActivity.kt
  10. 26 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/local/AuthLocalDataSource.kt
  11. 29 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/local/AuthLocalDataSourceImpl.kt
  12. 37 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/remote/AuthApi.kt
  13. 64 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/remote/AuthRemoteDataSource.kt
  14. 10 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/LoginRequest.kt
  15. 19 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/LoginResponse.kt
  16. 10 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/RegisterRequest.kt
  17. 105 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/repository/AuthRepository.kt
  18. 155 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/storage/TokenStore.kt
  19. 100 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/ui/LoginFragment.kt
  20. 104 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/ui/RegisterFragment.kt
  21. 136 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/bridge/BridgeManager.kt
  22. 272 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/camera/CameraHelper.kt
  23. 176 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/config/ConfigManager.kt
  24. 205 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/crash/CrashHelper.kt
  25. 371 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/dialog/CascadePickerDialog.kt
  26. 439 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/dialog/DialogHelper.kt
  27. 105 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/executor/ExecutorManager.kt
  28. 180 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/file/FilePickerHelper.kt
  29. 212 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/image/ImageLoader.kt
  30. 154 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/launch/AppInitializer.kt
  31. 71 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/launch/AppLaunchManager.kt
  32. 79 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/launch/LaunchActivity.kt
  33. 118 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/log/LogHelper.kt
  34. 125 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiBaseRemoteDataSource.kt
  35. 54 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiBaseRepository.kt
  36. 8 35
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiResponseParser.kt
  37. 38 15
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiServiceFactory.kt
  38. 104 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/NetworkHelper.kt
  39. 934 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/网络请求MVVM封装方案.md
  40. 926 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/网络请求封装方案.html
  41. 277 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/permission/PermissionHelper.kt
  42. 214 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/router/RouterHelper.kt
  43. 158 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/SocketIOManager.kt
  44. 181 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/storage/StorageManager.kt
  45. 40 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/ActivityExtensions.kt
  46. 314 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/ActivityManager.kt
  47. 489 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/BaseActivity.kt
  48. 137 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/BaseFragment.kt
  49. 40 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/FragmentExtensions.kt
  50. 181 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/MessageHelper.kt
  51. 167 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/StatusBarHelper.kt
  52. 539 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/UI层封装方案.md
  53. 156 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/util/UtilManager.kt
  54. 44 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/VersionCheckActivity.kt
  55. 200 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/VersionUpdateManager.kt
  56. 27 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/datasource/remote/VersionApi.kt
  57. 44 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/datasource/remote/VersionRemoteDataSource.kt
  58. 17 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/model/VersionResponse.kt
  59. 71 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/repository/VersionRepository.kt
  60. 77 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/ui/UpdateDialogFragment.kt
  61. 128 0
      base-common/src/main/res/layout/fragment_login.xml
  62. 134 0
      base-common/src/main/res/layout/fragment_register.xml

+ 4 - 170
app/src/main/java/com/narutohuo/xindazhou/XinDaZhouApplication.kt

@@ -1,184 +1,18 @@
 package com.narutohuo.xindazhou
 
 import android.app.Application
-import androidx.lifecycle.LifecycleObserver
-import androidx.lifecycle.OnLifecycleEvent
-import androidx.lifecycle.ProcessLifecycleOwner
-import com.alibaba.android.arouter.launcher.ARouter
-import com.narutohuo.xindazhou.common.config.ServerConfigManager
-import com.narutohuo.xindazhou.share.factory.ShareServiceFactory
-import com.narutohuo.xindazhou.socketio.factory.SocketIORepositoryFactory
-import com.narutohuo.xindazhou.user.datasource.local.TokenManager
-import com.narutohuo.xindazhou.user.repository.AuthRepository
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
+import com.narutohuo.xindazhou.common.launch.AppInitializer
 
 /**
  * 应用程序入口
  */
-class XinDaZhouApplication : Application(), LifecycleObserver {
-    
-    // 用于后台执行 SocketIO 重连的协程作用域
-    private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
+class XinDaZhouApplication : Application() {
     
     override fun onCreate() {
         super.onCreate()
         
-        // 初始化 ARouter(必须在其他初始化之前)
-        if (BuildConfig.DEBUG) {
-            // 开启日志
-            ARouter.openLog()
-            // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
-            ARouter.openDebug()
-        }
-        // 初始化 ARouter
-        ARouter.init(this)
-        
-        // 初始化 ServerConfigManager
-        ServerConfigManager.init(applicationContext)
-        
-        // 初始化 TokenManager
-        TokenManager.init(applicationContext)
-        
-        // 初始化网络管理器(使用 Fastjson)
-        val isDebug = try {
-            BuildConfig.DEBUG
-        } catch (e: Exception) {
-            false
-        }
-        com.narutohuo.xindazhou.core.network.NetworkManager.init(
-            com.narutohuo.xindazhou.core.network.NetworkConfig.Builder()
-                .baseUrl(ServerConfigManager.getHttpServerUrl())
-                .isDebug(isDebug)
-                .enableLogging(isDebug)
-                .build()
-        )
-        
-        // 配置 NetworkManager 的 Token 提供器
-        com.narutohuo.xindazhou.core.network.NetworkManager.tokenProvider = {
-            TokenManager.getAccessToken()
-        }
-        
-        // 初始化分享服务(从资源文件读取配置)
-        // 注意:需要在 strings.xml 中配置 share_umeng_app_key 等参数
-        try {
-            ShareServiceFactory.init(applicationContext)
-            android.util.Log.d("XinDaZhouApplication", "分享服务初始化成功")
-        } catch (e: Exception) {
-            // 分享服务初始化失败不影响应用运行,但分享功能将不可用
-            android.util.Log.e("XinDaZhouApplication", "分享服务初始化失败", e)
-        }
-        
-        // 注册 App 生命周期监听(监听前后台切换)
-        ProcessLifecycleOwner.get().lifecycle.addObserver(this)
-    }
-    
-    /**
-     * App 进入前台时调用
-     * 
-     * 检查 SocketIO 连接状态,如果断开则自动重连
-     * 如果连接失败(可能是 Token 过期),会自动刷新 Token 后重连
-     */
-    @OnLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_START)
-    fun onAppForeground() {
-        android.util.Log.d("XinDaZhouApplication", "App 进入前台,检查 SocketIO 连接状态")
-        
-        // 检查是否已登录
-        if (!TokenManager.isLoggedIn()) {
-            android.util.Log.d("XinDaZhouApplication", "用户未登录,跳过 SocketIO 重连")
-            return
-        }
-        
-        // 检查连接状态,如果断开则重连
-        appScope.launch {
-            val socketIORepository = SocketIORepositoryFactory.getInstance()
-            val socketUrl = ServerConfigManager.getSocketIOUrl()
-            var token = TokenManager.getAccessToken()
-            
-            if (token.isNullOrEmpty()) {
-                android.util.Log.w("XinDaZhouApplication", "Token 为空,无法重连 SocketIO")
-                return@launch
-            }
-            
-            // 如果已连接,无需重连
-            if (socketIORepository.isConnected()) {
-                android.util.Log.d("XinDaZhouApplication", "SocketIO 已连接,无需重连")
-                return@launch
-            }
-            
-            // 先尝试连接
-            android.util.Log.d("XinDaZhouApplication", "SocketIO 未连接,开始重连...")
-            socketIORepository.checkAndReconnect(socketUrl, token)
-            
-            // 等待 2 秒,检查连接是否成功
-            delay(2000)
-            
-            // 如果连接仍然失败,可能是 Token 过期,尝试刷新 Token 后重连
-            if (!socketIORepository.isConnected()) {
-                android.util.Log.d("XinDaZhouApplication", "连接失败,可能是 Token 过期,尝试刷新 Token 后重连")
-                val refreshedToken = refreshTokenIfNeeded()
-                if (!refreshedToken.isNullOrEmpty() && refreshedToken != token) {
-                    android.util.Log.d("XinDaZhouApplication", "Token 已刷新,使用新 Token 重连")
-                    socketIORepository.checkAndReconnect(socketUrl, refreshedToken)
-                } else {
-                    android.util.Log.w("XinDaZhouApplication", "Token 刷新失败或未刷新,无法重连")
-                }
-            } else {
-                android.util.Log.d("XinDaZhouApplication", "SocketIO 重连成功")
-            }
-        }
-    }
-    
-    /**
-     * 刷新 Token(如果需要)
-     * 
-     * @return 新的 accessToken,如果刷新失败返回 null
-     */
-    private suspend fun refreshTokenIfNeeded(): String? {
-        val refreshToken = TokenManager.getRefreshToken()
-        if (refreshToken.isNullOrEmpty()) {
-            android.util.Log.w("XinDaZhouApplication", "RefreshToken 为空,无法刷新")
-            return null
-        }
-        
-        return try {
-            val authRepository = AuthRepository()
-            val result = authRepository.refreshToken(refreshToken)
-            
-            result.getOrNull()?.let { loginResponse ->
-                // 保存新的 Token
-                TokenManager.saveToken(
-                    loginResponse.accessToken,
-                    loginResponse.refreshToken,
-                    loginResponse.userId
-                )
-                android.util.Log.d("XinDaZhouApplication", "Token 刷新成功")
-                loginResponse.accessToken
-            } ?: run {
-                android.util.Log.w("XinDaZhouApplication", "Token 刷新失败: ${result.exceptionOrNull()?.message}")
-                null
-            }
-        } catch (e: Exception) {
-            android.util.Log.e("XinDaZhouApplication", "Token 刷新异常", e)
-            null
-        }
-    }
-    
-    /**
-     * App 进入后台时调用
-     * 
-     * 注意:通常不断开 SocketIO 连接,因为:
-     * 1. SocketIO 连接是轻量级的
-     * 2. 用户可能需要在后台接收消息
-     * 3. 系统会在内存不足时自动清理
-     */
-    @OnLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_STOP)
-    fun onAppBackground() {
-        android.util.Log.d("XinDaZhouApplication", "App 进入后台")
-        // 不断开连接,保持连接以便接收消息
+        // 统一初始化所有模块
+        AppInitializer.init(this)
     }
 }
 

+ 1 - 1
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/LoginViewModel.kt

@@ -3,7 +3,7 @@ package com.narutohuo.xindazhou.user.ui.viewmodel
 import android.app.Application
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
-import com.narutohuo.xindazhou.user.repository.AuthRepository
+import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
 import com.narutohuo.xindazhou.user.ui.constant.UiConstants
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow

+ 7 - 2
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/LoginViewModelFactory.kt

@@ -3,7 +3,9 @@ package com.narutohuo.xindazhou.user.ui.viewmodel
 import android.app.Application
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
-import com.narutohuo.xindazhou.user.repository.AuthRepository
+import com.narutohuo.xindazhou.common.auth.datasource.local.AuthLocalDataSourceImpl
+import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSourceImpl
+import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
 
 /**
  * LoginViewModel工厂类
@@ -15,7 +17,10 @@ class LoginViewModelFactory(
     @Suppress("UNCHECKED_CAST")
     override fun <T : ViewModel> create(modelClass: Class<T>): T {
         if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
-            val authRepository = AuthRepository()
+            val authRepository = AuthRepository(
+                AuthRemoteDataSourceImpl(),
+                AuthLocalDataSourceImpl()
+            )
             return LoginViewModel(application, authRepository) as T
         }
         throw IllegalArgumentException("Unknown ViewModel class")

+ 1 - 1
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/RegisterViewModel.kt

@@ -3,7 +3,7 @@ package com.narutohuo.xindazhou.user.ui.viewmodel
 import android.app.Application
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
-import com.narutohuo.xindazhou.user.repository.AuthRepository
+import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
 import com.narutohuo.xindazhou.user.ui.constant.UiConstants
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow

+ 7 - 2
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/RegisterViewModelFactory.kt

@@ -3,7 +3,9 @@ package com.narutohuo.xindazhou.user.ui.viewmodel
 import android.app.Application
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
-import com.narutohuo.xindazhou.user.repository.AuthRepository
+import com.narutohuo.xindazhou.common.auth.datasource.local.AuthLocalDataSourceImpl
+import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSourceImpl
+import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
 
 /**
  * RegisterViewModel工厂类
@@ -15,7 +17,10 @@ class RegisterViewModelFactory(
     @Suppress("UNCHECKED_CAST")
     override fun <T : ViewModel> create(modelClass: Class<T>): T {
         if (modelClass.isAssignableFrom(RegisterViewModel::class.java)) {
-            val authRepository = AuthRepository()
+            val authRepository = AuthRepository(
+                AuthRemoteDataSourceImpl(),
+                AuthLocalDataSourceImpl()
+            )
             return RegisterViewModel(application, authRepository) as T
         }
         throw IllegalArgumentException("Unknown ViewModel class")

+ 2 - 2
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/UserViewModel.kt

@@ -7,7 +7,7 @@ import com.narutohuo.xindazhou.socketio.factory.SocketIORepositoryFactory
 import com.narutohuo.xindazhou.socketio.model.SocketIOResponse
 import com.narutohuo.xindazhou.socketio.repository.SocketIORepository
 import com.narutohuo.xindazhou.common.config.ServerConfigManager
-import com.narutohuo.xindazhou.user.datasource.local.TokenManager
+import com.narutohuo.xindazhou.common.auth.AuthManager
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -65,7 +65,7 @@ class UserViewModel(
         viewModelScope.launch {
             addLog("正在连接SocketIO服务器...")
             val socketUrl = ServerConfigManager.getSocketIOUrl()
-            val token = TokenManager.getAccessToken()
+            val token = AuthManager.getAccessToken()
             
             if (token.isNullOrEmpty()) {
                 val errorMsg = "未登录,无法获取Token"

+ 274 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/AuthManager.kt

@@ -0,0 +1,274 @@
+package com.narutohuo.xindazhou.common.auth
+
+import android.content.Context
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.lifecycleScope
+import com.narutohuo.xindazhou.common.auth.datasource.local.AuthLocalDataSource
+import com.narutohuo.xindazhou.common.auth.datasource.local.AuthLocalDataSourceImpl
+import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSource
+import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSourceImpl
+import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
+import com.narutohuo.xindazhou.common.auth.storage.TokenStore
+import com.narutohuo.xindazhou.common.log.LogHelper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * 认证管理器
+ * 
+ * 封装认证相关的完整逻辑,包括:
+ * - 用户登录
+ * - 用户注册
+ * - Token 刷新
+ * - 登录状态管理
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 Application 中初始化(推荐方式,使用默认实现)
+ * AuthManager.init(context)
+ * 
+ * // 或者使用自定义的 localDataSource
+ * AuthManager.init(localDataSource)
+ * 
+ * // 在 Activity 中登录
+ * AuthManager.login(activity, mobile, password) { result ->
+ *     result.onSuccess { response ->
+ *         // 登录成功,跳转到主界面
+ *     }
+ *     result.onFailure { error ->
+ *         // 登录失败,显示错误
+ *     }
+ * }
+ * ```
+ */
+object AuthManager {
+    
+    private var localDataSource: AuthLocalDataSource? = null
+    private var remoteDataSource: AuthRemoteDataSource = AuthRemoteDataSourceImpl()
+    private var repository: AuthRepository? = null
+    
+    /**
+     * 初始化认证管理器(使用默认实现)
+     * 
+     * 需要在 Application.onCreate() 中调用
+     * 会自动初始化 TokenStore 并使用默认的 AuthLocalDataSourceImpl
+     * 
+     * @param context 应用上下文
+     */
+    fun init(context: Context) {
+        // 初始化 TokenStore
+        TokenStore.init(context)
+        // 使用默认实现
+        val defaultLocalDataSource = AuthLocalDataSourceImpl()
+        this.localDataSource = defaultLocalDataSource
+        this.repository = AuthRepository(remoteDataSource, defaultLocalDataSource)
+    }
+    
+    /**
+     * 初始化认证管理器(使用自定义实现)
+     * 
+     * 如果需要自定义本地数据源,可以使用此方法
+     * 
+     * @param localDataSource 自定义的本地数据源
+     */
+    fun init(localDataSource: AuthLocalDataSource) {
+        this.localDataSource = localDataSource
+        this.repository = AuthRepository(remoteDataSource, localDataSource)
+    }
+    
+    /**
+     * 用户登录
+     * 
+     * @param activity Activity 实例
+     * @param mobile 手机号
+     * @param password 密码
+     * @param onResult 登录结果回调
+     */
+    fun login(
+        activity: FragmentActivity,
+        mobile: String,
+        password: String,
+        onResult: (Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>) -> Unit
+    ) {
+        activity.lifecycleScope.launch {
+            try {
+                val repo = repository ?: run {
+                    LogHelper.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
+                    onResult(Result.failure(Exception("AuthManager 未初始化")))
+                    return@launch
+                }
+                
+                val result = repo.login(mobile, password)
+                onResult(result as Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>)
+            } catch (e: Exception) {
+                LogHelper.e("AuthManager", "登录异常", e)
+                onResult(Result.failure(e))
+            }
+        }
+    }
+    
+    /**
+     * 用户注册
+     * 
+     * @param activity Activity 实例
+     * @param mobile 手机号
+     * @param password 密码
+     * @param onResult 注册结果回调
+     */
+    fun register(
+        activity: FragmentActivity,
+        mobile: String,
+        password: String,
+        onResult: (Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>) -> Unit
+    ) {
+        activity.lifecycleScope.launch {
+            try {
+                val repo = repository ?: run {
+                    LogHelper.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
+                    onResult(Result.failure(Exception("AuthManager 未初始化")))
+                    return@launch
+                }
+                
+                val result = repo.register(mobile, password)
+                onResult(result as Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>)
+            } catch (e: Exception) {
+                LogHelper.e("AuthManager", "注册异常", e)
+                onResult(Result.failure(e))
+            }
+        }
+    }
+    
+    /**
+     * 刷新 Token
+     * 
+     * @param activity Activity 实例
+     * @param refreshToken 刷新令牌
+     * @param onResult 刷新结果回调
+     */
+    fun refreshToken(
+        activity: FragmentActivity,
+        refreshToken: String,
+        onResult: (Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>) -> Unit
+    ) {
+        activity.lifecycleScope.launch {
+            try {
+                val repo = repository ?: run {
+                    LogHelper.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
+                    onResult(Result.failure(Exception("AuthManager 未初始化")))
+                    return@launch
+                }
+                
+                val result = repo.refreshToken(refreshToken)
+                onResult(result as Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>)
+            } catch (e: Exception) {
+                LogHelper.e("AuthManager", "Token 刷新异常", e)
+                onResult(Result.failure(e))
+            }
+        }
+    }
+    
+    // ========== 便捷方法 ==========
+    
+    /**
+     * 检查是否已登录
+     * 
+     * @return true 表示已登录
+     */
+    fun isLoggedIn(): Boolean {
+        return TokenStore.isLoggedIn()
+    }
+    
+    /**
+     * 获取当前 Access Token
+     * 
+     * @return Access Token,如果未登录返回 null
+     */
+    fun getAccessToken(): String? {
+        return TokenStore.getAccessToken()
+    }
+    
+    /**
+     * 获取当前 Refresh Token
+     * 
+     * @return Refresh Token,如果未登录返回 null
+     */
+    fun getRefreshToken(): String? {
+        return TokenStore.getRefreshToken()
+    }
+    
+    /**
+     * 刷新 Token(如果需要)
+     * 
+     * 在后台协程中自动刷新 Token,无需 Activity 生命周期
+     * 
+     * @param scope 协程作用域(例如:applicationScope)
+     * @param onResult 刷新结果回调(可选)
+     * @return 新的 accessToken,如果刷新失败返回 null
+     */
+    suspend fun refreshTokenIfNeeded(
+        scope: CoroutineScope? = null,
+        onResult: ((Result<String?>) -> Unit)? = null
+    ): String? {
+        val refreshToken = TokenStore.getRefreshToken()
+        if (refreshToken.isNullOrEmpty()) {
+            LogHelper.w("AuthManager", "RefreshToken 为空,无法刷新")
+            onResult?.invoke(Result.failure(Exception("RefreshToken 为空")))
+            return null
+        }
+        
+        return try {
+            val repo = repository ?: run {
+                val error = Exception("AuthManager 未初始化")
+                LogHelper.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
+                onResult?.invoke(Result.failure(error))
+                return null
+            }
+            
+            val result = repo.refreshToken(refreshToken)
+            
+            result.getOrNull()?.let { loginResponse ->
+                // Token 已由 AuthRepository 自动保存
+                LogHelper.d("AuthManager", "Token 刷新成功")
+                onResult?.invoke(Result.success(loginResponse.accessToken))
+                loginResponse.accessToken
+            } ?: run {
+                val error = result.exceptionOrNull() ?: Exception("Token 刷新失败")
+                LogHelper.w("AuthManager", "Token 刷新失败: ${error.message}")
+                onResult?.invoke(Result.failure(error))
+                null
+            }
+        } catch (e: Exception) {
+            LogHelper.e("AuthManager", "Token 刷新异常", e)
+            onResult?.invoke(Result.failure(e))
+            null
+        }
+    }
+    
+    /**
+     * 在后台协程中刷新 Token(如果需要)
+     * 
+     * @param scope 协程作用域
+     * @param onResult 刷新结果回调(可选)
+     */
+    fun refreshTokenIfNeededAsync(
+        scope: CoroutineScope,
+        onResult: ((Result<String?>) -> Unit)? = null
+    ) {
+        scope.launch(Dispatchers.IO) {
+            refreshTokenIfNeeded(scope, onResult)
+        }
+    }
+    
+    /**
+     * 登出
+     * 
+     * 清除所有 Token 和用户信息
+     */
+    fun logout() {
+        TokenStore.clearToken()
+        LogHelper.d("AuthManager", "用户已登出")
+    }
+}
+

+ 99 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/LoginActivity.kt

@@ -0,0 +1,99 @@
+package com.narutohuo.xindazhou.common.auth
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.FragmentActivity
+import com.narutohuo.xindazhou.common.auth.ui.LoginFragment
+import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.common.ui.ActivityManager
+
+/**
+ * 登录页 Activity(完整封装,可直接使用)
+ * 
+ * 使用方式:
+ * 通过 Intent 启动,传入主界面 Activity 类名:
+ * ```kotlin
+ * val intent = Intent(context, LoginActivity::class.java).apply {
+ *     putExtra(LoginActivity.EXTRA_MAIN_ACTIVITY_CLASS, MainActivity::class.java.name)
+ * }
+ * startActivity(intent)
+ * ```
+ */
+class LoginActivity : AppCompatActivity() {
+    
+    companion object {
+        /**
+         * Intent 参数:主界面 Activity 类名
+         */
+        const val EXTRA_MAIN_ACTIVITY_CLASS = "extra_main_activity_class"
+    }
+    
+    private var mainActivityClass: Class<out FragmentActivity>? = null
+    
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        
+        // 注册到 ActivityManager
+        ActivityManager.addActivity(this)
+        
+        // 从 Intent 获取主界面 Activity 类名
+        val mainActivityClassName = intent.getStringExtra(EXTRA_MAIN_ACTIVITY_CLASS)
+        
+        if (mainActivityClassName.isNullOrEmpty()) {
+            LogHelper.e("LoginActivity", "缺少参数:EXTRA_MAIN_ACTIVITY_CLASS")
+            finish()
+            return
+        }
+        
+        // 加载主界面 Activity 类
+        try {
+            @Suppress("UNCHECKED_CAST")
+            mainActivityClass = Class.forName(mainActivityClassName) as? Class<out FragmentActivity>
+            if (mainActivityClass == null) {
+                LogHelper.e("LoginActivity", "主界面 Activity 类不是 FragmentActivity 的子类:$mainActivityClassName")
+                finish()
+                return
+            }
+        } catch (e: Exception) {
+            LogHelper.e("LoginActivity", "加载主界面 Activity 类失败:$mainActivityClassName", e)
+            finish()
+            return
+        }
+        
+        // 设置简单的布局,只包含 Fragment 容器
+        val container = android.widget.FrameLayout(this).apply {
+            id = android.R.id.content
+        }
+        setContentView(container)
+        
+        // 显示登录 Fragment
+        if (supportFragmentManager.findFragmentById(android.R.id.content) == null) {
+            supportFragmentManager.beginTransaction()
+                .replace(android.R.id.content, LoginFragment())
+                .commit()
+        }
+    }
+    
+    /**
+     * 登录成功后的回调(公开方法,Fragment 可以调用)
+     */
+    fun handleLoginSuccess() {
+        val targetClass = mainActivityClass ?: run {
+            LogHelper.e("LoginActivity", "主界面 Activity 类未设置")
+            return
+        }
+        
+        val intent = Intent(this, targetClass).apply {
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+        }
+        startActivity(intent)
+        finish()
+    }
+    
+    override fun onDestroy() {
+        super.onDestroy()
+        // 从 ActivityManager 中移除
+        ActivityManager.removeActivity(this)
+    }
+}

+ 135 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/RegisterActivity.kt

@@ -0,0 +1,135 @@
+package com.narutohuo.xindazhou.common.auth
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.FragmentActivity
+import com.narutohuo.xindazhou.common.auth.ui.RegisterFragment
+import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.common.ui.ActivityManager
+
+/**
+ * 注册页 Activity(完整封装,可直接使用)
+ * 
+ * 使用方式:
+ * 通过 Intent 启动,传入必要的参数:
+ * ```kotlin
+ * val intent = Intent(context, RegisterActivity::class.java).apply {
+ *     putExtra(RegisterActivity.EXTRA_LOGIN_ACTIVITY_CLASS, LoginActivity::class.java.name)
+ *     putExtra(RegisterActivity.EXTRA_MAIN_ACTIVITY_CLASS, MainActivity::class.java.name)
+ *     putExtra(RegisterActivity.EXTRA_GO_TO_MAIN_AFTER_REGISTER, false) // 注册后是否直接进入主界面
+ * }
+ * startActivity(intent)
+ * ```
+ */
+class RegisterActivity : AppCompatActivity() {
+    
+    companion object {
+        /**
+         * Intent 参数:登录页 Activity 类名
+         */
+        const val EXTRA_LOGIN_ACTIVITY_CLASS = "extra_login_activity_class"
+        
+        /**
+         * Intent 参数:主界面 Activity 类名
+         */
+        const val EXTRA_MAIN_ACTIVITY_CLASS = "extra_main_activity_class"
+        
+        /**
+         * Intent 参数:注册成功后是否直接跳转到主界面(默认 false,跳转到登录页)
+         */
+        const val EXTRA_GO_TO_MAIN_AFTER_REGISTER = "extra_go_to_main_after_register"
+    }
+    
+    private var loginActivityClass: Class<out FragmentActivity>? = null
+    private var mainActivityClass: Class<out FragmentActivity>? = null
+    private var goToMainAfterRegister: Boolean = false
+    
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        
+        // 注册到 ActivityManager
+        ActivityManager.addActivity(this)
+        
+        // 从 Intent 获取参数
+        val loginActivityClassName = intent.getStringExtra(EXTRA_LOGIN_ACTIVITY_CLASS)
+        val mainActivityClassName = intent.getStringExtra(EXTRA_MAIN_ACTIVITY_CLASS)
+        goToMainAfterRegister = intent.getBooleanExtra(EXTRA_GO_TO_MAIN_AFTER_REGISTER, false)
+        
+        if (loginActivityClassName.isNullOrEmpty()) {
+            LogHelper.e("RegisterActivity", "缺少参数:EXTRA_LOGIN_ACTIVITY_CLASS")
+            finish()
+            return
+        }
+        
+        if (mainActivityClassName.isNullOrEmpty()) {
+            LogHelper.e("RegisterActivity", "缺少参数:EXTRA_MAIN_ACTIVITY_CLASS")
+            finish()
+            return
+        }
+        
+        // 加载 Activity 类
+        try {
+            @Suppress("UNCHECKED_CAST")
+            loginActivityClass = Class.forName(loginActivityClassName) as? Class<out FragmentActivity>
+            if (loginActivityClass == null) {
+                LogHelper.e("RegisterActivity", "登录页 Activity 类不是 FragmentActivity 的子类:$loginActivityClassName")
+                finish()
+                return
+            }
+            
+            @Suppress("UNCHECKED_CAST")
+            mainActivityClass = Class.forName(mainActivityClassName) as? Class<out FragmentActivity>
+            if (mainActivityClass == null) {
+                LogHelper.e("RegisterActivity", "主界面 Activity 类不是 FragmentActivity 的子类:$mainActivityClassName")
+                finish()
+                return
+            }
+        } catch (e: Exception) {
+            LogHelper.e("RegisterActivity", "加载 Activity 类失败", e)
+            finish()
+            return
+        }
+        
+        // 设置简单的布局,只包含 Fragment 容器
+        val container = android.widget.FrameLayout(this).apply {
+            id = android.R.id.content
+        }
+        setContentView(container)
+        
+        // 显示注册 Fragment
+        if (supportFragmentManager.findFragmentById(android.R.id.content) == null) {
+            supportFragmentManager.beginTransaction()
+                .replace(android.R.id.content, RegisterFragment())
+                .commit()
+        }
+    }
+    
+    /**
+     * 注册成功后的回调(公开方法,Fragment 可以调用)
+     */
+    fun handleRegisterSuccess() {
+        val targetClass = if (goToMainAfterRegister) {
+            mainActivityClass
+        } else {
+            loginActivityClass
+        }
+        
+        if (targetClass == null) {
+            LogHelper.e("RegisterActivity", "目标 Activity 类未设置")
+            return
+        }
+        
+        val intent = Intent(this, targetClass).apply {
+            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+        }
+        startActivity(intent)
+        finish()
+    }
+    
+    override fun onDestroy() {
+        super.onDestroy()
+        // 从 ActivityManager 中移除
+        ActivityManager.removeActivity(this)
+    }
+}

+ 26 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/local/AuthLocalDataSource.kt

@@ -0,0 +1,26 @@
+package com.narutohuo.xindazhou.common.auth.datasource.local
+
+import com.narutohuo.xindazhou.common.auth.model.LoginResponse
+
+/**
+ * 认证本地数据源接口
+ * 
+ * 注意:具体实现需要子类提供,因为 TokenManager 在业务模块中
+ */
+interface AuthLocalDataSource {
+    /**
+     * 保存Token
+     */
+    suspend fun saveToken(loginResponse: LoginResponse)
+    
+    /**
+     * 获取Access Token
+     */
+    suspend fun getToken(): String?
+    
+    /**
+     * 清除Token
+     */
+    suspend fun clearToken()
+}
+

+ 29 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/local/AuthLocalDataSourceImpl.kt

@@ -0,0 +1,29 @@
+package com.narutohuo.xindazhou.common.auth.datasource.local
+
+import com.narutohuo.xindazhou.common.auth.model.LoginResponse
+import com.narutohuo.xindazhou.common.auth.storage.TokenStore
+
+/**
+ * 认证本地数据源实现
+ * 
+ * 使用 TokenStore 进行 Token 的持久化存储
+ */
+class AuthLocalDataSourceImpl : AuthLocalDataSource {
+    
+    override suspend fun saveToken(loginResponse: LoginResponse) {
+        TokenStore.saveToken(
+            loginResponse.accessToken,
+            loginResponse.refreshToken,
+            loginResponse.userId
+        )
+    }
+    
+    override suspend fun getToken(): String? {
+        return TokenStore.getAccessToken()
+    }
+    
+    override suspend fun clearToken() {
+        TokenStore.clearToken()
+    }
+}
+

+ 37 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/remote/AuthApi.kt

@@ -0,0 +1,37 @@
+package com.narutohuo.xindazhou.common.auth.datasource.remote
+
+import com.narutohuo.xindazhou.common.auth.model.LoginRequest
+import com.narutohuo.xindazhou.common.auth.model.LoginResponse
+import com.narutohuo.xindazhou.common.auth.model.RegisterRequest
+import com.narutohuo.xindazhou.common.network.ApiCommonResult
+import retrofit2.Response
+import retrofit2.http.Body
+import retrofit2.http.POST
+import retrofit2.http.Query
+
+/**
+ * 认证相关API接口
+ */
+interface AuthApi {
+    
+    /**
+     * 用户登录
+     */
+    @POST("member/auth/login")
+    suspend fun login(@Body request: LoginRequest): Response<ApiCommonResult<LoginResponse>>
+    
+    /**
+     * 用户注册
+     */
+    @POST("member/auth/register")
+    suspend fun register(@Body request: RegisterRequest): Response<ApiCommonResult<LoginResponse>>
+    
+    /**
+     * 刷新 Token
+     * 
+     * 注意:服务端使用 @RequestParam,所以使用 @Query
+     */
+    @POST("member/auth/refresh-token")
+    suspend fun refreshToken(@Query("refreshToken") refreshToken: String): Response<ApiCommonResult<LoginResponse>>
+}
+

+ 64 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/remote/AuthRemoteDataSource.kt

@@ -0,0 +1,64 @@
+package com.narutohuo.xindazhou.common.auth.datasource.remote
+
+import com.narutohuo.xindazhou.common.auth.model.LoginRequest
+import com.narutohuo.xindazhou.common.auth.model.LoginResponse
+import com.narutohuo.xindazhou.common.auth.model.RegisterRequest
+import com.narutohuo.xindazhou.common.network.ApiBaseRemoteDataSource
+
+/**
+ * 认证远程数据源接口
+ */
+interface AuthRemoteDataSource {
+    /**
+     * 用户登录
+     */
+    suspend fun login(request: LoginRequest): Result<LoginResponse>
+    
+    /**
+     * 用户注册
+     */
+    suspend fun register(request: RegisterRequest): Result<LoginResponse>
+    
+    /**
+     * 刷新 Token
+     */
+    suspend fun refreshToken(refreshToken: String): Result<LoginResponse>
+}
+
+/**
+ * 认证远程数据源实现
+ * 
+ * 继承 ApiBaseRemoteDataSource,自动获得:
+ * - 统一的错误处理
+ * - 自动日志记录
+ * - 网络异常友好提示
+ * - 线程自动切换
+ */
+class AuthRemoteDataSourceImpl : ApiBaseRemoteDataSource(), AuthRemoteDataSource {
+    
+    private val authApi: AuthApi by lazy {
+        com.narutohuo.xindazhou.common.network.ApiServiceFactory.create<AuthApi>()
+    }
+    
+    override suspend fun login(request: LoginRequest): Result<LoginResponse> {
+        return executeRequest(
+            request = { authApi.login(request) },
+            errorMessage = "登录失败"
+        )
+    }
+    
+    override suspend fun register(request: RegisterRequest): Result<LoginResponse> {
+        return executeRequest(
+            request = { authApi.register(request) },
+            errorMessage = "注册失败"
+        )
+    }
+    
+    override suspend fun refreshToken(refreshToken: String): Result<LoginResponse> {
+        return executeRequest(
+            request = { authApi.refreshToken(refreshToken) },
+            errorMessage = "Token 刷新失败"
+        )
+    }
+}
+

+ 10 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/LoginRequest.kt

@@ -0,0 +1,10 @@
+package com.narutohuo.xindazhou.common.auth.model
+
+/**
+ * 登录请求参数
+ */
+data class LoginRequest(
+    val mobile: String,
+    val password: String
+)
+

+ 19 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/LoginResponse.kt

@@ -0,0 +1,19 @@
+package com.narutohuo.xindazhou.common.auth.model
+
+/**
+ * 登录响应数据
+ * 对应后端 AppAuthLoginRespVO
+ * 
+ * 注意:服务端返回的 expiresTime 是 LocalDateTime,Gson 会自动转换为时间戳(毫秒)
+ */
+data class LoginResponse(
+    val userId: Long,
+    val accessToken: String,
+    val refreshToken: String,
+    val expiresTime: Long? = null,  // 服务端返回 LocalDateTime,Gson 会自动转换为时间戳(毫秒)
+    val openid: String? = null,
+    // 以下字段为兼容字段(后端不返回,但前端可能需要)
+    val username: String? = null,
+    val mobile: String? = null
+)
+

+ 10 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/RegisterRequest.kt

@@ -0,0 +1,10 @@
+package com.narutohuo.xindazhou.common.auth.model
+
+/**
+ * 注册请求参数
+ */
+data class RegisterRequest(
+    val mobile: String,
+    val password: String
+)
+

+ 105 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/repository/AuthRepository.kt

@@ -0,0 +1,105 @@
+package com.narutohuo.xindazhou.common.auth.repository
+
+import com.narutohuo.xindazhou.common.auth.datasource.local.AuthLocalDataSource
+import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSource
+import com.narutohuo.xindazhou.common.auth.model.LoginRequest
+import com.narutohuo.xindazhou.common.auth.model.LoginResponse
+import com.narutohuo.xindazhou.common.auth.model.RegisterRequest
+import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.common.network.ApiBaseRepository
+
+/**
+ * 认证数据仓库
+ * 
+ * 负责统一管理认证相关的数据获取和缓存
+ * 
+ * 继承 ApiBaseRepository,自动获得:
+ * - 统一的错误处理
+ * - 日志记录
+ * - 数据转换扩展点
+ */
+class AuthRepository(
+    private val remoteDataSource: AuthRemoteDataSource,
+    private val localDataSource: AuthLocalDataSource
+) : ApiBaseRepository() {
+    
+    /**
+     * 用户登录
+     * 
+     * @param mobile 手机号
+     * @param password 密码
+     * @return 登录结果
+     */
+    suspend fun login(mobile: String, password: String): Result<LoginResponse> {
+        val request = LoginRequest(mobile, password)
+        
+        // 从远程数据源获取数据
+        val result = remoteDataSource.login(request)
+        
+        // 如果成功,保存到本地
+        result.onSuccess { response ->
+            localDataSource.saveToken(response)
+            LogHelper.d("AuthRepository", "login: Token已保存")
+        }
+        
+        // 如果失败,可以在这里添加额外的错误处理逻辑
+        result.onFailure { throwable ->
+            // 例如:记录日志、发送错误上报等
+            handleError(throwable)
+        }
+        
+        return result
+    }
+    
+    /**
+     * 用户注册
+     * 
+     * @param mobile 手机号
+     * @param password 密码
+     * @return 注册结果
+     */
+    suspend fun register(mobile: String, password: String): Result<LoginResponse> {
+        val request = RegisterRequest(mobile, password)
+        
+        // 从远程数据源获取数据
+        val result = remoteDataSource.register(request)
+        
+        // 如果成功,保存到本地
+        result.onSuccess { response ->
+            localDataSource.saveToken(response)
+            LogHelper.d("AuthRepository", "register: Token已保存")
+        }
+        
+        // 如果失败,可以在这里添加额外的错误处理逻辑
+        result.onFailure { throwable ->
+            handleError(throwable)
+        }
+        
+        return result
+    }
+    
+    /**
+     * 刷新 Token
+     * 
+     * @param refreshToken 刷新令牌
+     * @return 刷新结果(包含新的 accessToken 和 refreshToken)
+     */
+    suspend fun refreshToken(refreshToken: String): Result<LoginResponse> {
+        // 从远程数据源获取数据
+        val result = remoteDataSource.refreshToken(refreshToken)
+        
+        // 如果成功,保存到本地
+        result.onSuccess { response ->
+            localDataSource.saveToken(response)
+            LogHelper.d("AuthRepository", "refreshToken: Token已刷新并保存")
+        }
+        
+        // 如果失败,可以在这里添加额外的错误处理逻辑
+        result.onFailure { throwable ->
+            handleError(throwable)
+        }
+        
+        return result
+    }
+}
+

+ 155 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/storage/TokenStore.kt

@@ -0,0 +1,155 @@
+package com.narutohuo.xindazhou.common.auth.storage
+
+import android.content.Context
+import android.content.SharedPreferences
+
+/**
+ * Token 存储管理器
+ * 
+ * 负责 Token 的存储、读取和清除
+ * 独立模块,可在 base-common 中独立运行
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 Application 中初始化
+ * TokenStore.init(context)
+ * 
+ * // 保存 Token
+ * TokenStore.saveToken(accessToken, refreshToken, userId)
+ * 
+ * // 获取 Token
+ * val token = TokenStore.getAccessToken()
+ * 
+ * // 清除 Token
+ * TokenStore.clearToken()
+ * ```
+ */
+object TokenStore {
+    private const val PREFS_NAME = "xdz_auth_prefs"
+    private const val KEY_ACCESS_TOKEN = "access_token"
+    private const val KEY_REFRESH_TOKEN = "refresh_token"
+    private const val KEY_USER_ID = "user_id"
+    private const val KEY_SERVER_URL = "server_url"
+    private const val KEY_SOCKET_URL = "socket_url"
+    
+    private var prefs: SharedPreferences? = null
+    private var initialized = false
+    
+    /**
+     * 初始化 TokenStore
+     * 
+     * 需要在 Application.onCreate() 中调用
+     * 
+     * @param context 应用上下文
+     */
+    fun init(context: Context) {
+        prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+        initialized = true
+    }
+    
+    /**
+     * 检查是否已初始化
+     */
+    fun isInitialized(): Boolean = initialized
+    
+    /**
+     * 保存Token
+     * 
+     * @param accessToken 访问令牌
+     * @param refreshToken 刷新令牌
+     * @param userId 用户ID
+     */
+    fun saveToken(accessToken: String, refreshToken: String, userId: Long) {
+        prefs?.edit()?.apply {
+            putString(KEY_ACCESS_TOKEN, accessToken)
+            putString(KEY_REFRESH_TOKEN, refreshToken)
+            putLong(KEY_USER_ID, userId)
+            apply()
+        }
+    }
+    
+    /**
+     * 获取Access Token
+     * 
+     * @return Access Token,如果不存在返回 null
+     */
+    fun getAccessToken(): String? {
+        return prefs?.getString(KEY_ACCESS_TOKEN, null)
+    }
+    
+    /**
+     * 获取Refresh Token
+     * 
+     * @return Refresh Token,如果不存在返回 null
+     */
+    fun getRefreshToken(): String? {
+        return prefs?.getString(KEY_REFRESH_TOKEN, null)
+    }
+    
+    /**
+     * 获取用户ID
+     * 
+     * @return 用户ID,如果不存在返回 0
+     */
+    fun getUserId(): Long {
+        return prefs?.getLong(KEY_USER_ID, 0) ?: 0
+    }
+    
+    /**
+     * 判断是否已登录
+     * 
+     * @return true 表示已登录(有有效的 Access Token)
+     */
+    fun isLoggedIn(): Boolean {
+        return !getAccessToken().isNullOrEmpty()
+    }
+    
+    /**
+     * 清除Token(登出)
+     */
+    fun clearToken() {
+        prefs?.edit()?.apply {
+            remove(KEY_ACCESS_TOKEN)
+            remove(KEY_REFRESH_TOKEN)
+            remove(KEY_USER_ID)
+            apply()
+        }
+    }
+    
+    /**
+     * 保存服务器地址
+     * 
+     * @param url 服务器地址
+     */
+    fun saveServerUrl(url: String) {
+        prefs?.edit()?.putString(KEY_SERVER_URL, url)?.apply()
+    }
+    
+    /**
+     * 获取服务器地址
+     * 
+     * @return 服务器地址,如果不存在返回 null
+     */
+    fun getServerUrl(): String? {
+        return prefs?.getString(KEY_SERVER_URL, null)
+    }
+    
+    /**
+     * 保存Socket地址
+     * 
+     * @param url Socket地址
+     */
+    fun saveSocketUrl(url: String) {
+        prefs?.edit()?.putString(KEY_SOCKET_URL, url)?.apply()
+    }
+    
+    /**
+     * 获取Socket地址
+     * 
+     * @return Socket地址,如果不存在返回 null
+     */
+    fun getSocketUrl(): String? {
+        return prefs?.getString(KEY_SOCKET_URL, null)
+    }
+}
+

+ 100 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/ui/LoginFragment.kt

@@ -0,0 +1,100 @@
+package com.narutohuo.xindazhou.common.auth.ui
+
+import android.os.Bundle
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.textfield.TextInputEditText
+import com.narutohuo.xindazhou.common.auth.AuthManager
+import com.narutohuo.xindazhou.common.auth.LoginActivity
+import com.narutohuo.xindazhou.common.R
+import com.narutohuo.xindazhou.common.dialog.ServerConfigDialog
+import kotlinx.coroutines.launch
+import androidx.lifecycle.lifecycleScope
+
+/**
+ * 登录 Fragment(完整封装)
+ */
+class LoginFragment : Fragment() {
+    
+    private lateinit var etMobile: TextInputEditText
+    private lateinit var etPassword: TextInputEditText
+    private lateinit var btnLogin: MaterialButton
+    private lateinit var btnRegister: MaterialButton
+    private lateinit var progressBar: View
+    // 【测试功能】服务器配置按钮
+    private var ivServerConfig: View? = null
+    
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_login, container, false)
+    }
+    
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        
+        etMobile = view.findViewById(R.id.etMobile)
+        etPassword = view.findViewById(R.id.etPassword)
+        btnLogin = view.findViewById(R.id.btnLogin)
+        btnRegister = view.findViewById(R.id.btnRegister)
+        progressBar = view.findViewById(R.id.progressBar)
+        ivServerConfig = view.findViewById(R.id.ivServerConfig)
+        
+        // 【测试功能】服务器配置按钮
+        ivServerConfig?.setOnClickListener {
+            val dialog = ServerConfigDialog()
+            dialog.show(parentFragmentManager, "ServerConfigDialog")
+        }
+        
+        // 登录按钮
+        btnLogin.setOnClickListener {
+            val mobile = etMobile.text?.toString()?.trim() ?: ""
+            val password = etPassword.text?.toString() ?: ""
+            
+            if (TextUtils.isEmpty(mobile)) {
+                Toast.makeText(requireContext(), "请输入手机号", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            if (TextUtils.isEmpty(password)) {
+                Toast.makeText(requireContext(), "请输入密码", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            // 显示加载状态
+            progressBar.visibility = View.VISIBLE
+            btnLogin.isEnabled = false
+            
+            // 调用 AuthManager 登录
+            lifecycleScope.launch {
+                AuthManager.login(requireActivity(), mobile, password) { result ->
+                    progressBar.visibility = View.GONE
+                    btnLogin.isEnabled = true
+                    
+                    result.onSuccess {
+                        Toast.makeText(requireContext(), "登录成功", Toast.LENGTH_SHORT).show()
+                        // 通知 Activity 登录成功
+                        (activity as? LoginActivity)?.handleLoginSuccess()
+                    }.onFailure { error ->
+                        Toast.makeText(requireContext(), error.message ?: "登录失败", Toast.LENGTH_SHORT).show()
+                    }
+                }
+            }
+        }
+        
+        // 注册按钮
+        btnRegister.setOnClickListener {
+            // 跳转到注册页
+            val intent = android.content.Intent(requireContext(), com.narutohuo.xindazhou.common.auth.RegisterActivity::class.java)
+            startActivity(intent)
+        }
+    }
+}
+

+ 104 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/ui/RegisterFragment.kt

@@ -0,0 +1,104 @@
+package com.narutohuo.xindazhou.common.auth.ui
+
+import android.os.Bundle
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.button.MaterialButton
+import com.google.android.material.textfield.TextInputEditText
+import com.narutohuo.xindazhou.common.auth.AuthManager
+import com.narutohuo.xindazhou.common.auth.RegisterActivity
+import com.narutohuo.xindazhou.common.R
+import kotlinx.coroutines.launch
+
+/**
+ * 注册 Fragment(完整封装)
+ */
+class RegisterFragment : Fragment() {
+    
+    private lateinit var etMobile: TextInputEditText
+    private lateinit var etPassword: TextInputEditText
+    private lateinit var etConfirmPassword: TextInputEditText
+    private lateinit var btnRegister: MaterialButton
+    private lateinit var btnBackToLogin: MaterialButton
+    private lateinit var progressBar: View
+    
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        return inflater.inflate(R.layout.fragment_register, container, false)
+    }
+    
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        
+        etMobile = view.findViewById(R.id.etMobile)
+        etPassword = view.findViewById(R.id.etPassword)
+        etConfirmPassword = view.findViewById(R.id.etConfirmPassword)
+        btnRegister = view.findViewById(R.id.btnRegister)
+        btnBackToLogin = view.findViewById(R.id.btnBackToLogin)
+        progressBar = view.findViewById(R.id.progressBar)
+        
+        // 注册按钮
+        btnRegister.setOnClickListener {
+            val mobile = etMobile.text?.toString()?.trim() ?: ""
+            val password = etPassword.text?.toString() ?: ""
+            val confirmPassword = etConfirmPassword.text?.toString() ?: ""
+            
+            if (TextUtils.isEmpty(mobile)) {
+                Toast.makeText(requireContext(), "请输入手机号", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            if (TextUtils.isEmpty(password)) {
+                Toast.makeText(requireContext(), "请输入密码", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            if (password.length < 6 || password.length > 16) {
+                Toast.makeText(requireContext(), "密码长度为6-16位", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            if (password != confirmPassword) {
+                Toast.makeText(requireContext(), "两次输入的密码不一致", Toast.LENGTH_SHORT).show()
+                return@setOnClickListener
+            }
+            
+            // 显示加载状态
+            progressBar.visibility = View.VISIBLE
+            btnRegister.isEnabled = false
+            
+            // 调用 AuthManager 注册
+            lifecycleScope.launch {
+                AuthManager.register(requireActivity(), mobile, password) { result ->
+                    progressBar.visibility = View.GONE
+                    btnRegister.isEnabled = true
+                    
+                    result.onSuccess {
+                        Toast.makeText(requireContext(), "注册成功", Toast.LENGTH_SHORT).show()
+                        // 通知 Activity 注册成功
+                        (activity as? RegisterActivity)?.handleRegisterSuccess()
+                    }.onFailure { error ->
+                        Toast.makeText(requireContext(), error.message ?: "注册失败", Toast.LENGTH_SHORT).show()
+                    }
+                }
+            }
+        }
+        
+        // 返回登录按钮
+        btnBackToLogin.setOnClickListener {
+            // 跳转到登录页
+            val intent = android.content.Intent(requireContext(), com.narutohuo.xindazhou.common.auth.LoginActivity::class.java)
+            startActivity(intent)
+            activity?.finish()
+        }
+    }
+}
+

+ 136 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/bridge/BridgeManager.kt

@@ -0,0 +1,136 @@
+package com.narutohuo.xindazhou.common.bridge
+
+import com.narutohuo.xindazhou.core.log.ILog
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.CopyOnWriteArrayList
+
+/**
+ * 桥接服务管理器
+ * 
+ * 业务层封装,提供便捷的桥接服务调用方式
+ * 直接实现 H5 通信和模块间通信功能
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // H5通信
+ * BridgeManager.callH5Method("showToast", "Hello")
+ * BridgeManager.onH5Call("getUserInfo") { result ->
+ *     // 处理结果
+ * }
+ * 
+ * // 模块间通信
+ * BridgeManager.postEvent("user_login", userData)
+ * BridgeManager.subscribeEvent("user_logout") { data ->
+ *     // 处理事件
+ * }
+ * ```
+ */
+object BridgeManager {
+    
+    private const val TAG = "BridgeManager"
+    
+    // H5 方法回调存储
+    private val h5Callbacks = ConcurrentHashMap<String, (String) -> Unit>()
+    
+    // 事件订阅者存储
+    private val eventSubscribers = ConcurrentHashMap<String, CopyOnWriteArrayList<(Any?) -> Unit>>()
+    
+    // H5 调用原生方法的回调(由外部设置,比如 WebView 的 JavaScriptInterface)
+    private var h5CallNativeCallback: ((String, String) -> Unit)? = null
+    
+    /**
+     * 设置 H5 调用原生方法的回调
+     * 在 WebView 的 JavaScriptInterface 中调用此方法注册回调
+     * 
+     * @param callback 回调函数(method: 方法名, params: 参数)
+     */
+    fun setH5CallNativeCallback(callback: (String, String) -> Unit) {
+        h5CallNativeCallback = callback
+    }
+    
+    /**
+     * 调用 H5 方法
+     * 需要在 WebView 的 JavaScriptInterface 中实现对应的方法
+     * 
+     * @param method 方法名
+     * @param params 参数(JSON 字符串)
+     */
+    fun callH5Method(method: String, params: String = "{}") {
+        // 这里需要由外部实现(比如在 WebView 的 JavaScriptInterface 中)
+        // 可以通过回调或者事件通知外部执行
+        ILog.d(TAG, "callH5Method: $method, params: $params")
+        // 实际实现需要在 WebView 的 JavaScriptInterface 中调用
+        // 可以通过 postEvent 通知,或者设置回调
+    }
+    
+    /**
+     * 监听 H5 调用原生方法
+     * 
+     * @param method 方法名
+     * @param callback 回调函数
+     */
+    fun onH5Call(method: String, callback: (String) -> Unit) {
+        h5Callbacks[method] = callback
+    }
+    
+    /**
+     * 处理 H5 调用原生方法(由 WebView 的 JavaScriptInterface 调用)
+     * 
+     * @param method 方法名
+     * @param params 参数(JSON 字符串)
+     */
+    fun handleH5Call(method: String, params: String) {
+        h5Callbacks[method]?.invoke(params) ?: run {
+            ILog.w(TAG, "未注册的 H5 方法调用: $method")
+        }
+    }
+    
+    /**
+     * 发送事件(模块间通信)
+     * 
+     * @param event 事件名
+     * @param data 事件数据
+     */
+    fun postEvent(event: String, data: Any? = null) {
+        val subscribers = eventSubscribers[event]
+        if (subscribers != null && subscribers.isNotEmpty()) {
+            subscribers.forEach { callback ->
+                try {
+                    callback(data)
+                } catch (e: Exception) {
+                    ILog.e(TAG, "事件回调执行失败: $event", e)
+                }
+            }
+        } else {
+            ILog.d(TAG, "事件无订阅者: $event")
+        }
+    }
+    
+    /**
+     * 订阅事件(模块间通信)
+     * 
+     * @param event 事件名
+     * @param callback 回调函数
+     */
+    fun subscribeEvent(event: String, callback: (Any?) -> Unit) {
+        eventSubscribers.getOrPut(event) { CopyOnWriteArrayList() }.add(callback)
+    }
+    
+    /**
+     * 取消订阅事件
+     * 
+     * @param event 事件名
+     * @param callback 回调函数
+     */
+    fun unsubscribeEvent(event: String, callback: (Any?) -> Unit) {
+        eventSubscribers[event]?.remove(callback)
+    }
+    
+    /**
+     * 清除所有事件订阅
+     */
+    fun clearAllSubscriptions() {
+        eventSubscribers.clear()
+    }
+}
+

+ 272 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/camera/CameraHelper.kt

@@ -0,0 +1,272 @@
+package com.narutohuo.xindazhou.common.camera
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.os.Build
+import android.provider.MediaStore
+import androidx.activity.ComponentActivity
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.content.FileProvider
+import androidx.fragment.app.Fragment
+import java.io.File
+
+/**
+ * 相机/相册管理器
+ * 
+ * 统一封装相机拍照、相册选择、图片裁剪功能
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 注册拍照
+ * private val takePhotoLauncher = CameraHelper.registerTakePhoto(this) { uri ->
+ *     // 处理拍照结果
+ * }
+ * takePhotoLauncher.launch()
+ * 
+ * // 注册相册选择
+ * private val pickGalleryLauncher = CameraHelper.registerPickGallery(this) { uri ->
+ *     // 处理相册选择结果
+ * }
+ * pickGalleryLauncher.launch()
+ * 
+ * // 注册图片裁剪
+ * private val cropImageLauncher = CameraHelper.registerCropImage(this) { uri ->
+ *     // 处理裁剪结果
+ * }
+ * cropImageLauncher.launch(sourceUri)
+ * ```
+ */
+object CameraHelper {
+    
+    /**
+     * 临时文件目录(用于存储拍照和裁剪的临时文件)
+     * 需要在 Application 中设置
+     */
+    var tempFileDir: File? = null
+    
+    /**
+     * FileProvider Authority(用于 Android 7.0+ 文件共享)
+     * 需要在 Application 中设置,格式:${applicationId}.fileprovider
+     */
+    var fileProviderAuthority: String? = null
+    
+    /**
+     * 注册拍照功能(Activity)
+     * 
+     * @param activity ComponentActivity 实例
+     * @param onResult 拍照结果回调
+     * @return ActivityResultLauncher,调用 launch(uri) 启动相机(需要先创建临时文件并获取 URI)
+     */
+    fun registerTakePhoto(
+        activity: ComponentActivity,
+        onResult: (Uri?) -> Unit
+    ): ActivityResultLauncher<Uri> {
+        return activity.registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
+            if (success) {
+                // 注意:TakePicture 会自动保存到传入的 URI,这里需要调用者传入 URI
+                // 实际使用时应该在调用处创建临时文件并传入 URI
+            } else {
+                onResult(null)
+            }
+        }
+    }
+    
+    /**
+     * 注册拍照功能(Fragment)
+     * 
+     * @param fragment Fragment 实例
+     * @param onResult 拍照结果回调
+     * @return ActivityResultLauncher
+     */
+    fun registerTakePhoto(
+        fragment: Fragment,
+        onResult: (Uri?) -> Unit
+    ): ActivityResultLauncher<Uri> {
+        return fragment.registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
+            if (success) {
+                val tempFile = createTempImageFile(fragment.requireContext())
+                val uri = getUriForFile(fragment.requireContext(), tempFile)
+                onResult(uri)
+            } else {
+                onResult(null)
+            }
+        }
+    }
+    
+    /**
+     * 创建临时文件并启动拍照
+     * 
+     * @param launcher ActivityResultLauncher(通过 registerTakePhoto 获取)
+     * @param context 上下文
+     * @param onResult 拍照结果回调(返回图片 URI)
+     */
+    fun takePhoto(
+        launcher: ActivityResultLauncher<Uri>,
+        context: android.content.Context,
+        onResult: (Uri?) -> Unit
+    ) {
+        val tempFile = createTempImageFile(context)
+        val uri = getUriForFile(context, tempFile)
+        launcher.launch(uri)
+        // 注意:TakePicture 会自动保存到传入的 URI,所以这里需要保存 URI 以便后续使用
+        // 实际使用时应该在调用处保存 URI 并在回调中处理
+    }
+    
+    /**
+     * 注册相册选择功能(Activity)
+     * 
+     * @param activity ComponentActivity 实例
+     * @param onResult 选择结果回调
+     * @return ActivityResultLauncher
+     */
+    fun registerPickGallery(
+        activity: ComponentActivity,
+        onResult: (Uri?) -> Unit
+    ): ActivityResultLauncher<Intent> {
+        return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+            if (result.resultCode == Activity.RESULT_OK) {
+                val uri = result.data?.data
+                onResult(uri)
+            } else {
+                onResult(null)
+            }
+        }
+    }
+    
+    /**
+     * 注册相册选择功能(Fragment)
+     * 
+     * @param fragment Fragment 实例
+     * @param onResult 选择结果回调
+     * @return ActivityResultLauncher
+     */
+    fun registerPickGallery(
+        fragment: Fragment,
+        onResult: (Uri?) -> Unit
+    ): ActivityResultLauncher<Intent> {
+        return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+            if (result.resultCode == Activity.RESULT_OK) {
+                val uri = result.data?.data
+                onResult(uri)
+            } else {
+                onResult(null)
+            }
+        }
+    }
+    
+    /**
+     * 启动相册选择
+     * 
+     * @param launcher ActivityResultLauncher(通过 registerPickGallery 获取)
+     */
+    fun pickFromGallery(launcher: ActivityResultLauncher<Intent>) {
+        val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+        launcher.launch(intent)
+    }
+    
+    /**
+     * 注册图片裁剪功能(Activity)
+     * 
+     * @param activity ComponentActivity 实例
+     * @param onResult 裁剪结果回调
+     * @return ActivityResultLauncher,调用 launch(intent) 启动裁剪
+     */
+    fun registerCropImage(
+        activity: ComponentActivity,
+        onResult: (Uri?) -> Unit
+    ): ActivityResultLauncher<Intent> {
+        return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+            if (result.resultCode == Activity.RESULT_OK) {
+                val uri = result.data?.data
+                onResult(uri)
+            } else {
+                onResult(null)
+            }
+        }
+    }
+    
+    /**
+     * 注册图片裁剪功能(Fragment)
+     * 
+     * @param fragment Fragment 实例
+     * @param onResult 裁剪结果回调
+     * @return ActivityResultLauncher
+     */
+    fun registerCropImage(
+        fragment: Fragment,
+        onResult: (Uri?) -> Unit
+    ): ActivityResultLauncher<Uri> {
+        return fragment.registerForActivityResult(ActivityResultContracts.TakePicture()) { success ->
+            if (success) {
+                val tempFile = createTempImageFile(fragment.requireContext())
+                val uri = getUriForFile(fragment.requireContext(), tempFile)
+                onResult(uri)
+            } else {
+                onResult(null)
+            }
+        }
+    }
+    
+    /**
+     * 启动图片裁剪
+     * 
+     * @param launcher ActivityResultLauncher(通过 registerCropImage 获取)
+     * @param context 上下文
+     * @param sourceUri 源图片 URI
+     * @param outputUri 输出图片 URI(可选,如果为 null 则使用临时文件)
+     */
+    fun cropImage(
+        launcher: ActivityResultLauncher<Intent>,
+        context: android.content.Context,
+        sourceUri: Uri,
+        outputUri: Uri? = null
+    ) {
+        val finalOutputUri = outputUri ?: run {
+            val tempFile = createTempImageFile(context)
+            getUriForFile(context, tempFile)
+        }
+        
+        val intent = Intent("com.android.camera.action.CROP").apply {
+            setDataAndType(sourceUri, "image/*")
+            putExtra("crop", "true")
+            putExtra("aspectX", 1)
+            putExtra("aspectY", 1)
+            putExtra("outputX", 800)
+            putExtra("outputY", 800)
+            putExtra("scale", true)
+            putExtra("return-data", false)
+            putExtra(MediaStore.EXTRA_OUTPUT, finalOutputUri)
+            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+            addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
+        }
+        
+        launcher.launch(intent)
+    }
+    
+    // ========== 内部辅助方法 ==========
+    
+    /**
+     * 创建临时图片文件
+     */
+    private fun createTempImageFile(context: android.content.Context): File {
+        val dir = tempFileDir ?: context.cacheDir
+        val fileName = "temp_image_${System.currentTimeMillis()}.jpg"
+        return File(dir, fileName)
+    }
+    
+    /**
+     * 获取文件的 URI(兼容 Android 7.0+)
+     */
+    private fun getUriForFile(context: android.content.Context, file: File): Uri {
+        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            val authority = fileProviderAuthority
+                ?: throw IllegalStateException("fileProviderAuthority 未设置,请在 Application 中设置")
+            FileProvider.getUriForFile(context, authority, file)
+        } else {
+            Uri.fromFile(file)
+        }
+    }
+}
+

+ 176 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/config/ConfigManager.kt

@@ -0,0 +1,176 @@
+package com.narutohuo.xindazhou.common.config
+
+import android.content.Context
+import android.content.SharedPreferences
+import com.narutohuo.xindazhou.core.log.ILog
+
+/**
+ * 配置管理器
+ * 
+ * 业务层封装,提供便捷的配置管理方式
+ * 直接使用 SharedPreferences 实现配置存储
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 初始化(在 Application 中)
+ * ConfigManager.init(applicationContext)
+ * 
+ * // 获取配置
+ * val apiUrl = ConfigManager.getString("api_url", "https://api.example.com")
+ * val isDebug = ConfigManager.getBoolean("is_debug", false)
+ * 
+ * // 设置配置
+ * ConfigManager.setString("api_url", "https://api.example.com")
+ * ConfigManager.setBoolean("is_debug", true)
+ * 
+ * // 环境切换
+ * val env = ConfigManager.getEnvironment()  // dev, test, prod
+ * ConfigManager.setEnvironment("prod")
+ * 
+ * // 功能开关
+ * val enabled = ConfigManager.isFeatureEnabled("new_feature")
+ * ConfigManager.setFeatureEnabled("new_feature", true)
+ * ```
+ */
+object ConfigManager {
+    
+    private const val TAG = "ConfigManager"
+    private const val PREFS_NAME = "app_config"
+    private const val KEY_ENVIRONMENT = "environment"
+    private const val KEY_FEATURE_PREFIX = "feature_"
+    
+    private var sharedPreferences: SharedPreferences? = null
+    
+    /**
+     * 初始化配置管理器
+     * 需要在 Application 中调用
+     * 
+     * @param context 上下文
+     */
+    fun init(context: Context) {
+        sharedPreferences = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+    }
+    
+    private fun getPrefs(): SharedPreferences {
+        return sharedPreferences ?: throw IllegalStateException(
+            "ConfigManager 未初始化,请在 Application 中调用 ConfigManager.init(context)"
+        )
+    }
+    
+    // ========== 基础配置操作 ==========
+    
+    // ========== 基础配置操作 ==========
+    
+    /**
+     * 获取字符串配置
+     * 
+     * @param key 配置键
+     * @param defaultValue 默认值
+     * @return 配置值
+     */
+    fun getString(key: String, defaultValue: String = ""): String {
+        return getPrefs().getString(key, defaultValue) ?: defaultValue
+    }
+    
+    /**
+     * 获取整数配置
+     * 
+     * @param key 配置键
+     * @param defaultValue 默认值
+     * @return 配置值
+     */
+    fun getInt(key: String, defaultValue: Int = 0): Int {
+        return getPrefs().getInt(key, defaultValue)
+    }
+    
+    /**
+     * 获取布尔配置
+     * 
+     * @param key 配置键
+     * @param defaultValue 默认值
+     * @return 配置值
+     */
+    fun getBoolean(key: String, defaultValue: Boolean = false): Boolean {
+        return getPrefs().getBoolean(key, defaultValue)
+    }
+    
+    /**
+     * 设置字符串配置
+     * 
+     * @param key 配置键
+     * @param value 配置值
+     */
+    fun setString(key: String, value: String) {
+        getPrefs().edit().putString(key, value).apply()
+    }
+    
+    /**
+     * 设置整数配置
+     * 
+     * @param key 配置键
+     * @param value 配置值
+     */
+    fun setInt(key: String, value: Int) {
+        getPrefs().edit().putInt(key, value).apply()
+    }
+    
+    /**
+     * 设置布尔配置
+     * 
+     * @param key 配置键
+     * @param value 配置值
+     */
+    fun setBoolean(key: String, value: Boolean) {
+        getPrefs().edit().putBoolean(key, value).apply()
+    }
+    
+    // ========== 环境切换 ==========
+    
+    /**
+     * 获取当前环境
+     * 
+     * @return 环境名称(dev, test, prod)
+     */
+    fun getEnvironment(): String {
+        return getString(KEY_ENVIRONMENT, "dev")
+    }
+    
+    /**
+     * 设置环境
+     * 
+     * @param env 环境名称(dev, test, prod)
+     */
+    fun setEnvironment(env: String) {
+        setString(KEY_ENVIRONMENT, env)
+    }
+    
+    // ========== 功能开关 ==========
+    
+    /**
+     * 检查功能是否启用
+     * 
+     * @param feature 功能名称
+     * @return true 表示功能已启用
+     */
+    fun isFeatureEnabled(feature: String): Boolean {
+        return getBoolean("$KEY_FEATURE_PREFIX$feature", false)
+    }
+    
+    /**
+     * 设置功能开关
+     * 
+     * @param feature 功能名称
+     * @param enabled 是否启用
+     */
+    fun setFeatureEnabled(feature: String, enabled: Boolean) {
+        setBoolean("$KEY_FEATURE_PREFIX$feature", enabled)
+    }
+    
+    /**
+     * 清除所有配置
+     */
+    fun clearAll() {
+        getPrefs().edit().clear().apply()
+    }
+}
+

+ 205 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/crash/CrashHelper.kt

@@ -0,0 +1,205 @@
+package com.narutohuo.xindazhou.common.crash
+
+import android.app.Application
+import com.narutohuo.xindazhou.core.log.ILog
+import java.io.File
+import java.io.FileWriter
+import java.io.PrintWriter
+import java.text.SimpleDateFormat
+import java.util.*
+
+/**
+ * 崩溃收集管理器
+ * 
+ * 统一封装崩溃收集功能,自动捕获未处理的异常并记录
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // Application 中初始化
+ * CrashHelper.init(this) { throwable ->
+ *     // 自定义崩溃处理(如上报到服务器)
+ *     // 上报崩溃日志到服务器
+ * }
+ * 
+ * // 手动记录异常
+ * CrashHelper.logException(exception)
+ * ```
+ */
+object CrashHelper {
+    
+    private const val TAG = "CrashHelper"
+    private var application: Application? = null
+    private var crashCallback: ((Throwable) -> Unit)? = null
+    private var crashLogDir: File? = null
+    private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
+    
+    /**
+     * 初始化崩溃收集
+     * 
+     * @param application Application 实例
+     * @param crashLogDir 崩溃日志保存目录(可选,如果为 null 则使用应用缓存目录)
+     * @param crashCallback 崩溃回调(可选,用于上报崩溃日志)
+     */
+    fun init(
+        application: Application,
+        crashLogDir: File? = null,
+        crashCallback: ((Throwable) -> Unit)? = null
+    ) {
+        this.application = application
+        this.crashLogDir = crashLogDir ?: application.cacheDir
+        this.crashCallback = crashCallback
+        
+        // 设置全局异常处理器
+        Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
+            handleCrash(thread, throwable)
+        }
+        
+        ILog.d(TAG, "崩溃收集已初始化,日志目录: ${this.crashLogDir?.absolutePath}")
+    }
+    
+    /**
+     * 处理崩溃
+     */
+    private fun handleCrash(thread: Thread, throwable: Throwable) {
+        try {
+            // 记录崩溃日志
+            val crashLog = generateCrashLog(thread, throwable)
+            saveCrashLog(crashLog)
+            
+            // 调用回调
+            crashCallback?.invoke(throwable)
+            
+            // 记录日志
+            ILog.e(TAG, "应用崩溃", throwable)
+            ILog.e(TAG, "崩溃日志已保存")
+        } catch (e: Exception) {
+            ILog.e(TAG, "处理崩溃时发生异常", e)
+        } finally {
+            // 调用系统默认处理器(可选,如果需要显示系统崩溃对话框)
+            // 如果需要自定义崩溃界面,可以不调用系统默认处理器
+            val defaultHandler = Thread.getDefaultUncaughtExceptionHandler()
+            if (defaultHandler !is Thread.UncaughtExceptionHandler || 
+                defaultHandler.javaClass.name.contains("CrashHelper")) {
+                // 如果已经是我们的处理器,不再调用系统默认处理器
+            } else {
+                // 调用系统默认处理器(会显示系统崩溃对话框)
+                // defaultHandler.uncaughtException(thread, throwable)
+            }
+        }
+    }
+    
+    /**
+     * 生成崩溃日志
+     */
+    private fun generateCrashLog(thread: Thread, throwable: Throwable): String {
+        val sb = StringBuilder()
+        sb.append("========== 崩溃信息 ==========\n")
+        sb.append("时间: ${dateFormat.format(Date())}\n")
+        sb.append("线程: ${thread.name}\n")
+        sb.append("异常类型: ${throwable.javaClass.name}\n")
+        sb.append("异常消息: ${throwable.message}\n")
+        sb.append("\n")
+        sb.append("========== 堆栈信息 ==========\n")
+        
+        val sw = java.io.StringWriter()
+        val pw = PrintWriter(sw)
+        throwable.printStackTrace(pw)
+        sb.append(sw.toString())
+        
+        sb.append("\n")
+        sb.append("========== 设备信息 ==========\n")
+        application?.let { app ->
+            sb.append("应用版本: ${getAppVersion(app)}\n")
+            sb.append("Android版本: ${android.os.Build.VERSION.RELEASE}\n")
+            sb.append("设备型号: ${android.os.Build.MODEL}\n")
+            sb.append("设备厂商: ${android.os.Build.MANUFACTURER}\n")
+            sb.append("SDK版本: ${android.os.Build.VERSION.SDK_INT}\n")
+        }
+        
+        sb.append("============================\n")
+        
+        return sb.toString()
+    }
+    
+    /**
+     * 保存崩溃日志到文件
+     */
+    private fun saveCrashLog(crashLog: String) {
+        try {
+            val logDir = crashLogDir ?: return
+            if (!logDir.exists()) {
+                logDir.mkdirs()
+            }
+            
+            val fileName = "crash_${System.currentTimeMillis()}.log"
+            val logFile = File(logDir, fileName)
+            
+            FileWriter(logFile).use { writer ->
+                writer.write(crashLog)
+                writer.flush()
+            }
+            
+            ILog.d(TAG, "崩溃日志已保存: ${logFile.absolutePath}")
+        } catch (e: Exception) {
+            ILog.e(TAG, "保存崩溃日志失败", e)
+        }
+    }
+    
+    /**
+     * 手动记录异常(用于捕获的异常)
+     * 
+     * @param throwable 异常对象
+     */
+    fun logException(throwable: Throwable) {
+        try {
+            val crashLog = generateCrashLog(Thread.currentThread(), throwable)
+            saveCrashLog(crashLog)
+            crashCallback?.invoke(throwable)
+            ILog.e(TAG, "异常已记录", throwable)
+        } catch (e: Exception) {
+            ILog.e(TAG, "记录异常时发生错误", e)
+        }
+    }
+    
+    /**
+     * 获取应用版本信息
+     */
+    private fun getAppVersion(application: Application): String {
+        return try {
+            val packageInfo = application.packageManager.getPackageInfo(application.packageName, 0)
+            "${packageInfo.versionName} (${packageInfo.versionCode})"
+        } catch (e: Exception) {
+            "未知"
+        }
+    }
+    
+    /**
+     * 获取所有崩溃日志文件
+     * 
+     * @return 崩溃日志文件列表
+     */
+    fun getAllCrashLogs(): List<File> {
+        val logDir = crashLogDir ?: return emptyList()
+        if (!logDir.exists()) {
+            return emptyList()
+        }
+        
+        return logDir.listFiles { _, name ->
+            name.startsWith("crash_") && name.endsWith(".log")
+        }?.toList() ?: emptyList()
+    }
+    
+    /**
+     * 清除所有崩溃日志
+     */
+    fun clearCrashLogs() {
+        val logDir = crashLogDir ?: return
+        if (!logDir.exists()) {
+            return
+        }
+        
+        getAllCrashLogs().forEach { it.delete() }
+        ILog.d(TAG, "所有崩溃日志已清除")
+    }
+}
+

+ 371 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/dialog/CascadePickerDialog.kt

@@ -0,0 +1,371 @@
+package com.narutohuo.xindazhou.common.dialog
+
+import android.app.Activity
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+
+/**
+ * 级联选择器对话框
+ * 
+ * 支持多级联动选择,类似 iOS 的滚轮选择器
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 二级联动
+ * val data = listOf(
+ *     CascadeData("北京", listOf("东城区", "西城区", "朝阳区")),
+ *     CascadeData("上海", listOf("黄浦区", "徐汇区", "长宁区"))
+ * )
+ * CascadePickerDialog.show(context, data) { selected ->
+ *     // selected 是选中的路径,如 ["北京", "朝阳区"]
+ * }
+ * ```
+ */
+object CascadePickerDialog {
+    
+    /**
+     * 级联数据模型
+     */
+    data class CascadeData(
+        val label: String,
+        val children: List<CascadeData>? = null,
+        val value: Any? = null  // 可选的值对象
+    )
+    
+    /**
+     * 显示级联选择器
+     * 
+     * @param context 上下文
+     * @param data 级联数据列表
+     * @param title 标题(可选)
+     * @param onConfirm 确认回调,返回选中的路径数组
+     * @param onCancel 取消回调(可选)
+     */
+    fun show(
+        context: Context,
+        data: List<CascadeData>,
+        title: String? = null,
+        onConfirm: ((List<String>) -> Unit)? = null,
+        onCancel: (() -> Unit)? = null
+    ): Dialog {
+        return createDialog(context, data, title, onConfirm, onCancel)
+    }
+    
+    /**
+     * 显示级联选择器(Activity 扩展函数)
+     */
+    fun Activity.showCascadePicker(
+        data: List<CascadeData>,
+        title: String? = null,
+        onConfirm: ((List<String>) -> Unit)? = null,
+        onCancel: (() -> Unit)? = null
+    ): Dialog {
+        return show(this, data, title, onConfirm, onCancel)
+    }
+    
+    /**
+     * 显示级联选择器(Fragment 扩展函数)
+     */
+    fun Fragment.showCascadePicker(
+        data: List<CascadeData>,
+        title: String? = null,
+        onConfirm: ((List<String>) -> Unit)? = null,
+        onCancel: (() -> Unit)? = null
+    ): Dialog {
+        return show(requireContext(), data, title, onConfirm, onCancel)
+    }
+    
+    /**
+     * 创建级联选择器对话框
+     */
+    private fun createDialog(
+        context: Context,
+        data: List<CascadeData>,
+        title: String?,
+        onConfirm: ((List<String>) -> Unit)?,
+        onCancel: (() -> Unit)?
+    ): Dialog {
+        val dialog = Dialog(context)
+        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
+        dialog.window?.apply {
+            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+            setGravity(Gravity.BOTTOM)
+        }
+        
+        // 创建主容器
+        val container = LinearLayout(context).apply {
+            orientation = LinearLayout.VERTICAL
+            setBackgroundColor(Color.WHITE)
+            layoutParams = LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT
+            )
+        }
+        
+        // 标题栏
+        val titleBar = LinearLayout(context).apply {
+            orientation = LinearLayout.HORIZONTAL
+            setPadding(0, dpToPx(context, 16f), 0, dpToPx(context, 16f))
+            gravity = Gravity.CENTER_VERTICAL
+        }
+        
+        // 取消按钮
+        val cancelButton = TextView(context).apply {
+            text = "取消"
+            textSize = 16f
+            setTextColor(Color.parseColor("#007AFF"))
+            setPadding(dpToPx(context, 16f), 0, dpToPx(context, 16f), 0)
+            setOnClickListener {
+                dialog.dismiss()
+                onCancel?.invoke()
+            }
+        }
+        titleBar.addView(cancelButton)
+        
+        // 标题
+        if (!title.isNullOrEmpty()) {
+            val titleView = TextView(context).apply {
+                this.text = title
+                textSize = 17f
+                setTextColor(Color.parseColor("#000000"))
+                setTypeface(null, android.graphics.Typeface.BOLD)
+                layoutParams = LinearLayout.LayoutParams(
+                    0,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    1f
+                )
+                gravity = Gravity.CENTER
+            }
+            titleBar.addView(titleView)
+        } else {
+            // 占位
+            val placeholder = View(context).apply {
+                layoutParams = LinearLayout.LayoutParams(
+                    0,
+                    ViewGroup.LayoutParams.WRAP_CONTENT,
+                    1f
+                )
+            }
+            titleBar.addView(placeholder)
+        }
+        
+        // 级联选择器视图(先创建,以便在确定按钮中使用)
+        val pickerView = CascadePickerView(context, data)
+        
+        // 确定按钮
+        val confirmButton = TextView(context).apply {
+            text = "确定"
+            textSize = 16f
+            setTextColor(Color.parseColor("#007AFF"))
+            setPadding(dpToPx(context, 16f), 0, dpToPx(context, 16f), 0)
+            setOnClickListener {
+                // 获取选中的路径
+                val selected = pickerView.getSelectedPath()
+                dialog.dismiss()
+                onConfirm?.invoke(selected)
+            }
+        }
+        titleBar.addView(confirmButton)
+        
+        container.addView(titleBar)
+        
+        // 分隔线
+        val divider = View(context).apply {
+            layoutParams = LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                dpToPx(context, 1f)
+            )
+            setBackgroundColor(Color.parseColor("#E5E5EA"))
+        }
+        container.addView(divider)
+        container.addView(pickerView, LinearLayout.LayoutParams(
+            ViewGroup.LayoutParams.MATCH_PARENT,
+            dpToPx(context, 200f)
+        ))
+        
+        dialog.setContentView(container)
+        dialog.setCancelable(true)
+        dialog.setCanceledOnTouchOutside(true)
+        dialog.show()
+        
+        return dialog
+    }
+    
+    /**
+     * 级联选择器视图
+     */
+    private class CascadePickerView(
+        context: Context,
+        private val data: List<CascadeData>
+    ) : LinearLayout(context) {
+        
+        private val recyclerViews = mutableListOf<RecyclerView>()
+        private val adapters = mutableListOf<CascadeAdapter>()
+        private var currentLevel = 0
+        private val selectedPath = mutableListOf<String>()
+        
+        init {
+            orientation = LinearLayout.HORIZONTAL
+            setupLevel(0, data)
+        }
+        
+        /**
+         * 设置选择级别
+         */
+        private fun setupLevel(level: Int, items: List<CascadeData>) {
+            // 移除多余的级别
+            while (recyclerViews.size > level + 1) {
+                removeView(recyclerViews.removeAt(recyclerViews.size - 1))
+                adapters.removeAt(adapters.size - 1)
+            }
+            
+            // 如果当前级别已存在,更新数据
+            if (level < recyclerViews.size) {
+                adapters[level].updateData(items)
+            } else {
+                // 创建新的级别
+                val recyclerView = RecyclerView(context).apply {
+                    layoutManager = LinearLayoutManager(context)
+                    adapter = CascadeAdapter(items) { item ->
+                        onItemSelected(level, item)
+                    }
+                }
+                
+                // 添加分隔线(除了第一级)
+                if (level > 0) {
+                    val divider = View(context).apply {
+                        layoutParams = LayoutParams(
+                            dpToPx(context, 1f),
+                            LayoutParams.MATCH_PARENT
+                        )
+                        setBackgroundColor(Color.parseColor("#E5E5EA"))
+                    }
+                    addView(divider)
+                }
+                
+                addView(recyclerView, LayoutParams(
+                    0,
+                    LayoutParams.MATCH_PARENT,
+                    1f
+                ))
+                
+                recyclerViews.add(recyclerView)
+                adapters.add(recyclerView.adapter as CascadeAdapter)
+            }
+        }
+        
+        /**
+         * 处理项目选择
+         */
+        private fun onItemSelected(level: Int, item: CascadeData) {
+            // 更新选中路径
+            while (selectedPath.size > level) {
+                selectedPath.removeAt(selectedPath.size - 1)
+            }
+            selectedPath.add(item.label)
+            
+            // 如果有子级,显示下一级
+            if (!item.children.isNullOrEmpty()) {
+                setupLevel(level + 1, item.children)
+            } else {
+                // 没有子级,移除后续级别
+                while (recyclerViews.size > level + 1) {
+                    removeView(recyclerViews.removeAt(recyclerViews.size - 1))
+                    adapters.removeAt(adapters.size - 1)
+                }
+            }
+        }
+        
+        /**
+         * 获取选中的路径
+         */
+        fun getSelectedPath(): List<String> {
+            return selectedPath.toList()
+        }
+    }
+    
+    /**
+     * 级联适配器
+     */
+    private class CascadeAdapter(
+        private var items: List<CascadeData>,
+        private val onItemClick: (CascadeData) -> Unit
+    ) : RecyclerView.Adapter<CascadeAdapter.ViewHolder>() {
+        
+        private var selectedPosition = -1
+        
+        fun updateData(newItems: List<CascadeData>) {
+            items = newItems
+            selectedPosition = -1
+            notifyDataSetChanged()
+        }
+        
+        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+            val textView = TextView(parent.context).apply {
+                textSize = 16f
+                setTextColor(Color.parseColor("#000000"))
+                setPadding(dpToPx(parent.context, 16f), dpToPx(parent.context, 12f), dpToPx(parent.context, 16f), dpToPx(parent.context, 12f))
+                gravity = Gravity.CENTER_VERTICAL
+                setBackgroundResource(android.R.drawable.list_selector_background)
+            }
+            return ViewHolder(textView)
+        }
+        
+        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+            val item = items[position]
+            holder.textView.text = item.label
+            
+            // 选中状态
+            if (position == selectedPosition) {
+                holder.textView.setTextColor(Color.parseColor("#007AFF"))
+                holder.textView.setTypeface(null, android.graphics.Typeface.BOLD)
+            } else {
+                holder.textView.setTextColor(Color.parseColor("#000000"))
+                holder.textView.setTypeface(null, android.graphics.Typeface.NORMAL)
+            }
+            
+            holder.textView.setOnClickListener {
+                val oldPosition = selectedPosition
+                selectedPosition = position
+                if (oldPosition >= 0) {
+                    notifyItemChanged(oldPosition)
+                }
+                notifyItemChanged(position)
+                onItemClick(item)
+            }
+        }
+        
+        override fun getItemCount() = items.size
+        
+        class ViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)
+    }
+    
+    /**
+     * dp 转 px
+     */
+    private fun dpToPx(context: Context, dp: Float): Int {
+        val density = context.resources.displayMetrics.density
+        return (dp * density + 0.5f).toInt()
+    }
+    
+    /**
+     * dp 转 px(重载方法,兼容 Int 参数)
+     */
+    private fun dpToPx(context: Context, dp: Int): Int {
+        return dpToPx(context, dp.toFloat())
+    }
+}
+

+ 439 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/dialog/DialogHelper.kt

@@ -0,0 +1,439 @@
+package com.narutohuo.xindazhou.common.dialog
+
+import android.app.Activity
+import android.app.AlertDialog
+import android.app.Dialog
+import android.content.Context
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.view.Window
+import android.widget.EditText
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.fragment.app.Fragment
+import com.narutohuo.xindazhou.common.ui.MessageHelper
+
+/**
+ * 对话框管理器
+ * 
+ * 统一封装常用对话框,提供简洁美观的对话框体验(参考现代移动端设计风格)
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 简单提示(只有一个确定按钮)
+ * DialogHelper.showAlert(context, "提示", "这是一个提示框")
+ * 
+ * // 确认对话框
+ * DialogHelper.showConfirm(context, "确认", "确定要删除吗?") {
+ *     // 确认操作
+ * }
+ * 
+ * // 输入对话框
+ * DialogHelper.showInput(context, "输入", "请输入用户名") { input ->
+ *     // 处理输入
+ * }
+ * 
+ * // 列表选择对话框
+ * DialogHelper.showList(context, "选择", listOf("选项1", "选项2")) { index, item ->
+ *     // 处理选择
+ * }
+ * ```
+ */
+object DialogHelper {
+    
+    /**
+     * 显示提示框(只有一个确定按钮)
+     * 
+     * @param context 上下文
+     * @param title 标题(可选)
+     * @param message 消息内容
+     * @param confirmText 确认按钮文本(默认"确定")
+     * @param onConfirm 确认回调(可选)
+     * @return Dialog 实例
+     */
+    fun showAlert(
+        context: Context,
+        title: String? = null,
+        message: String,
+        confirmText: String = "确定",
+        onConfirm: (() -> Unit)? = null
+    ): Dialog {
+        return createConfirmDialog(context, title, message, confirmText, null, onConfirm, null)
+    }
+    
+    /**
+     * 显示确认对话框(有确定和取消按钮)
+     * 
+     * @param context 上下文
+     * @param title 标题(可选)
+     * @param message 消息内容
+     * @param confirmText 确认按钮文本(默认"确定")
+     * @param cancelText 取消按钮文本(默认"取消")
+     * @param onConfirm 确认回调
+     * @param onCancel 取消回调(可选)
+     * @return Dialog 实例
+     */
+    fun showConfirm(
+        context: Context,
+        title: String? = null,
+        message: String,
+        confirmText: String = "确定",
+        cancelText: String = "取消",
+        onConfirm: () -> Unit,
+        onCancel: (() -> Unit)? = null
+    ): Dialog {
+        return createConfirmDialog(context, title, message, confirmText, cancelText, onConfirm, onCancel)
+            }
+    
+    /**
+     * 显示提示框(Activity 扩展函数)
+     */
+    fun Activity.showAlert(
+        title: String? = null,
+        message: String,
+        confirmText: String = "确定",
+        onConfirm: (() -> Unit)? = null
+    ): Dialog {
+        return showAlert(this, title, message, confirmText, onConfirm)
+    }
+    
+    /**
+     * 显示确认对话框(Activity 扩展函数)
+     */
+    fun Activity.showConfirm(
+        title: String? = null,
+        message: String,
+        confirmText: String = "确定",
+        cancelText: String = "取消",
+        onConfirm: () -> Unit,
+        onCancel: (() -> Unit)? = null
+    ): Dialog {
+        return showConfirm(this, title, message, confirmText, cancelText, onConfirm, onCancel)
+    }
+    
+    /**
+     * 显示提示框(Fragment 扩展函数)
+     */
+    fun Fragment.showAlert(
+        title: String? = null,
+        message: String,
+        confirmText: String = "确定",
+        onConfirm: (() -> Unit)? = null
+    ): Dialog {
+        return showAlert(requireContext(), title, message, confirmText, onConfirm)
+    }
+    
+    /**
+     * 显示确认对话框(Fragment 扩展函数)
+     */
+    fun Fragment.showConfirm(
+        title: String? = null,
+        message: String,
+        confirmText: String = "确定",
+        cancelText: String = "取消",
+        onConfirm: () -> Unit,
+        onCancel: (() -> Unit)? = null
+    ): Dialog {
+        return showConfirm(requireContext(), title, message, confirmText, cancelText, onConfirm, onCancel)
+    }
+    
+    /**
+     * 显示输入对话框
+     * 
+     * @param context 上下文
+     * @param title 标题
+     * @param hint 输入框提示文本
+     * @param defaultText 默认文本(可选)
+     * @param confirmText 确认按钮文本(默认"确定")
+     * @param cancelText 取消按钮文本(默认"取消")
+     * @param onConfirm 确认回调(参数为输入的内容)
+     * @param onCancel 取消回调(可选)
+     */
+    fun showInput(
+        context: Context,
+        title: String,
+        hint: String = "",
+        defaultText: String = "",
+        confirmText: String = "确定",
+        cancelText: String = "取消",
+        onConfirm: (String) -> Unit,
+        onCancel: (() -> Unit)? = null
+    ) {
+        val editText = EditText(context)
+        editText.hint = hint
+        editText.setText(defaultText)
+        
+        AlertDialog.Builder(context)
+            .setTitle(title)
+            .setView(editText)
+            .setPositiveButton(confirmText) { _, _ ->
+                val input = editText.text.toString().trim()
+                if (input.isNotEmpty()) {
+                    onConfirm(input)
+                } else {
+                    MessageHelper.showToast(editText, "输入不能为空")
+                }
+            }
+            .setNegativeButton(cancelText) { _, _ ->
+                onCancel?.invoke()
+            }
+            .setCancelable(true)
+            .show()
+    }
+    
+    /**
+     * 显示输入对话框(Activity 扩展函数)
+     */
+    fun Activity.showInput(
+        title: String,
+        hint: String = "",
+        defaultText: String = "",
+        confirmText: String = "确定",
+        cancelText: String = "取消",
+        onConfirm: (String) -> Unit,
+        onCancel: (() -> Unit)? = null
+    ) {
+        showInput(this, title, hint, defaultText, confirmText, cancelText, onConfirm, onCancel)
+    }
+    
+    /**
+     * 显示输入对话框(Fragment 扩展函数)
+     */
+    fun Fragment.showInput(
+        title: String,
+        hint: String = "",
+        defaultText: String = "",
+        confirmText: String = "确定",
+        cancelText: String = "取消",
+        onConfirm: (String) -> Unit,
+        onCancel: (() -> Unit)? = null
+    ) {
+        requireContext().let {
+            showInput(it, title, hint, defaultText, confirmText, cancelText, onConfirm, onCancel)
+        }
+    }
+    
+    /**
+     * 显示列表选择对话框
+     * 
+     * @param context 上下文
+     * @param title 标题
+     * @param items 列表项
+     * @param onSelect 选择回调(参数为索引和选中项)
+     */
+    fun showList(
+        context: Context,
+        title: String,
+        items: List<String>,
+        onSelect: (Int, String) -> Unit
+    ) {
+        AlertDialog.Builder(context)
+            .setTitle(title)
+            .setItems(items.toTypedArray()) { _, which ->
+                onSelect(which, items[which])
+            }
+            .setCancelable(true)
+            .show()
+    }
+    
+    /**
+     * 显示列表选择对话框(Activity 扩展函数)
+     */
+    fun Activity.showList(
+        title: String,
+        items: List<String>,
+        onSelect: (Int, String) -> Unit
+    ) {
+        showList(this, title, items, onSelect)
+    }
+    
+    /**
+     * 显示列表选择对话框(Fragment 扩展函数)
+     */
+    fun Fragment.showList(
+        title: String,
+        items: List<String>,
+        onSelect: (Int, String) -> Unit
+    ) {
+        requireContext().let {
+            showList(it, title, items, onSelect)
+        }
+    }
+    
+    /**
+     * 显示加载对话框(简单实现,可扩展)
+     * 
+     * @param context 上下文
+     * @return AlertDialog 实例,用于后续关闭
+     */
+    fun showLoading(context: Context): AlertDialog {
+        return AlertDialog.Builder(context)
+            .setMessage("加载中...")
+            .setCancelable(false)
+            .show()
+    }
+    
+    /**
+     * 显示加载对话框(Activity 扩展函数)
+     */
+    fun Activity.showLoading(): AlertDialog {
+        return showLoading(this)
+    }
+    
+    /**
+     * 显示加载对话框(Fragment 扩展函数)
+     */
+    fun Fragment.showLoading(): AlertDialog {
+        return requireContext().let { showLoading(it) }
+    }
+    
+    // ==================== 私有方法 ====================
+    
+    /**
+     * 创建确认对话框(使用圆角、居中、简洁的样式)
+     */
+    private fun createConfirmDialog(
+        context: Context,
+        title: String?,
+        message: String,
+        confirmText: String,
+        cancelText: String?,
+        onConfirm: (() -> Unit)?,
+        onCancel: (() -> Unit)?
+    ): Dialog {
+        val dialog = Dialog(context)
+        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
+        dialog.window?.apply {
+            setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+        }
+        
+        // 创建主容器
+        val container = LinearLayout(context).apply {
+            orientation = LinearLayout.VERTICAL
+            // 设置圆角背景
+            background = createRoundRectDrawable(context, 14f)
+            layoutParams = LinearLayout.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.WRAP_CONTENT
+            ).apply {
+                setMargins(dpToPx(context, 40f), 0, dpToPx(context, 40f), 0)
+            }
+        }
+        
+        // 标题
+        if (!title.isNullOrEmpty()) {
+            val titleView = TextView(context).apply {
+                text = title
+                textSize = 17f
+                setTextColor(Color.parseColor("#000000"))
+                gravity = Gravity.CENTER
+                setPadding(0, dpToPx(context, 20f), 0, dpToPx(context, 12f))
+            }
+            container.addView(titleView)
+        }
+        
+        // 消息内容
+        val messageView = TextView(context).apply {
+            text = message
+            textSize = 13f
+            setTextColor(Color.parseColor("#000000"))
+            gravity = Gravity.CENTER
+            setPadding(dpToPx(context, 20f), 0, dpToPx(context, 20f), dpToPx(context, 20f))
+        }
+        container.addView(messageView)
+        
+        // 按钮容器
+        val buttonContainer = LinearLayout(context).apply {
+            orientation = LinearLayout.HORIZONTAL
+            if (cancelText != null) {
+                // 有取消按钮时,添加分隔线
+                val divider = View(context).apply {
+                    layoutParams = LinearLayout.LayoutParams(
+                        dpToPx(context, 1f),
+                        ViewGroup.LayoutParams.MATCH_PARENT
+                    )
+                    setBackgroundColor(Color.parseColor("#D1D1D6"))
+                }
+                addView(divider)
+            }
+        }
+        
+        // 取消按钮
+        if (cancelText != null) {
+            val cancelButton = createButton(context, cancelText, Color.parseColor("#007AFF"), false) {
+                dialog.dismiss()
+                onCancel?.invoke()
+            }
+            buttonContainer.addView(cancelButton, LinearLayout.LayoutParams(
+                0,
+                dpToPx(context, 44f),
+                1f
+            ))
+        }
+        
+        // 确定按钮
+        val confirmButton = createButton(context, confirmText, Color.parseColor("#007AFF"), cancelText == null) {
+            dialog.dismiss()
+            onConfirm?.invoke()
+        }
+        buttonContainer.addView(confirmButton, LinearLayout.LayoutParams(
+            0,
+            dpToPx(context, 44f),
+            1f
+        ))
+        
+        container.addView(buttonContainer)
+        
+        dialog.setContentView(container)
+        dialog.setCancelable(true)
+        dialog.setCanceledOnTouchOutside(true)
+        dialog.show()
+        
+        return dialog
+    }
+    
+    /**
+     * 创建按钮
+     */
+    private fun createButton(
+        context: Context,
+        text: String,
+        textColor: Int,
+        isBold: Boolean,
+        onClick: () -> Unit
+    ): TextView {
+        return TextView(context).apply {
+            this.text = text
+            this.setTextColor(textColor)
+            textSize = 17f
+            if (isBold) {
+                setTypeface(null, android.graphics.Typeface.BOLD)
+            }
+            gravity = Gravity.CENTER
+            setOnClickListener { onClick() }
+            setBackgroundResource(android.R.drawable.list_selector_background)
+        }
+    }
+    
+    /**
+     * 创建圆角背景
+     */
+    private fun createRoundRectDrawable(context: Context, radius: Float): android.graphics.drawable.GradientDrawable {
+        return android.graphics.drawable.GradientDrawable().apply {
+            setColor(Color.WHITE)
+            cornerRadius = radius * context.resources.displayMetrics.density
+        }
+    }
+    
+    /**
+     * dp 转 px
+     */
+    private fun dpToPx(context: Context, dp: Float): Int {
+        val density = context.resources.displayMetrics.density
+        return (dp * density + 0.5f).toInt()
+    }
+}

+ 105 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/executor/ExecutorManager.kt

@@ -0,0 +1,105 @@
+package com.narutohuo.xindazhou.common.executor
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import java.util.concurrent.ConcurrentHashMap
+
+/**
+ * 执行器管理器
+ * 
+ * 业务层封装,提供便捷的异步任务执行方式
+ * 直接使用 Kotlin Coroutines 实现,无需接口
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // IO线程执行
+ * ExecutorManager.executeIO {
+ *     // 执行耗时操作
+ * }
+ * 
+ * // 主线程执行
+ * ExecutorManager.executeMain {
+ *     // 更新UI
+ * }
+ * 
+ * // 延迟执行
+ * val taskId = ExecutorManager.executeDelayed(1000) {
+ *     // 1秒后执行
+ * }
+ * 
+ * // 取消任务
+ * ExecutorManager.cancel(taskId)
+ * ```
+ */
+object ExecutorManager {
+    
+    // 全局协程作用域(IO)
+    private val ioScope = CoroutineScope(Dispatchers.IO)
+    
+    // 全局协程作用域(Main)
+    private val mainScope = CoroutineScope(Dispatchers.Main)
+    
+    // 存储延迟任务,用于取消
+    private val delayedTasks = ConcurrentHashMap<String, Job>()
+    
+    /**
+     * 在 IO 线程执行
+     * 
+     * @param block 要执行的代码块
+     */
+    fun executeIO(block: suspend () -> Unit) {
+        ioScope.launch {
+            block()
+        }
+    }
+    
+    /**
+     * 在主线程执行
+     * 
+     * @param block 要执行的代码块
+     */
+    fun executeMain(block: () -> Unit) {
+        mainScope.launch {
+            block()
+        }
+    }
+    
+    /**
+     * 延迟执行
+     * 
+     * @param delayMillis 延迟时间(毫秒)
+     * @param block 要执行的代码块
+     * @return 任务ID,可用于取消任务
+     */
+    fun executeDelayed(delayMillis: Long, block: () -> Unit): String {
+        val taskId = "task_${System.currentTimeMillis()}_${delayedTasks.size}"
+        val job = mainScope.launch {
+            delay(delayMillis)
+            block()
+            delayedTasks.remove(taskId)
+        }
+        delayedTasks[taskId] = job
+        return taskId
+    }
+    
+    /**
+     * 取消任务
+     * 
+     * @param taskId 任务ID(通过 executeDelayed 返回)
+     */
+    fun cancel(taskId: String) {
+        delayedTasks.remove(taskId)?.cancel()
+    }
+    
+    /**
+     * 取消所有延迟任务
+     */
+    fun cancelAll() {
+        delayedTasks.values.forEach { it.cancel() }
+        delayedTasks.clear()
+    }
+}
+

+ 180 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/file/FilePickerHelper.kt

@@ -0,0 +1,180 @@
+package com.narutohuo.xindazhou.common.file
+
+import android.app.Activity
+import android.content.Intent
+import android.net.Uri
+import android.provider.MediaStore
+import androidx.activity.ComponentActivity
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.Fragment
+
+/**
+ * 文件选择器管理器
+ * 
+ * 统一封装文件选择功能,提供便捷的文件选择方式
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 Activity/Fragment 中注册
+ * private val pickImageLauncher = FilePickerHelper.registerPickImage(this) { uri ->
+ *     // 处理选中的图片
+ * }
+ * 
+ * // 选择图片
+ * pickImageLauncher.launch()
+ * 
+ * // 选择文件
+ * private val pickFileLauncher = FilePickerHelper.registerPickFile(this, "image/png") { uri ->
+ *     // 处理选中的文件
+ * }
+ * pickFileLauncher.launch()
+ * ```
+ */
+object FilePickerHelper {
+    
+    /**
+     * 注册图片选择器(Activity)
+     * 
+     * @param activity ComponentActivity 实例
+     * @param onResult 选择结果回调
+     * @return ActivityResultLauncher,调用 launch() 启动选择器
+     */
+    fun registerPickImage(
+        activity: ComponentActivity,
+        onResult: (Uri?) -> Unit
+    ): ActivityResultLauncher<Intent> {
+        return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+            if (result.resultCode == Activity.RESULT_OK) {
+                val uri = result.data?.data
+                onResult(uri)
+            } else {
+                onResult(null)
+            }
+        }
+    }
+    
+    /**
+     * 注册图片选择器(Fragment)
+     * 
+     * @param fragment Fragment 实例
+     * @param onResult 选择结果回调
+     * @return ActivityResultLauncher,调用 launch() 启动选择器
+     */
+    fun registerPickImage(
+        fragment: Fragment,
+        onResult: (Uri?) -> Unit
+    ): ActivityResultLauncher<Intent> {
+        return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+            if (result.resultCode == Activity.RESULT_OK) {
+                val uri = result.data?.data
+                onResult(uri)
+            } else {
+                onResult(null)
+            }
+        }
+    }
+    
+    /**
+     * 启动图片选择器(简化版,使用 Intent)
+     * 
+     * @param activity Activity 实例
+     * @param onResult 选择结果回调
+     */
+    fun pickImage(activity: Activity, onResult: (Uri?) -> Unit) {
+        val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
+        activity.startActivityForResult(intent, 1001)
+        // 注意:这种方式需要重写 onActivityResult,建议使用 registerPickImage
+    }
+    
+    /**
+     * 注册文件选择器(Activity)
+     * 
+     * @param activity ComponentActivity 实例
+     * @param mimeType MIME 类型(如 "image/png", "application/pdf")
+     * @param onResult 选择结果回调
+     * @return ActivityResultLauncher
+     */
+    fun registerPickFile(
+        activity: ComponentActivity,
+        mimeType: String,
+        onResult: (Uri?) -> Unit
+    ): ActivityResultLauncher<Intent> {
+        return activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+            if (result.resultCode == Activity.RESULT_OK) {
+                val uri = result.data?.data
+                onResult(uri)
+            } else {
+                onResult(null)
+            }
+        }
+    }
+    
+    /**
+     * 注册文件选择器(Fragment)
+     * 
+     * @param fragment Fragment 实例
+     * @param mimeType MIME 类型
+     * @param onResult 选择结果回调
+     * @return ActivityResultLauncher
+     */
+    fun registerPickFile(
+        fragment: Fragment,
+        mimeType: String,
+        onResult: (Uri?) -> Unit
+    ): ActivityResultLauncher<Intent> {
+        return fragment.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+            if (result.resultCode == Activity.RESULT_OK) {
+                val uri = result.data?.data
+                onResult(uri)
+            } else {
+                onResult(null)
+            }
+        }
+    }
+    
+    /**
+     * 启动文件选择器(简化版)
+     * 
+     * @param activity Activity 实例
+     * @param mimeType MIME 类型
+     * @param onResult 选择结果回调
+     */
+    fun pickFile(activity: Activity, mimeType: String, onResult: (Uri?) -> Unit) {
+        val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
+            type = mimeType
+            addCategory(Intent.CATEGORY_OPENABLE)
+        }
+        activity.startActivityForResult(Intent.createChooser(intent, "选择文件"), 1002)
+        // 注意:这种方式需要重写 onActivityResult,建议使用 registerPickFile
+    }
+    
+    /**
+     * 注册多文件选择器(Fragment,Android 11+)
+     * 
+     * @param fragment Fragment 实例
+     * @param mimeType MIME 类型
+     * @param onResult 选择结果回调
+     * @return ActivityResultLauncher
+     */
+    fun registerPickMultipleFiles(
+        fragment: Fragment,
+        mimeType: String,
+        onResult: (List<Uri>) -> Unit
+    ): ActivityResultLauncher<String> {
+        return fragment.registerForActivityResult(ActivityResultContracts.GetMultipleContents()) { uris ->
+            onResult(uris)
+        }
+    }
+    
+    /**
+     * 启动多文件选择器(Fragment)
+     * 
+     * @param launcher ActivityResultLauncher(通过 registerPickMultipleFiles 获取)
+     * @param mimeType MIME 类型
+     */
+    fun pickMultipleFiles(launcher: ActivityResultLauncher<String>, mimeType: String) {
+        launcher.launch(mimeType)
+    }
+}
+

+ 212 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/image/ImageLoader.kt

@@ -0,0 +1,212 @@
+package com.narutohuo.xindazhou.common.image
+
+import android.graphics.drawable.Drawable
+import android.widget.ImageView
+import androidx.annotation.DrawableRes
+import com.bumptech.glide.Glide
+import com.bumptech.glide.load.engine.DiskCacheStrategy
+import com.bumptech.glide.load.resource.bitmap.CircleCrop
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners
+import com.bumptech.glide.request.RequestOptions
+import com.bumptech.glide.request.target.CustomTarget
+import com.bumptech.glide.request.transition.Transition
+
+/**
+ * 图片加载管理器
+ * 
+ * 统一封装 Glide 图片加载,提供便捷的图片加载方式
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 基础加载
+ * ImageLoader.load("https://example.com/image.jpg", imageView)
+ * 
+ * // 带占位图
+ * ImageLoader.load("https://example.com/image.jpg", imageView, R.drawable.placeholder)
+ * 
+ * // 圆形图片
+ * ImageLoader.loadCircle("https://example.com/avatar.jpg", imageView)
+ * 
+ * // 圆角图片
+ * ImageLoader.loadRound("https://example.com/image.jpg", imageView, 10)
+ * 
+ * // 清除缓存
+ * ImageLoader.clearCache(context)
+ * ```
+ */
+object ImageLoader {
+    
+    /**
+     * 默认占位图资源ID(可在 Application 中设置)
+     */
+    @DrawableRes
+    var defaultPlaceholder: Int? = null
+    
+    /**
+     * 默认错误图资源ID(可在 Application 中设置)
+     */
+    @DrawableRes
+    var defaultError: Int? = null
+    
+    /**
+     * 默认圆角半径(像素)
+     */
+    var defaultRadius: Int = 8
+    
+    /**
+     * 加载图片(基础方法)
+     * 
+     * @param url 图片URL(可以是网络URL、本地路径、资源ID等)
+     * @param imageView 目标 ImageView
+     * @param placeholder 占位图资源ID(可选,如果为 null 则使用默认占位图)
+     * @param error 错误图资源ID(可选,如果为 null 则使用默认错误图)
+     */
+    fun load(
+        url: Any?,
+        imageView: ImageView,
+        @DrawableRes placeholder: Int? = null,
+        @DrawableRes error: Int? = null
+    ) {
+        val requestOptions = RequestOptions()
+            .diskCacheStrategy(DiskCacheStrategy.ALL)
+            .skipMemoryCache(false)
+        
+        // 设置占位图
+        placeholder?.let { requestOptions.placeholder(it) }
+            ?: defaultPlaceholder?.let { requestOptions.placeholder(it) }
+        
+        // 设置错误图
+        error?.let { requestOptions.error(it) }
+            ?: defaultError?.let { requestOptions.error(it) }
+        
+        Glide.with(imageView.context)
+            .load(url)
+            .apply(requestOptions)
+            .into(imageView)
+    }
+    
+    /**
+     * 加载圆形图片
+     * 
+     * @param url 图片URL
+     * @param imageView 目标 ImageView
+     * @param placeholder 占位图资源ID(可选)
+     * @param error 错误图资源ID(可选)
+     */
+    fun loadCircle(
+        url: Any?,
+        imageView: ImageView,
+        @DrawableRes placeholder: Int? = null,
+        @DrawableRes error: Int? = null
+    ) {
+        val requestOptions = RequestOptions()
+            .diskCacheStrategy(DiskCacheStrategy.ALL)
+            .skipMemoryCache(false)
+            .transform(CircleCrop())
+        
+        placeholder?.let { requestOptions.placeholder(it) }
+            ?: defaultPlaceholder?.let { requestOptions.placeholder(it) }
+        
+        error?.let { requestOptions.error(it) }
+            ?: defaultError?.let { requestOptions.error(it) }
+        
+        Glide.with(imageView.context)
+            .load(url)
+            .apply(requestOptions)
+            .into(imageView)
+    }
+    
+    /**
+     * 加载圆角图片
+     * 
+     * @param url 图片URL
+     * @param imageView 目标 ImageView
+     * @param radius 圆角半径(像素)
+     * @param placeholder 占位图资源ID(可选)
+     * @param error 错误图资源ID(可选)
+     */
+    fun loadRound(
+        url: Any?,
+        imageView: ImageView,
+        radius: Int = defaultRadius,
+        @DrawableRes placeholder: Int? = null,
+        @DrawableRes error: Int? = null
+    ) {
+        val requestOptions = RequestOptions()
+            .diskCacheStrategy(DiskCacheStrategy.ALL)
+            .skipMemoryCache(false)
+            .transform(RoundedCorners(radius))
+        
+        placeholder?.let { requestOptions.placeholder(it) }
+            ?: defaultPlaceholder?.let { requestOptions.placeholder(it) }
+        
+        error?.let { requestOptions.error(it) }
+            ?: defaultError?.let { requestOptions.error(it) }
+        
+        Glide.with(imageView.context)
+            .load(url)
+            .apply(requestOptions)
+            .into(imageView)
+    }
+    
+    /**
+     * 加载图片并获取 Drawable(用于自定义处理)
+     * 
+     * @param url 图片URL
+     * @param onSuccess 成功回调
+     * @param onError 失败回调
+     */
+    fun loadAsDrawable(
+        url: Any?,
+        imageView: ImageView,
+        onSuccess: (Drawable) -> Unit,
+        onError: (() -> Unit)? = null
+    ) {
+        Glide.with(imageView.context)
+            .load(url)
+            .into(object : CustomTarget<Drawable>() {
+                override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
+                    onSuccess(resource)
+                }
+                
+                override fun onLoadCleared(placeholder: Drawable?) {
+                    // 清理资源
+                }
+                
+                override fun onLoadFailed(errorDrawable: Drawable?) {
+                    onError?.invoke()
+                }
+            })
+    }
+    
+    /**
+     * 清除内存缓存
+     * 
+     * @param imageView ImageView 上下文
+     */
+    fun clearMemoryCache(imageView: ImageView) {
+        Glide.get(imageView.context).clearMemory()
+    }
+    
+    /**
+     * 清除磁盘缓存(需要在后台线程执行)
+     * 
+     * @param imageView ImageView 上下文
+     */
+    fun clearDiskCache(imageView: ImageView) {
+        Thread {
+            Glide.get(imageView.context).clearDiskCache()
+        }.start()
+    }
+    
+    /**
+     * 清除所有缓存
+     * 
+     * @param imageView ImageView 上下文
+     */
+    fun clearCache(imageView: ImageView) {
+        clearMemoryCache(imageView)
+        clearDiskCache(imageView)
+    }
+}
+

+ 154 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/launch/AppInitializer.kt

@@ -0,0 +1,154 @@
+package com.narutohuo.xindazhou.common.launch
+
+import android.app.Application
+import com.alibaba.android.arouter.launcher.ARouter
+import com.narutohuo.xindazhou.common.auth.AuthManager
+import com.narutohuo.xindazhou.common.config.ServerConfigManager
+import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.common.network.ApiServiceFactory
+import com.narutohuo.xindazhou.common.network.NetworkHelper
+import com.narutohuo.xindazhou.common.socketio.SocketIOManager
+import com.narutohuo.xindazhou.common.version.VersionUpdateManager
+
+/**
+ * 应用初始化管理器
+ * 
+ * 统一管理所有模块的初始化,简化 Application 代码
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 Application.onCreate() 中调用
+ * AppInitializer.init(this)
+ * ```
+ */
+object AppInitializer {
+    
+    private const val TAG = "AppInitializer"
+    private var initialized = false
+    
+    /**
+     * 初始化所有模块
+     * 
+     * 按顺序初始化各个模块,确保依赖关系正确
+     * 
+     * @param application Application 实例
+     */
+    fun init(application: Application) {
+        if (initialized) {
+            LogHelper.w(TAG, "AppInitializer 已初始化,跳过重复初始化")
+            return
+        }
+        
+        try {
+            // 1. 初始化 ARouter(必须在其他初始化之前)
+            initARouter(application)
+            
+            // 2. 初始化配置管理器
+            ServerConfigManager.init(application.applicationContext)
+            
+            // 3. 初始化认证管理器(会自动初始化 TokenStore)
+            AuthManager.init(application.applicationContext)
+            
+            // 4. 初始化网络管理器
+            initNetwork(application)
+            
+            // 5. 初始化版本更新管理器
+            VersionUpdateManager.init(application.applicationContext)
+            
+            // 6. 初始化 SocketIO 管理器(自动处理生命周期和重连)
+            SocketIOManager.init(application)
+            
+            // 7. 初始化分享服务(可选,失败不影响应用运行)
+            initShareService(application)
+            
+            initialized = true
+            LogHelper.d(TAG, "所有模块初始化完成")
+        } catch (e: Exception) {
+            LogHelper.e(TAG, "初始化失败", e)
+            throw e
+        }
+    }
+    
+    /**
+     * 初始化 ARouter
+     */
+    private fun initARouter(application: Application) {
+        val isDebug = isDebugMode(application)
+        if (isDebug) {
+            // 开启日志
+            ARouter.openLog()
+            // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
+            ARouter.openDebug()
+        }
+        // 初始化 ARouter
+        ARouter.init(application)
+        LogHelper.d(TAG, "ARouter 初始化完成")
+    }
+    
+    /**
+     * 初始化网络管理器
+     */
+    private fun initNetwork(application: Application) {
+        val isDebug = isDebugMode(application)
+        val baseUrl = ServerConfigManager.getHttpServerUrl()
+        
+        // 初始化网络管理器
+        NetworkHelper.init(
+            baseUrl = baseUrl,
+            isDebug = isDebug,
+            enableLogging = isDebug
+        )
+        
+        // 配置 Token 提供器
+        NetworkHelper.setTokenProvider {
+            AuthManager.getAccessToken()
+        }
+        
+        // 初始化 ApiServiceFactory 的 baseUrlProvider
+        // 这样所有使用 ApiServiceFactory.create() 的地方都可以自动获取 baseUrl
+        ApiServiceFactory.baseUrlProvider = {
+            ServerConfigManager.getHttpServerUrl()
+        }
+        
+        LogHelper.d(TAG, "网络管理器初始化完成")
+    }
+    
+    /**
+     * 初始化分享服务
+     * 
+     * 分享服务初始化失败不影响应用运行,但分享功能将不可用
+     * 使用反射调用,避免 base-common 强依赖 capability-share 模块
+     */
+    private fun initShareService(application: Application) {
+        try {
+            // 使用反射调用,避免强依赖
+            val shareServiceFactoryClass = Class.forName("com.narutohuo.xindazhou.share.factory.ShareServiceFactory")
+            val initMethod = shareServiceFactoryClass.getMethod("init", Application::class.java)
+            initMethod.invoke(null, application)
+            LogHelper.d(TAG, "分享服务初始化成功")
+        } catch (e: ClassNotFoundException) {
+            // 分享服务模块未引入,跳过初始化(这是正常的,因为分享服务是可选的)
+            LogHelper.d(TAG, "分享服务模块未引入,跳过初始化")
+        } catch (e: Exception) {
+            // 分享服务初始化失败不影响应用运行,但分享功能将不可用
+            LogHelper.e(TAG, "分享服务初始化失败,分享功能将不可用", e)
+        }
+    }
+    
+    /**
+     * 获取是否为调试模式
+     * 
+     * 安全地获取 BuildConfig.DEBUG,避免模块未引入时崩溃
+     */
+    private fun isDebugMode(application: Application): Boolean {
+        return try {
+            val buildConfigClass = Class.forName("${application.packageName}.BuildConfig")
+            val debugField = buildConfigClass.getField("DEBUG")
+            debugField.getBoolean(null)
+        } catch (e: Exception) {
+            LogHelper.e(TAG, "无法获取 BuildConfig.DEBUG,默认返回 false", e)
+            false
+        }
+    }
+}
+

+ 71 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/launch/AppLaunchManager.kt

@@ -0,0 +1,71 @@
+package com.narutohuo.xindazhou.common.launch
+
+import android.content.Intent
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.lifecycleScope
+import com.narutohuo.xindazhou.common.log.LogHelper
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/**
+ * 应用启动管理器
+ * 
+ * 只负责登录状态判断和页面跳转,不处理版本检查
+ * 版本检查由 VersionUpdateManager 独立处理
+ */
+object AppLaunchManager {
+    
+    /**
+     * 处理应用启动逻辑(根据登录状态跳转)
+     * 
+     * @param activity Activity 实例
+     * @param isLoggedIn 判断是否已登录的函数
+     * @param mainActivityClass 主界面 Activity 类
+     * @param loginActivityClass 登录页 Activity 类
+     * @param splashDelay 启动画面显示时长(毫秒,默认 500ms)
+     */
+    fun handleLaunch(
+        activity: FragmentActivity,
+        isLoggedIn: () -> Boolean,
+        mainActivityClass: Class<out FragmentActivity>,
+        loginActivityClass: Class<out FragmentActivity>,
+        splashDelay: Long = 500
+    ) {
+        activity.lifecycleScope.launch {
+            try {
+                // 显示启动画面
+                delay(splashDelay)
+                
+                // 根据登录状态跳转
+                if (isLoggedIn()) {
+                    // 已登录,跳转到主界面
+                    val intent = Intent(activity, mainActivityClass).apply {
+                        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+                    }
+                    activity.startActivity(intent)
+                    activity.finish()
+                } else {
+                    // 未登录,跳转到登录页
+                    val intent = Intent(activity, loginActivityClass).apply {
+                        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+                    }
+                    activity.startActivity(intent)
+                    activity.finish()
+                }
+            } catch (e: Exception) {
+                LogHelper.e("AppLaunchManager", "启动处理失败", e)
+                // 出错时默认跳转到登录页
+                try {
+                    val intent = Intent(activity, loginActivityClass).apply {
+                        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
+                    }
+                    activity.startActivity(intent)
+                    activity.finish()
+                } catch (ex: Exception) {
+                    LogHelper.e("AppLaunchManager", "跳转登录页失败", ex)
+                }
+            }
+        }
+    }
+}
+

+ 79 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/launch/LaunchActivity.kt

@@ -0,0 +1,79 @@
+package com.narutohuo.xindazhou.common.launch
+
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.FragmentActivity
+import com.narutohuo.xindazhou.common.ui.ActivityManager
+
+/**
+ * 启动页 Activity(完整封装)
+ * 
+ * 职责:
+ * 1. 显示启动画面
+ * 2. 判断登录状态并跳转到对应页面
+ * 3. 自动处理启动逻辑
+ * 
+ * 使用方式:
+ * 在 app 模块中继承此类,实现 getMainActivityClass() 和 getLoginActivityClass() 方法
+ * 
+ * 示例:
+ * ```kotlin
+ * class SplashActivity : LaunchActivity() {
+ *     override fun getMainActivityClass() = MainActivity::class.java
+ *     override fun getLoginActivityClass() = LoginActivity::class.java
+ *     override fun isLoggedIn() = TokenManager.isLoggedIn()
+ * }
+ * ```
+ */
+abstract class LaunchActivity : AppCompatActivity() {
+    
+    /**
+     * 获取主界面 Activity 类(子类实现)
+     */
+    protected abstract fun getMainActivityClass(): Class<out FragmentActivity>
+    
+    /**
+     * 获取登录页 Activity 类(子类实现)
+     */
+    protected abstract fun getLoginActivityClass(): Class<out FragmentActivity>
+    
+    /**
+     * 判断是否已登录(子类实现)
+     */
+    protected abstract fun isLoggedIn(): Boolean
+    
+    /**
+     * 启动画面显示时长(毫秒,默认 500ms,子类可重写)
+     */
+    protected open val splashDelay: Long = 500
+    
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        
+        // 注册到 ActivityManager
+        ActivityManager.addActivity(this)
+        
+        // 设置简单的启动页布局
+        val root = View(this).apply {
+            setBackgroundColor(android.graphics.Color.WHITE)
+        }
+        setContentView(root)
+        
+        // 自动处理启动逻辑
+        AppLaunchManager.handleLaunch(
+            activity = this,
+            isLoggedIn = { isLoggedIn() },
+            mainActivityClass = getMainActivityClass(),
+            loginActivityClass = getLoginActivityClass(),
+            splashDelay = splashDelay
+        )
+    }
+    
+    override fun onDestroy() {
+        super.onDestroy()
+        // 从 ActivityManager 中移除
+        ActivityManager.removeActivity(this)
+    }
+}
+

+ 118 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/log/LogHelper.kt

@@ -0,0 +1,118 @@
+package com.narutohuo.xindazhou.common.log
+
+import com.narutohuo.xindazhou.core.log.ILog as CoreILog
+import com.narutohuo.xindazhou.core.log.LogLevel
+
+/**
+ * 日志工具类封装
+ * 
+ * 业务层统一使用此类进行日志记录,不直接使用 base-core 的 ILog
+ * 
+ * **使用示例**:
+ * ```kotlin
+ * // 调试日志
+ * LogHelper.d("Tag", "调试信息")
+ * 
+ * // 信息日志
+ * LogHelper.i("Tag", "一般信息")
+ * 
+ * // 警告日志
+ * LogHelper.w("Tag", "警告信息")
+ * 
+ * // 错误日志
+ * LogHelper.e("Tag", "错误信息", exception)
+ * 
+ * // 上传日志文件
+ * LogHelper.uploadLogs()
+ * 
+ * // 收集崩溃信息
+ * LogHelper.collectCrash(throwable)
+ * ```
+ */
+object LogHelper {
+    /**
+     * 初始化日志实现(在 Application 中调用,可选)
+     * 
+     * @param enableLogging 是否启用日志(默认 true)
+     */
+    @JvmStatic
+    fun init(enableLogging: Boolean = true) {
+        CoreILog.init(enableLogging = enableLogging)
+    }
+    
+    /**
+     * 调试日志
+     * 
+     * @param tag 标签
+     * @param message 消息
+     */
+    @JvmStatic
+    fun d(tag: String, message: String) {
+        CoreILog.d(tag, message)
+    }
+    
+    /**
+     * 信息日志
+     * 
+     * @param tag 标签
+     * @param message 消息
+     */
+    @JvmStatic
+    fun i(tag: String, message: String) {
+        CoreILog.i(tag, message)
+    }
+    
+    /**
+     * 警告日志
+     * 
+     * @param tag 标签
+     * @param message 消息
+     */
+    @JvmStatic
+    fun w(tag: String, message: String) {
+        CoreILog.w(tag, message)
+    }
+    
+    /**
+     * 错误日志
+     * 
+     * @param tag 标签
+     * @param message 消息
+     * @param throwable 异常(可选)
+     */
+    @JvmStatic
+    fun e(tag: String, message: String, throwable: Throwable? = null) {
+        CoreILog.e(tag, message, throwable)
+    }
+    
+    /**
+     * 按级别记录日志
+     * 
+     * @param level 日志级别
+     * @param tag 标签
+     * @param message 消息
+     * @param throwable 异常(可选)
+     */
+    @JvmStatic
+    fun log(level: LogLevel, tag: String, message: String, throwable: Throwable? = null) {
+        CoreILog.log(level, tag, message, throwable)
+    }
+    
+    /**
+     * 上传日志文件
+     */
+    @JvmStatic
+    fun uploadLogs() {
+        CoreILog.uploadLogs()
+    }
+    
+    /**
+     * 收集崩溃信息
+     * 
+     * @param throwable 异常
+     */
+    @JvmStatic
+    fun collectCrash(throwable: Throwable) {
+        CoreILog.collectCrash(throwable)
+    }
+}

+ 125 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiBaseRemoteDataSource.kt

@@ -0,0 +1,125 @@
+package com.narutohuo.xindazhou.common.network
+
+import android.util.Log
+import retrofit2.Response
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.net.UnknownHostException
+import java.net.SocketTimeoutException
+
+/**
+ * API 基础远程数据源
+ * 
+ * 封装通用网络请求逻辑,统一处理:
+ * - 错误处理和异常捕获
+ * - 日志记录
+ * - 线程切换(自动切换到 IO 线程)
+ * - 网络异常友好提示
+ * 
+ * 使用方式:
+ * ```kotlin
+ * class AuthRemoteDataSource : ApiBaseRemoteDataSource() {
+ *     private val authApi: AuthApi = ApiServiceFactory.create()
+ *     
+ *     suspend fun login(request: LoginRequest): Result<LoginResponse> {
+ *         return executeRequest(
+ *             request = { authApi.login(request) },
+ *             errorMessage = "登录失败"
+ *         )
+ *     }
+ * }
+ * ```
+ */
+abstract class ApiBaseRemoteDataSource {
+    
+    /**
+     * 获取日志标签(子类可重写)
+     * 
+     * 注意:必须使用 public,因为内联函数需要访问此属性
+     * 内联函数会将代码复制到调用处,如果使用 protected/private 会导致访问权限错误
+     */
+    open val tag: String
+        get() = this::class.simpleName ?: "ApiBaseRemoteDataSource"
+    
+    /**
+     * 处理网络请求(通用方法)
+     * 
+     * 自动处理:
+     * - HTTP 错误码
+     * - 业务错误码
+     * - 网络异常
+     * - 超时异常
+     * - 日志记录
+     * 
+     * @param request 网络请求的 suspend 函数
+     * @param errorMessage 错误消息前缀
+     * @return Result<T>
+     */
+    protected suspend inline fun <reified T> executeRequest(
+        crossinline request: suspend () -> Response<ApiCommonResult<T>>,
+        errorMessage: String = "请求失败"
+    ): Result<T> = withContext(Dispatchers.IO) {
+        try {
+            Log.d(tag, "开始请求: ${T::class.simpleName}")
+            val response = request()
+            
+            // 使用 reified 泛型,自动获取 Class
+            ApiResponseParser.handleResponse(
+                response = response,
+                dataClass = T::class.java,
+                tag = tag,
+                errorMessage = errorMessage
+            )
+        } catch (e: SocketTimeoutException) {
+            val errorMsg = "请求超时,请检查网络连接"
+            Log.e(tag, errorMsg, e)
+            Result.failure(Exception(errorMsg))
+        } catch (e: UnknownHostException) {
+            val errorMsg = "网络连接失败,请检查网络设置"
+            Log.e(tag, errorMsg, e)
+            Result.failure(Exception(errorMsg))
+        } catch (e: Exception) {
+            val errorMsg = e.message ?: errorMessage
+            Log.e(tag, "请求异常: $errorMsg", e)
+            Result.failure(Exception(errorMsg))
+        }
+    }
+    
+    /**
+     * 处理可空数据的网络请求
+     * 
+     * 用于处理服务器可能返回 null 的情况
+     * 
+     * @param request 网络请求的 suspend 函数
+     * @param errorMessage 错误消息前缀
+     * @return Result<T?>
+     */
+    protected suspend inline fun <reified T> executeNullableRequest(
+        crossinline request: suspend () -> Response<ApiCommonResult<T>>,
+        errorMessage: String = "请求失败"
+    ): Result<T?> = withContext(Dispatchers.IO) {
+        try {
+            Log.d(tag, "开始请求(可空): ${T::class.simpleName}")
+            val response = request()
+            
+            ApiResponseParser.handleNullableResponse(
+                response = response,
+                tag = tag,
+                errorMessage = errorMessage
+            )
+        } catch (e: SocketTimeoutException) {
+            val errorMsg = "请求超时,请检查网络连接"
+            Log.e(tag, errorMsg, e)
+            Result.failure(Exception(errorMsg))
+        } catch (e: UnknownHostException) {
+            val errorMsg = "网络连接失败,请检查网络设置"
+            Log.e(tag, errorMsg, e)
+            Result.failure(Exception(errorMsg))
+        } catch (e: Exception) {
+            val errorMsg = e.message ?: errorMessage
+            Log.e(tag, "请求异常: $errorMsg", e)
+            Result.failure(Exception(errorMsg))
+        }
+    }
+}
+

+ 54 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiBaseRepository.kt

@@ -0,0 +1,54 @@
+package com.narutohuo.xindazhou.common.network
+
+import android.util.Log
+
+/**
+ * API 基础仓库类
+ * 
+ * 封装通用仓库逻辑:
+ * - 统一错误处理
+ * - 数据转换(Data Model → Domain Model)
+ * - 缓存策略(可选)
+ * 
+ * 使用方式:
+ * ```kotlin
+ * class AuthRepository(
+ *     private val remoteDataSource: AuthRemoteDataSource
+ * ) : ApiBaseRepository() {
+ *     
+ *     suspend fun login(mobile: String, password: String): Result<LoginResponse> {
+ *         val request = LoginRequest(mobile, password)
+ *         return remoteDataSource.login(request)
+ *     }
+ * }
+ * ```
+ */
+open class ApiBaseRepository {
+    
+    /**
+     * 获取日志标签(子类可重写)
+     */
+    protected open val tag: String
+        get() = this::class.simpleName ?: "ApiBaseRepository"
+    
+    /**
+     * 处理数据转换(Data Model → Domain Model)
+     * 
+     * 子类可重写此方法实现自定义转换逻辑
+     */
+    protected open fun <T> transform(data: T): T {
+        return data
+    }
+    
+    /**
+     * 统一错误处理
+     * 
+     * 子类可重写此方法实现自定义错误处理
+     */
+    protected open fun handleError(throwable: Throwable): Result<Nothing> {
+        val errorMsg = throwable.message ?: "操作失败"
+        Log.e(tag, "操作失败: $errorMsg", throwable)
+        return Result.failure(throwable)
+    }
+}
+

+ 8 - 35
base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiResponseParser.kt

@@ -1,18 +1,15 @@
 package com.narutohuo.xindazhou.common.network
 
 import android.util.Log
-import com.alibaba.fastjson2.JSON
 import retrofit2.Response
-import java.lang.reflect.ParameterizedType
-import java.lang.reflect.Type
 
 /**
- * 通用响应结果
+ * API 通用响应结果
  * 对应后端 CommonResult<T>
  * 
  * 所有API响应的统一包装格式
  */
-data class CommonResult<T>(
+data class ApiCommonResult<T>(
     val code: Int,
     val msg: String?,
     val data: T?
@@ -21,7 +18,7 @@ data class CommonResult<T>(
 /**
  * API响应解析器
  * 
- * 统一处理 Retrofit Response,将 Response<CommonResult<T>> 转换为 Result<T>
+ * 统一处理 Retrofit Response,将 Response<ApiCommonResult<T>> 转换为 Result<T>
  */
 object ApiResponseParser {
     
@@ -40,7 +37,7 @@ object ApiResponseParser {
      * @return Result<T> 成功返回数据,失败返回异常
      */
     fun <T> handleResponse(
-        response: Response<CommonResult<T>>,
+        response: Response<ApiCommonResult<T>>,
         dataClass: Class<T>,
         tag: String = "ApiResponseParser",
         errorMessage: String = "请求失败"
@@ -71,37 +68,13 @@ object ApiResponseParser {
                 return Result.failure(Exception(errorMsg))
             }
             
-            // 4. 提取业务数据并转换类型
-            val dataObj = commonResult.data
-            if (dataObj == null) {
+            // 4. 提取业务数据(Gson 已经处理好了类型转换)
+            val data = commonResult.data
+            if (data == null) {
                 Log.e(tag, "服务器返回数据为空: code=${commonResult.code}, msg=${commonResult.msg}")
                 return Result.failure(Exception("服务器返回数据为空"))
             }
             
-            // 5. 如果 data 是 Map 或 JSONObject 类型(Fastjson2 解析嵌套泛型时的默认行为),需要转换为目标类型
-            val data: T = if (dataObj is Map<*, *> || dataObj.javaClass.name.contains("JSONObject") || dataObj.javaClass.name.contains("JSON")) {
-                try {
-                    Log.d(tag, "检测到 data 为 Map/JSONObject 类型,转换为: ${dataClass.simpleName}")
-                    val dataJsonString = JSON.toJSONString(dataObj)
-                    Log.d(tag, "转换前的 JSON: $dataJsonString")
-                    
-                    // 使用 Fastjson2 解析,支持 LocalDateTime 自动转换为时间戳
-                    @Suppress("UNCHECKED_CAST")
-                    val converted = JSON.parseObject(dataJsonString, dataClass) as T
-                    Log.d(tag, "转换成功: ${converted?.javaClass?.simpleName}")
-                    converted
-                } catch (e: Exception) {
-                    Log.e(tag, "转换 data 类型失败: ${e.message}", e)
-                    Log.e(tag, "原始 data 类型: ${dataObj.javaClass.name}")
-                    Log.e(tag, "目标类型: ${dataClass.name}")
-                    // 如果转换失败,抛出异常,不要静默失败
-                    throw Exception("无法将 ${dataObj.javaClass.simpleName} 转换为 ${dataClass.simpleName}: ${e.message}", e)
-                }
-            } else {
-                @Suppress("UNCHECKED_CAST")
-                dataObj as T
-            }
-            
             Log.d(tag, "请求成功: data类型=${data?.javaClass?.simpleName ?: "null"}")
             Result.success(data)
             
@@ -120,7 +93,7 @@ object ApiResponseParser {
      * @return Result<T?> 成功返回数据(可能为null),失败返回异常
      */
     fun <T> handleNullableResponse(
-        response: Response<CommonResult<T>>,
+        response: Response<ApiCommonResult<T>>,
         tag: String = "ApiResponseParser",
         errorMessage: String = "请求失败"
     ): Result<T?> {

+ 38 - 15
base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiServiceFactory.kt

@@ -1,6 +1,5 @@
 package com.narutohuo.xindazhou.common.network
 
-import com.narutohuo.xindazhou.common.config.ServerConfigManager
 import com.narutohuo.xindazhou.core.network.NetworkManager
 import retrofit2.Retrofit
 
@@ -11,32 +10,56 @@ import retrofit2.Retrofit
  * 
  * 使用方式:
  * ```kotlin
+ * // 方式1:使用默认 baseUrl(需要在 Application 中设置 baseUrlProvider)
+ * ApiServiceFactory.baseUrlProvider = { "https://api.example.com" }
  * val authApi = ApiServiceFactory.create<AuthApi>()
+ * 
+ * // 方式2:使用自定义 baseUrl
+ * val authApi = ApiServiceFactory.create<AuthApi>("https://api.example.com")
+ * 
+ * // 方式3:使用指定的 Retrofit 实例
+ * val retrofit = NetworkManager.getRetrofit(...)
+ * val authApi = ApiServiceFactory.create<AuthApi>(retrofit)
  * ```
  */
 object ApiServiceFactory {
     
     /**
+     * baseUrl 提供器(默认配置)
+     * 
+     * 在 Application 中设置一次即可,后续调用 create() 时如果不传 baseUrl,
+     * 将使用此提供器返回的 baseUrl
+     * 
+     * 示例:
+     * ```kotlin
+     * ApiServiceFactory.baseUrlProvider = { 
+     *     ServerConfigManager.getHttpServerUrl() 
+     * }
+     * ```
+     */
+    var baseUrlProvider: (() -> String)? = null
+    
+    /**
      * 创建 API 服务实例(工厂方法)
      * 
-     * @param baseUrl 基础URL,如果为 null 则使用 ServerConfigManager 的配置
+     * @param baseUrl 基础URL,如果为 null 则使用 baseUrlProvider
      * @return API 服务实例
+     * @throws IllegalStateException 如果 baseUrl 和 baseUrlProvider 都未设置
      */
     inline fun <reified T> create(baseUrl: String? = null): T {
-        val url = baseUrl ?: ServerConfigManager.getHttpServerUrl()
-        
-        // 如果 baseUrl 不同,需要创建新的 Retrofit 实例
-        val retrofit = if (baseUrl != null) {
-            // 使用自定义 baseUrl,创建新的 Retrofit 实例
-            NetworkManager.getRetrofit(
-                com.narutohuo.xindazhou.core.network.NetworkConfig.Builder()
-                    .baseUrl(url)
-                    .build()
+        // 优先级:传入的 baseUrl > baseUrlProvider > 抛出异常
+        val url = baseUrl ?: baseUrlProvider?.invoke()
+            ?: throw IllegalStateException(
+                "baseUrl 未设置。请先调用 ApiServiceFactory.baseUrlProvider = { ... } " +
+                "或在 create() 中传入 baseUrl 参数"
             )
-        } else {
-            // 使用默认配置
-            NetworkManager.getRetrofit()
-        }
+        
+        // 使用 NetworkManager 创建 Retrofit 实例
+        val retrofit = NetworkManager.getRetrofit(
+            com.narutohuo.xindazhou.core.network.NetworkConfig.Builder()
+                .baseUrl(url)
+                .build()
+        )
         
         return retrofit.create(T::class.java)
     }

+ 104 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/NetworkHelper.kt

@@ -0,0 +1,104 @@
+package com.narutohuo.xindazhou.common.network
+
+import com.narutohuo.xindazhou.core.network.NetworkManager as CoreNetworkManager
+import com.narutohuo.xindazhou.core.network.NetworkConfig
+
+/**
+ * 网络管理器封装
+ * 
+ * 业务层统一使用此类进行网络初始化,不直接使用 base-core 的 NetworkManager
+ * 
+ * **使用示例**:
+ * ```kotlin
+ * // 在 Application 中初始化网络管理器
+ * NetworkHelper.init(
+ *     baseUrl = "https://api.example.com",
+ *     isDebug = BuildConfig.DEBUG,
+ *     enableLogging = BuildConfig.DEBUG
+ * )
+ * 
+ * // 设置 Token 提供器
+ * NetworkHelper.setTokenProvider {
+ *     TokenManager.getAccessToken()
+ * }
+ * 
+ * // 重置网络管理器(切换服务器地址时)
+ * NetworkHelper.reset()
+ * ```
+ */
+object NetworkHelper {
+    /**
+     * 初始化网络管理器
+     * 
+     * @param baseUrl 基础URL
+     * @param isDebug 是否为调试模式
+     * @param enableLogging 是否启用日志
+     */
+    @JvmStatic
+    fun init(
+        baseUrl: String,
+        isDebug: Boolean = false,
+        enableLogging: Boolean = isDebug
+    ) {
+        CoreNetworkManager.init(
+            NetworkConfig.Builder()
+                .baseUrl(baseUrl)
+                .isDebug(isDebug)
+                .enableLogging(enableLogging)
+                .build()
+        )
+    }
+    
+    /**
+     * 初始化网络管理器(使用完整配置)
+     * 
+     * @param baseUrl 基础URL
+     * @param connectTimeoutSeconds 连接超时时间(秒)
+     * @param readTimeoutSeconds 读取超时时间(秒)
+     * @param writeTimeoutSeconds 写入超时时间(秒)
+     * @param isDebug 是否为调试模式
+     * @param enableLogging 是否启用日志
+     */
+    @JvmStatic
+    fun init(
+        baseUrl: String,
+        connectTimeoutSeconds: Long,
+        readTimeoutSeconds: Long,
+        writeTimeoutSeconds: Long,
+        isDebug: Boolean = false,
+        enableLogging: Boolean = isDebug
+    ) {
+        CoreNetworkManager.init(
+            NetworkConfig.Builder()
+                .baseUrl(baseUrl)
+                .connectTimeout(connectTimeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)
+                .readTimeout(readTimeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)
+                .writeTimeout(writeTimeoutSeconds, java.util.concurrent.TimeUnit.SECONDS)
+                .isDebug(isDebug)
+                .enableLogging(enableLogging)
+                .build()
+        )
+    }
+    
+    /**
+     * 设置 Token 提供器
+     * 
+     * 用于自动在请求头中添加 Authorization Token
+     * 
+     * @param tokenProvider Token 提供器,返回当前的 accessToken
+     */
+    @JvmStatic
+    fun setTokenProvider(tokenProvider: () -> String?) {
+        CoreNetworkManager.tokenProvider = tokenProvider
+    }
+    
+    /**
+     * 重置网络管理器(用于切换服务器地址时)
+     * 
+     * 清空已缓存的 Retrofit 和 OkHttpClient 实例,下次使用时会重新创建
+     */
+    @JvmStatic
+    fun reset() {
+        CoreNetworkManager.reset()
+    }
+}

+ 934 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/网络请求MVVM封装方案.md

@@ -0,0 +1,934 @@
+# 网络请求 MVVM 架构封装方案(优化版)
+
+## 一、设计目标
+
+将现有的 `ApiResponseParser` 和 `ApiServiceFactory` 封装成符合 MVVM 架构的完整 Data 层组件,统一处理公共逻辑,业务层只需简单调用。
+
+## 二、架构设计
+
+### 2.1 现有组件
+
+- ✅ `ApiResponseParser` - 响应解析器(已存在)
+- ✅ `ApiServiceFactory` - API 服务工厂(已存在)
+
+### 2.2 新增组件
+
+- **BaseRemoteDataSource** - 基础远程数据源抽象类(增强版)
+  - 封装通用网络请求逻辑
+  - 统一错误处理、异常捕获、日志记录
+  - 使用 reified 泛型简化调用,无需传入 dataClass
+  - 自动切换到 IO 线程
+
+- **BaseRepository** - 基础仓库类(可选)
+  - 封装通用仓库逻辑
+  - 统一数据转换和缓存处理
+
+## 三、实现方案
+
+### 3.1 BaseRemoteDataSource 抽象类(增强版)
+
+```kotlin
+package com.narutohuo.xindazhou.common.network
+
+import android.util.Log
+import retrofit2.Response
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.net.UnknownHostException
+import java.net.SocketTimeoutException
+
+/**
+ * 基础远程数据源
+ * 
+ * 封装通用网络请求逻辑,统一处理:
+ * - 错误处理和异常捕获
+ * - 日志记录
+ * - 线程切换(自动切换到 IO 线程)
+ * - 网络异常友好提示
+ * 
+ * 使用方式:
+ * ```kotlin
+ * class AuthRemoteDataSource : BaseRemoteDataSource() {
+ *     private val authApi: AuthApi = ApiServiceFactory.create()
+ *     
+ *     suspend fun login(request: LoginRequest): Result<LoginResponse> {
+ *         return executeRequest(
+ *             request = { authApi.login(request) },
+ *             errorMessage = "登录失败"
+ *         )
+ *     }
+ * }
+ * ```
+ */
+abstract class BaseRemoteDataSource {
+    
+    /**
+     * 获取日志标签(子类可重写)
+     */
+    protected open val tag: String
+        get() = this::class.simpleName ?: "BaseRemoteDataSource"
+    
+    /**
+     * 处理网络请求(通用方法)
+     * 
+     * 自动处理:
+     * - HTTP 错误码
+     * - 业务错误码
+     * - 网络异常
+     * - 超时异常
+     * - 日志记录
+     * 
+     * @param request 网络请求的 suspend 函数
+     * @param errorMessage 错误消息前缀
+     * @return Result<T>
+     */
+    protected suspend inline fun <reified T> executeRequest(
+        crossinline request: suspend () -> Response<CommonResult<T>>,
+        errorMessage: String = "请求失败"
+    ): Result<T> = withContext(Dispatchers.IO) {
+        try {
+            Log.d(tag, "开始请求: ${T::class.simpleName}")
+            val response = request()
+            
+            // 使用 reified 泛型,自动获取 Class
+            ApiResponseParser.handleResponse(
+                response = response,
+                dataClass = T::class.java,
+                tag = tag,
+                errorMessage = errorMessage
+            )
+        } catch (e: SocketTimeoutException) {
+            val errorMsg = "请求超时,请检查网络连接"
+            Log.e(tag, errorMsg, e)
+            Result.failure(Exception(errorMsg))
+        } catch (e: UnknownHostException) {
+            val errorMsg = "网络连接失败,请检查网络设置"
+            Log.e(tag, errorMsg, e)
+            Result.failure(Exception(errorMsg))
+        } catch (e: Exception) {
+            val errorMsg = e.message ?: errorMessage
+            Log.e(tag, "请求异常: $errorMsg", e)
+            Result.failure(Exception(errorMsg))
+        }
+    }
+    
+    /**
+     * 处理可空数据的网络请求
+     * 
+     * 用于处理服务器可能返回 null 的情况
+     * 
+     * @param request 网络请求的 suspend 函数
+     * @param errorMessage 错误消息前缀
+     * @return Result<T?>
+     */
+    protected suspend inline fun <reified T> executeNullableRequest(
+        crossinline request: suspend () -> Response<CommonResult<T>>,
+        errorMessage: String = "请求失败"
+    ): Result<T?> = withContext(Dispatchers.IO) {
+        try {
+            Log.d(tag, "开始请求(可空): ${T::class.simpleName}")
+            val response = request()
+            
+            ApiResponseParser.handleNullableResponse(
+                response = response,
+                tag = tag,
+                errorMessage = errorMessage
+            )
+        } catch (e: SocketTimeoutException) {
+            val errorMsg = "请求超时,请检查网络连接"
+            Log.e(tag, errorMsg, e)
+            Result.failure(Exception(errorMsg))
+        } catch (e: UnknownHostException) {
+            val errorMsg = "网络连接失败,请检查网络设置"
+            Log.e(tag, errorMsg, e)
+            Result.failure(Exception(errorMsg))
+        } catch (e: Exception) {
+            val errorMsg = e.message ?: errorMessage
+            Log.e(tag, "请求异常: $errorMsg", e)
+            Result.failure(Exception(errorMsg))
+        }
+    }
+}
+```
+
+### 3.2 BaseRepository 基础仓库类(可选)
+
+```kotlin
+package com.narutohuo.xindazhou.common.network
+
+import android.util.Log
+
+/**
+ * 基础仓库类
+ * 
+ * 封装通用仓库逻辑:
+ * - 统一错误处理
+ * - 数据转换(Data Model → Domain Model)
+ * - 缓存策略(可选)
+ * 
+ * 使用方式:
+ * ```kotlin
+ * class AuthRepository(
+ *     private val remoteDataSource: AuthRemoteDataSource
+ * ) : BaseRepository() {
+ *     
+ *     suspend fun login(mobile: String, password: String): Result<LoginResponse> {
+ *         val request = LoginRequest(mobile, password)
+ *         return remoteDataSource.login(request)
+ *     }
+ * }
+ * ```
+ */
+open class BaseRepository {
+    
+    /**
+     * 获取日志标签(子类可重写)
+     */
+    protected open val tag: String
+        get() = this::class.simpleName ?: "BaseRepository"
+    
+    /**
+     * 处理数据转换(Data Model → Domain Model)
+     * 
+     * 子类可重写此方法实现自定义转换逻辑
+     */
+    protected open fun <T> transform(data: T): T {
+        return data
+    }
+    
+    /**
+     * 统一错误处理
+     * 
+     * 子类可重写此方法实现自定义错误处理
+     */
+    protected open fun handleError(throwable: Throwable): Result<Nothing> {
+        val errorMsg = throwable.message ?: "操作失败"
+        Log.e(tag, "操作失败: $errorMsg", throwable)
+        return Result.failure(throwable)
+    }
+}
+```
+
+### 3.3 使用示例
+
+#### 示例 1:AuthRemoteDataSource(优化后)
+
+```kotlin
+package com.narutohuo.xindazhou.user.data.remote
+
+import com.narutohuo.xindazhou.common.network.BaseRemoteDataSource
+import com.narutohuo.xindazhou.common.network.ApiServiceFactory
+import com.narutohuo.xindazhou.user.data.model.LoginRequest
+import com.narutohuo.xindazhou.user.data.model.LoginResponse
+import com.narutohuo.xindazhou.user.data.api.AuthApi
+
+/**
+ * 认证远程数据源
+ * 
+ * 继承 BaseRemoteDataSource,自动获得:
+ * - 统一的错误处理
+ * - 自动日志记录
+ * - 网络异常友好提示
+ * - 线程自动切换
+ */
+class AuthRemoteDataSource : BaseRemoteDataSource() {
+    
+    private val authApi: AuthApi = ApiServiceFactory.create()
+    
+    /**
+     * 登录
+     * 
+     * 无需传入 dataClass,使用 reified 泛型自动推断
+     */
+    suspend fun login(request: LoginRequest): Result<LoginResponse> {
+        return executeRequest(
+            request = { authApi.login(request) },
+            errorMessage = "登录失败"
+        )
+    }
+    
+    /**
+     * 注册
+     */
+    suspend fun register(request: RegisterRequest): Result<RegisterResponse> {
+        return executeRequest(
+            request = { authApi.register(request) },
+            errorMessage = "注册失败"
+        )
+    }
+    
+    /**
+     * 刷新Token(可空数据)
+     */
+    suspend fun refreshToken(refreshToken: String): Result<TokenResponse?> {
+        return executeNullableRequest(
+            request = { authApi.refreshToken(refreshToken) },
+            errorMessage = "刷新Token失败"
+        )
+    }
+}
+```
+
+#### 示例 2:UserRemoteDataSource(优化后)
+
+```kotlin
+package com.narutohuo.xindazhou.user.data.remote
+
+import com.narutohuo.xindazhou.common.network.BaseRemoteDataSource
+import com.narutohuo.xindazhou.common.network.ApiServiceFactory
+import com.narutohuo.xindazhou.user.data.model.UserInfo
+import com.narutohuo.xindazhou.user.data.api.UserApi
+
+/**
+ * 用户远程数据源
+ */
+class UserRemoteDataSource : BaseRemoteDataSource() {
+    
+    // 可以自定义日志标签
+    override val tag: String = "UserRemoteDataSource"
+    
+    private val userApi: UserApi = ApiServiceFactory.create()
+    
+    /**
+     * 获取用户信息
+     */
+    suspend fun getUserInfo(userId: String): Result<UserInfo> {
+        return executeRequest(
+            request = { userApi.getUserInfo(userId) },
+            errorMessage = "获取用户信息失败"
+        )
+    }
+    
+    /**
+     * 更新用户信息(可空数据)
+     */
+    suspend fun updateUserInfo(request: UpdateUserRequest): Result<UserInfo?> {
+        return executeNullableRequest(
+            request = { userApi.updateUserInfo(request) },
+            errorMessage = "更新用户信息失败"
+        )
+    }
+}
+```
+
+#### 示例 3:AuthRepository(使用 BaseRepository)
+
+```kotlin
+package com.narutohuo.xindazhou.user.data.repository
+
+import com.narutohuo.xindazhou.common.network.BaseRepository
+import com.narutohuo.xindazhou.user.data.remote.AuthRemoteDataSource
+import com.narutohuo.xindazhou.user.data.model.LoginRequest
+import com.narutohuo.xindazhou.user.data.model.LoginResponse
+
+/**
+ * 认证仓库
+ * 
+ * 继承 BaseRepository,自动获得:
+ * - 统一的错误处理
+ * - 日志记录
+ * - 数据转换扩展点
+ */
+class AuthRepository(
+    private val remoteDataSource: AuthRemoteDataSource
+) : BaseRepository() {
+    
+    /**
+     * 登录
+     */
+    suspend fun login(mobile: String, password: String): Result<LoginResponse> {
+        val request = LoginRequest(mobile, password)
+        return remoteDataSource.login(request)
+            .onFailure { throwable ->
+                // 可以在这里添加额外的错误处理逻辑
+                // 例如:记录日志、发送错误上报等
+                handleError(throwable)
+            }
+    }
+    
+    /**
+     * 注册
+     */
+    suspend fun register(mobile: String, password: String, verifyCode: String): Result<RegisterResponse> {
+        val request = RegisterRequest(mobile, password, verifyCode)
+        return remoteDataSource.register(request)
+    }
+}
+```
+
+### 3.4 ViewModel 层使用(保持不变)
+
+```kotlin
+package com.narutohuo.xindazhou.user.ui.viewmodel
+
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import kotlinx.coroutines.launch
+import com.narutohuo.xindazhou.user.data.repository.AuthRepository
+
+class LoginViewModel(
+    application: Application,
+    private val authRepository: AuthRepository
+) : AndroidViewModel(application) {
+    
+    private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
+    val loginState: StateFlow<LoginState> = _loginState
+    
+    fun login(mobile: String, password: String) {
+        viewModelScope.launch {
+            _loginState.value = LoginState.Loading
+            
+            authRepository.login(mobile, password)
+                .onSuccess {
+                    _loginState.value = LoginState.Success("登录成功")
+                }
+                .onFailure { e ->
+                    _loginState.value = LoginState.Error(e.message ?: "登录失败")
+                }
+        }
+    }
+}
+```
+
+## 四、目录结构
+
+```
+base-common/src/main/java/com/narutohuo/xindazhou/common/
+├── network/
+│   ├── ApiResponseParser.kt          # 响应解析器(已存在)
+│   ├── ApiServiceFactory.kt          # API服务工厂(已存在)
+│   ├── CommonResult.kt               # 通用响应模型(已存在)
+│   ├── BaseRemoteDataSource.kt       # 基础远程数据源(新增,增强版)
+│   └── BaseRepository.kt             # 基础仓库类(新增,可选)
+│
+└── ui/
+    ├── BaseActivity.kt               # 基础Activity(新增)
+    └── BaseFragment.kt               # 基础Fragment(新增)
+
+示例模块:
+user/
+├── data/
+│   ├── remote/
+│   │   └── AuthRemoteDataSource.kt   # 认证远程数据源(继承BaseRemoteDataSource)
+│   └── repository/
+│       └── AuthRepository.kt         # 认证仓库(继承BaseRepository,可选)
+│
+└── ui/
+    ├── login/
+    │   ├── LoginActivity.kt          # 登录Activity(继承BaseActivity)
+    │   └── LoginFragment.kt          # 登录Fragment(继承BaseFragment)
+    └── viewmodel/
+        └── LoginViewModel.kt         # 登录ViewModel
+```
+
+## 五、优化亮点
+
+### 5.1 使用 reified 泛型,简化调用
+- ❌ **之前**:需要手动传入 `dataClass = LoginResponse::class.java`
+- ✅ **现在**:自动推断类型,无需传入 `dataClass`
+
+### 5.2 统一公共处理
+- ✅ **自动线程切换**:所有请求自动切换到 IO 线程
+- ✅ **统一错误处理**:网络异常、超时等自动处理并给出友好提示
+- ✅ **自动日志记录**:请求开始、成功、失败自动记录日志
+- ✅ **自定义日志标签**:子类可重写 `tag` 属性自定义日志标签
+
+### 5.3 网络异常友好提示
+- `SocketTimeoutException` → "请求超时,请检查网络连接"
+- `UnknownHostException` → "网络连接失败,请检查网络设置"
+- 其他异常 → 显示具体错误信息
+
+### 5.4 代码对比
+
+**优化前:**
+```kotlin
+suspend fun login(request: LoginRequest): Result<LoginResponse> {
+    return try {
+        val response = authApi.login(request)
+        ApiResponseParser.handleResponse(
+            response,
+            LoginResponse::class.java,
+            "登录失败"
+        )
+    } catch (e: Exception) {
+        Result.failure(e)
+    }
+}
+```
+
+**优化后:**
+```kotlin
+suspend fun login(request: LoginRequest): Result<LoginResponse> {
+    return executeRequest(
+        request = { authApi.login(request) },
+        errorMessage = "登录失败"
+    )
+}
+```
+
+## 六、优势
+
+✅ **代码简化**:统一的网络请求处理,减少重复代码 70%+  
+✅ **错误统一**:统一的错误处理逻辑,友好的错误提示  
+✅ **易于测试**:DataSource 和 Repository 可以轻松 Mock  
+✅ **符合架构**:完全符合 MVVM 四层架构设计  
+✅ **自动处理**:线程切换、日志记录、异常处理全自动  
+✅ **类型安全**:使用 reified 泛型,编译时类型检查  
+
+## 七、业务层基类设计
+
+### 7.1 BaseActivity 基类
+
+```kotlin
+package com.narutohuo.xindazhou.common.ui
+
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import androidx.viewbinding.ViewBinding
+import kotlinx.coroutines.launch
+import android.widget.Toast
+
+/**
+ * 基础 Activity
+ * 
+ * 封装通用功能:
+ * - ViewBinding 支持
+ * - 统一的加载状态管理
+ * - 统一的错误提示
+ * - 生命周期管理
+ * 
+ * 使用方式:
+ * ```kotlin
+ * class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+ *     override fun onCreate(savedInstanceState: Bundle?) {
+ *         super.onCreate(savedInstanceState)
+ *         // 你的代码
+ *     }
+ * }
+ * ```
+ */
+abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
+    
+    protected lateinit var binding: VB
+    protected abstract fun getViewBinding(): VB
+    
+    // 加载状态(子类可重写自定义实现)
+    protected open fun showLoading() {
+        // 默认实现:显示加载提示
+        // 子类可重写自定义加载UI
+    }
+    
+    protected open fun hideLoading() {
+        // 默认实现:隐藏加载提示
+        // 子类可重写自定义加载UI
+    }
+    
+    // 错误提示(子类可重写自定义实现)
+    protected open fun showError(message: String) {
+        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+    }
+    
+    protected open fun showSuccess(message: String) {
+        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
+    }
+    
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        binding = getViewBinding()
+        setContentView(binding.root)
+        initView()
+        initObserver()
+    }
+    
+    /**
+     * 初始化视图(子类实现)
+     */
+    protected open fun initView() {}
+    
+    /**
+     * 初始化观察者(子类实现)
+     */
+    protected open fun initObserver() {}
+    
+    /**
+     * 统一执行网络请求
+     * 
+     * 自动处理加载状态和错误提示
+     */
+    protected fun <T> executeRequest(
+        request: suspend () -> Result<T>,
+        onSuccess: (T) -> Unit,
+        onError: ((String) -> Unit)? = null,
+        showLoading: Boolean = true
+    ) {
+        lifecycleScope.launch {
+            try {
+                if (showLoading) showLoading()
+                
+                request()
+                    .onSuccess { data ->
+                        hideLoading()
+                        onSuccess(data)
+                    }
+                    .onFailure { throwable ->
+                        hideLoading()
+                        val errorMsg = throwable.message ?: "操作失败"
+                        if (onError != null) {
+                            onError(errorMsg)
+                        } else {
+                            showError(errorMsg)
+                        }
+                    }
+            } catch (e: Exception) {
+                hideLoading()
+                val errorMsg = e.message ?: "操作失败"
+                if (onError != null) {
+                    onError(errorMsg)
+                } else {
+                    showError(errorMsg)
+                }
+            }
+        }
+    }
+}
+```
+
+### 7.2 BaseFragment 基类
+
+```kotlin
+package com.narutohuo.xindazhou.common.ui
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import androidx.viewbinding.ViewBinding
+import kotlinx.coroutines.launch
+
+/**
+ * 基础 Fragment
+ * 
+ * 封装通用功能:
+ * - ViewBinding 支持
+ * - 统一的加载状态管理
+ * - 统一的错误提示
+ * - 生命周期管理
+ * 
+ * 使用方式:
+ * ```kotlin
+ * class LoginFragment : BaseFragment<FragmentLoginBinding>() {
+ *     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ *         super.onViewCreated(view, savedInstanceState)
+ *         // 你的代码
+ *     }
+ * }
+ * ```
+ */
+abstract class BaseFragment<VB : ViewBinding> : Fragment() {
+    
+    protected lateinit var binding: VB
+    protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB
+    
+    // 加载状态(子类可重写自定义实现)
+    protected open fun showLoading() {
+        // 默认实现:显示加载提示
+        // 子类可重写自定义加载UI
+    }
+    
+    protected open fun hideLoading() {
+        // 默认实现:隐藏加载提示
+        // 子类可重写自定义加载UI
+    }
+    
+    // 错误提示(子类可重写自定义实现)
+    protected open fun showError(message: String) {
+        Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
+    }
+    
+    protected open fun showSuccess(message: String) {
+        Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
+    }
+    
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        binding = getViewBinding(inflater, container)
+        return binding.root
+    }
+    
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        initView()
+        initObserver()
+    }
+    
+    /**
+     * 初始化视图(子类实现)
+     */
+    protected open fun initView() {}
+    
+    /**
+     * 初始化观察者(子类实现)
+     */
+    protected open fun initObserver() {}
+    
+    /**
+     * 统一执行网络请求
+     * 
+     * 自动处理加载状态和错误提示
+     */
+    protected fun <T> executeRequest(
+        request: suspend () -> Result<T>,
+        onSuccess: (T) -> Unit,
+        onError: ((String) -> Unit)? = null,
+        showLoading: Boolean = true
+    ) {
+        lifecycleScope.launch {
+            try {
+                if (showLoading) showLoading()
+                
+                request()
+                    .onSuccess { data ->
+                        hideLoading()
+                        onSuccess(data)
+                    }
+                    .onFailure { throwable ->
+                        hideLoading()
+                        val errorMsg = throwable.message ?: "操作失败"
+                        if (onError != null) {
+                            onError(errorMsg)
+                        } else {
+                            showError(errorMsg)
+                        }
+                    }
+            } catch (e: Exception) {
+                hideLoading()
+                val errorMsg = e.message ?: "操作失败"
+                if (onError != null) {
+                    onError(errorMsg)
+                } else {
+                    showError(errorMsg)
+                }
+            }
+        }
+    }
+}
+```
+
+## 八、业务层使用示例
+
+### 8.1 Activity 中使用(推荐使用 ViewModel)
+
+```kotlin
+package com.narutohuo.xindazhou.user.ui.login
+
+import androidx.lifecycle.ViewModelProvider
+import com.narutohuo.xindazhou.common.ui.BaseActivity
+import com.narutohuo.xindazhou.user.databinding.ActivityLoginBinding
+import com.narutohuo.xindazhou.user.ui.viewmodel.LoginViewModel
+import com.narutohuo.xindazhou.user.ui.viewmodel.LoginViewModelFactory
+
+/**
+ * 登录 Activity
+ */
+class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+    
+    private lateinit var viewModel: LoginViewModel
+    
+    override fun getViewBinding(): ActivityLoginBinding {
+        return ActivityLoginBinding.inflate(layoutInflater)
+    }
+    
+    override fun initView() {
+        // 初始化 ViewModel
+        viewModel = ViewModelProvider(
+            this,
+            LoginViewModelFactory(application)
+        )[LoginViewModel::class.java]
+        
+        // 设置点击事件
+        binding.btnLogin.setOnClickListener {
+            val mobile = binding.etMobile.text.toString()
+            val password = binding.etPassword.text.toString()
+            viewModel.login(mobile, password)
+        }
+    }
+    
+    override fun initObserver() {
+        // 观察登录状态
+        lifecycleScope.launch {
+            viewModel.loginState.collect { state ->
+                when (state) {
+                    is LoginState.Idle -> {}
+                    is LoginState.Loading -> showLoading()
+                    is LoginState.Success -> {
+                        hideLoading()
+                        showSuccess(state.message)
+                        // 跳转到主页
+                    }
+                    is LoginState.Error -> {
+                        hideLoading()
+                        showError(state.message)
+                    }
+                }
+            }
+        }
+    }
+}
+```
+
+### 8.2 Fragment 中使用(推荐使用 ViewModel)
+
+```kotlin
+package com.narutohuo.xindazhou.user.ui.login
+
+import androidx.fragment.app.viewModels
+import com.narutohuo.xindazhou.common.ui.BaseFragment
+import com.narutohuo.xindazhou.user.databinding.FragmentLoginBinding
+import com.narutohuo.xindazhou.user.ui.viewmodel.LoginViewModel
+
+/**
+ * 登录 Fragment
+ */
+class LoginFragment : BaseFragment<FragmentLoginBinding>() {
+    
+    private val viewModel: LoginViewModel by viewModels { 
+        LoginViewModelFactory(requireActivity().application) 
+    }
+    
+    override fun getViewBinding(
+        inflater: LayoutInflater,
+        container: ViewGroup?
+    ): FragmentLoginBinding {
+        return FragmentLoginBinding.inflate(inflater, container, false)
+    }
+    
+    override fun initView() {
+        // 设置点击事件
+        binding.btnLogin.setOnClickListener {
+            val mobile = binding.etMobile.text.toString()
+            val password = binding.etPassword.text.toString()
+            viewModel.login(mobile, password)
+        }
+    }
+    
+    override fun initObserver() {
+        // 观察登录状态
+        lifecycleScope.launch {
+            viewModel.loginState.collect { state ->
+                when (state) {
+                    is LoginState.Idle -> {}
+                    is LoginState.Loading -> showLoading()
+                    is LoginState.Success -> {
+                        hideLoading()
+                        showSuccess(state.message)
+                    }
+                    is LoginState.Error -> {
+                        hideLoading()
+                        showError(state.message)
+                    }
+                }
+            }
+        }
+    }
+}
+```
+
+### 8.3 Activity 中直接使用 Repository(简单场景)
+
+```kotlin
+package com.narutohuo.xindazhou.user.ui.login
+
+import com.narutohuo.xindazhou.common.ui.BaseActivity
+import com.narutohuo.xindazhou.user.databinding.ActivityLoginBinding
+import com.narutohuo.xindazhou.user.data.repository.AuthRepository
+
+/**
+ * 登录 Activity(直接使用 Repository)
+ * 
+ * 适用于简单的业务场景,不推荐用于复杂业务
+ */
+class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+    
+    private val authRepository = AuthRepository(...) // 通过依赖注入获取
+    
+    override fun getViewBinding(): ActivityLoginBinding {
+        return ActivityLoginBinding.inflate(layoutInflater)
+    }
+    
+    override fun initView() {
+        binding.btnLogin.setOnClickListener {
+            val mobile = binding.etMobile.text.toString()
+            val password = binding.etPassword.text.toString()
+            
+            // 使用基类的 executeRequest 方法
+            executeRequest(
+                request = { authRepository.login(mobile, password) },
+                onSuccess = { response ->
+                    showSuccess("登录成功")
+                    // 处理登录成功
+                },
+                onError = { errorMsg ->
+                    // 默认会调用 showError,也可以自定义处理
+                }
+            )
+        }
+    }
+}
+```
+
+## 九、完整架构层次
+
+```
+业务层(UI层)
+├── BaseActivity / BaseFragment          # 基础UI组件(新增)
+│   ├── ViewBinding 支持
+│   ├── 加载状态管理
+│   ├── 错误提示
+│   └── executeRequest() 便捷方法
+│
+├── Activity / Fragment                  # 业务UI组件
+│   └── 继承 BaseActivity / BaseFragment
+│
+└── ViewModel                            # 视图模型
+    └── 使用 Repository
+
+数据层
+├── Repository                           # 数据仓库
+│   └── 继承 BaseRepository(可选)
+│
+├── RemoteDataSource                     # 远程数据源
+│   └── 继承 BaseRemoteDataSource
+│
+└── ApiServiceFactory                    # API服务工厂
+```
+
+## 十、使用注意事项
+
+1. **必须继承基类**:
+   - 所有 RemoteDataSource 必须继承 `BaseRemoteDataSource`
+   - 所有 Activity 建议继承 `BaseActivity`
+   - 所有 Fragment 建议继承 `BaseFragment`
+
+2. **使用 executeRequest**:
+   - DataSource 层:使用 `executeRequest()` 处理非空数据
+   - DataSource 层:使用 `executeNullableRequest()` 处理可空数据
+   - Activity/Fragment 层:使用 `executeRequest()` 统一处理请求(如果直接使用 Repository)
+
+3. **推荐使用 ViewModel**:
+   - 复杂业务推荐使用 ViewModel + StateFlow
+   - 简单业务可以直接使用基类的 `executeRequest()` 方法
+
+4. **自定义实现**:
+   - 可以重写 `showLoading()`, `hideLoading()`, `showError()` 等方法自定义UI
+   - 可以重写 `tag` 属性自定义日志标签
+
+5. **CommonResult**:已在 `ApiResponseParser.kt` 中定义,无需重复定义
+

Fichier diff supprimé car celui-ci est trop grand
+ 926 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/网络请求封装方案.html


+ 277 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/permission/PermissionHelper.kt

@@ -0,0 +1,277 @@
+package com.narutohuo.xindazhou.common.permission
+
+import android.app.Activity
+import android.content.Context
+import android.content.pm.PackageManager
+import androidx.activity.ComponentActivity
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.content.ContextCompat
+import androidx.fragment.app.Fragment
+import com.narutohuo.xindazhou.common.dialog.DialogHelper
+
+/**
+ * 权限管理封装
+ * 
+ * 统一封装 Android 权限请求功能,提供便捷的权限管理方式
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 Activity 中注册权限请求
+ * private val requestPermissionLauncher = PermissionHelper.registerRequestPermission(this) { permissions ->
+ *     // 处理权限请求结果
+ *     permissions.forEach { (permission, granted) ->
+ *         if (granted) {
+ *             // 权限已授予
+ *         } else {
+ *             // 权限被拒绝
+ *         }
+ *     }
+ * }
+ * 
+ * // 请求单个权限
+ * PermissionHelper.requestPermission(activity, Manifest.permission.CAMERA) { granted ->
+ *     if (granted) {
+ *         // 权限已授予
+ *     }
+ * }
+ * 
+ * // 请求多个权限
+ * PermissionHelper.requestPermissions(activity, arrayOf(
+ *     Manifest.permission.CAMERA,
+ *     Manifest.permission.READ_EXTERNAL_STORAGE
+ * )) { permissions ->
+ *     // 处理权限结果
+ * }
+ * ```
+ */
+object PermissionHelper {
+    
+    /**
+     * 注册权限请求器(Activity)
+     * 
+     * @param activity ComponentActivity 实例
+     * @param onResult 权限请求结果回调(Map<权限名, 是否授予>)
+     * @return ActivityResultLauncher,调用 launch() 启动权限请求
+     */
+    fun registerRequestPermission(
+        activity: ComponentActivity,
+        onResult: (Map<String, Boolean>) -> Unit
+    ): ActivityResultLauncher<Array<String>> {
+        return activity.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
+            onResult(permissions)
+        }
+    }
+    
+    /**
+     * 注册权限请求器(Fragment)
+     * 
+     * @param fragment Fragment 实例
+     * @param onResult 权限请求结果回调
+     * @return ActivityResultLauncher
+     */
+    fun registerRequestPermission(
+        fragment: Fragment,
+        onResult: (Map<String, Boolean>) -> Unit
+    ): ActivityResultLauncher<Array<String>> {
+        return fragment.registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
+            onResult(permissions)
+        }
+    }
+    
+    /**
+     * 注册单个权限请求器(Activity)
+     * 
+     * @param activity ComponentActivity 实例
+     * @param onResult 权限请求结果回调(是否授予)
+     * @return ActivityResultLauncher,调用 launch() 启动权限请求
+     */
+    fun registerRequestSinglePermission(
+        activity: ComponentActivity,
+        onResult: (Boolean) -> Unit
+    ): ActivityResultLauncher<String> {
+        return activity.registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
+            onResult(granted)
+        }
+    }
+    
+    /**
+     * 注册单个权限请求器(Fragment)
+     * 
+     * @param fragment Fragment 实例
+     * @param onResult 权限请求结果回调
+     * @return ActivityResultLauncher
+     */
+    fun registerRequestSinglePermission(
+        fragment: Fragment,
+        onResult: (Boolean) -> Unit
+    ): ActivityResultLauncher<String> {
+        return fragment.registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
+            onResult(granted)
+        }
+    }
+    
+    /**
+     * 检查权限是否已授予
+     * 
+     * @param context 上下文
+     * @param permission 权限名称
+     * @return true 表示已授予,false 表示未授予
+     */
+    fun checkPermission(context: Context, permission: String): Boolean {
+        return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
+    }
+    
+    /**
+     * 检查多个权限是否已授予
+     * 
+     * @param context 上下文
+     * @param permissions 权限名称数组
+     * @return Map<权限名, 是否授予>
+     */
+    fun checkPermissions(context: Context, permissions: Array<String>): Map<String, Boolean> {
+        return permissions.associateWith { permission ->
+            checkPermission(context, permission)
+        }
+    }
+    
+    /**
+     * 检查所有权限是否都已授予
+     * 
+     * @param context 上下文
+     * @param permissions 权限名称数组
+     * @return true 表示所有权限都已授予
+     */
+    fun checkAllPermissionsGranted(context: Context, permissions: Array<String>): Boolean {
+        return permissions.all { checkPermission(context, it) }
+    }
+    
+    /**
+     * 请求单个权限(简化版,需要先注册)
+     * 
+     * @param launcher ActivityResultLauncher(通过 registerRequestSinglePermission 获取)
+     * @param permission 权限名称
+     */
+    fun requestPermission(launcher: ActivityResultLauncher<String>, permission: String) {
+        launcher.launch(permission)
+    }
+    
+    /**
+     * 请求多个权限(简化版,需要先注册)
+     * 
+     * @param launcher ActivityResultLauncher(通过 registerRequestPermission 获取)
+     * @param permissions 权限名称数组
+     */
+    fun requestPermissions(launcher: ActivityResultLauncher<Array<String>>, permissions: Array<String>) {
+        launcher.launch(permissions)
+    }
+    
+    /**
+     * 检查权限是否应该显示说明(用户之前拒绝过)
+     * 
+     * @param activity Activity 实例
+     * @param permission 权限名称
+     * @return true 表示应该显示说明
+     */
+    fun shouldShowRequestPermissionRationale(activity: Activity, permission: String): Boolean {
+        return activity.shouldShowRequestPermissionRationale(permission)
+    }
+    
+    /**
+     * 检查权限是否应该显示说明(Fragment)
+     * 
+     * @param fragment Fragment 实例
+     * @param permission 权限名称
+     * @return true 表示应该显示说明
+     */
+    fun shouldShowRequestPermissionRationale(fragment: Fragment, permission: String): Boolean {
+        return fragment.shouldShowRequestPermissionRationale(permission)
+    }
+    
+    /**
+     * 请求权限(带说明对话框,如果用户之前拒绝过)
+     * 
+     * @param activity ComponentActivity 实例
+     * @param permission 权限名称
+     * @param rationale 权限说明文本
+     * @param onRationaleConfirmed 用户确认说明后的回调(在这里执行实际权限请求)
+     * @param onResult 权限请求结果回调
+     */
+    fun requestPermissionWithRationale(
+        activity: ComponentActivity,
+        permission: String,
+        rationale: String,
+        onRationaleConfirmed: () -> Unit,
+        onResult: (Boolean) -> Unit
+    ) {
+        if (checkPermission(activity, permission)) {
+            // 权限已授予
+            onResult(true)
+            return
+        }
+        
+        if (shouldShowRequestPermissionRationale(activity, permission)) {
+            // 用户之前拒绝过,显示说明对话框
+            com.narutohuo.xindazhou.common.dialog.DialogHelper.showConfirm(
+                activity,
+                "权限说明",
+                rationale,
+                confirmText = "确定",
+                cancelText = "取消",
+                onConfirm = {
+                    onRationaleConfirmed()
+                },
+                onCancel = {
+                    onResult(false)
+                }
+            )
+        } else {
+            // 直接请求权限
+            onRationaleConfirmed()
+        }
+    }
+    
+    /**
+     * 请求权限(带说明对话框,Fragment 版本)
+     * 
+     * @param fragment Fragment 实例
+     * @param permission 权限名称
+     * @param rationale 权限说明文本
+     * @param onRationaleConfirmed 用户确认说明后的回调
+     * @param onResult 权限请求结果回调
+     */
+    fun requestPermissionWithRationale(
+        fragment: Fragment,
+        permission: String,
+        rationale: String,
+        onRationaleConfirmed: () -> Unit,
+        onResult: (Boolean) -> Unit
+    ) {
+        if (checkPermission(fragment.requireContext(), permission)) {
+            // 权限已授予
+            onResult(true)
+            return
+        }
+        
+        if (shouldShowRequestPermissionRationale(fragment, permission)) {
+            // 用户之前拒绝过,显示说明对话框
+            DialogHelper.showConfirm(
+                fragment.requireContext(),
+                "权限说明",
+                rationale,
+                confirmText = "确定",
+                cancelText = "取消",
+                onConfirm = {
+                    onRationaleConfirmed()
+                },
+                onCancel = {
+                    onResult(false)
+                }
+            )
+        } else {
+            // 直接请求权限
+            onRationaleConfirmed()
+        }
+    }
+}
+

+ 214 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/router/RouterHelper.kt

@@ -0,0 +1,214 @@
+package com.narutohuo.xindazhou.common.router
+
+import android.app.Activity
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import com.alibaba.android.arouter.launcher.ARouter
+
+/**
+ * 路由管理器
+ * 
+ * 统一封装 ARouter 路由跳转,提供便捷的路由调用方式
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 基础跳转
+ * RouterHelper.navigate("/user/profile")
+ * 
+ * // 带参数跳转
+ * RouterHelper.navigate("/user/profile") {
+ *     putString("userId", "123")
+ *     putString("userName", "张三")
+ * }
+ * 
+ * // 带结果回调跳转
+ * RouterHelper.navigateForResult(activity, "/user/select", 1001) {
+ *     putString("type", "user")
+ * }
+ * 
+ * // 获取服务
+ * val pushService = RouterHelper.getService<IPushService>()
+ * ```
+ */
+object RouterHelper {
+    
+    /**
+     * 基础跳转
+     * 
+     * @param path 路由路径
+     */
+    fun navigate(path: String) {
+        ARouter.getInstance()
+            .build(path)
+            .navigation()
+    }
+    
+    /**
+     * 带参数跳转
+     * 
+     * @param path 路由路径
+     * @param params 参数构建器
+     */
+    fun navigate(path: String, params: (Bundle.() -> Unit)? = null) {
+        val postcard = ARouter.getInstance().build(path)
+        
+        params?.let {
+            val bundle = Bundle().apply(it)
+            postcard.with(bundle)
+        }
+        
+        postcard.navigation()
+    }
+    
+    /**
+     * 带参数跳转(使用 RouteBuilder)
+     * 
+     * @param path 路由路径
+     * @return RouteBuilder,用于链式调用
+     */
+    fun build(path: String): RouteBuilder {
+        return RouteBuilder(path)
+    }
+    
+    /**
+     * 带结果回调跳转(Activity)
+     * 
+     * @param activity Activity 实例
+     * @param path 路由路径
+     * @param requestCode 请求码
+     * @param params 参数构建器(可选)
+     */
+    fun navigateForResult(
+        activity: Activity,
+        path: String,
+        requestCode: Int,
+        params: (Bundle.() -> Unit)? = null
+    ) {
+        val postcard = ARouter.getInstance().build(path)
+        
+        params?.let {
+            val bundle = Bundle().apply(it)
+            postcard.with(bundle)
+        }
+        
+        postcard.navigation(activity, requestCode)
+    }
+    
+    /**
+     * 带结果回调跳转(Fragment)
+     * 
+     * @param fragment Fragment 实例
+     * @param path 路由路径
+     * @param requestCode 请求码
+     * @param params 参数构建器(可选)
+     */
+    fun navigateForResult(
+        fragment: Fragment,
+        path: String,
+        requestCode: Int,
+        params: (Bundle.() -> Unit)? = null
+    ) {
+        val postcard = ARouter.getInstance().build(path)
+        
+        params?.let {
+            val bundle = Bundle().apply(it)
+            postcard.with(bundle)
+        }
+        
+        fragment.startActivityForResult(postcard.navigation() as android.content.Intent, requestCode)
+    }
+    
+    /**
+     * 获取服务实例
+     * 
+     * @param path 服务路径(可选,如果为 null 则通过类型查找)
+     * @return 服务实例,如果未找到返回 null
+     */
+    inline fun <reified T> getService(path: String? = null): T? {
+        return try {
+            if (path != null) {
+                ARouter.getInstance().build(path).navigation() as? T
+            } else {
+                ARouter.getInstance().navigation(T::class.java)
+            }
+        } catch (e: Exception) {
+            null
+        }
+    }
+    
+    /**
+     * 路由构建器(链式调用)
+     */
+    class RouteBuilder(private val path: String) {
+        private val bundle = Bundle()
+        
+        /**
+         * 添加字符串参数
+         */
+        fun withString(key: String, value: String): RouteBuilder {
+            bundle.putString(key, value)
+            return this
+        }
+        
+        /**
+         * 添加整数参数
+         */
+        fun withInt(key: String, value: Int): RouteBuilder {
+            bundle.putInt(key, value)
+            return this
+        }
+        
+        /**
+         * 添加布尔参数
+         */
+        fun withBoolean(key: String, value: Boolean): RouteBuilder {
+            bundle.putBoolean(key, value)
+            return this
+        }
+        
+        /**
+         * 添加长整数参数
+         */
+        fun withLong(key: String, value: Long): RouteBuilder {
+            bundle.putLong(key, value)
+            return this
+        }
+        
+        /**
+         * 添加 Parcelable 参数
+         */
+        fun withParcelable(key: String, value: android.os.Parcelable): RouteBuilder {
+            bundle.putParcelable(key, value)
+            return this
+        }
+        
+        /**
+         * 添加 Serializable 参数
+         */
+        fun withSerializable(key: String, value: java.io.Serializable): RouteBuilder {
+            bundle.putSerializable(key, value)
+            return this
+        }
+        
+        /**
+         * 执行跳转
+         */
+        fun navigate() {
+            ARouter.getInstance()
+                .build(path)
+                .with(bundle)
+                .navigation()
+        }
+        
+        /**
+         * 执行跳转(带结果回调)
+         */
+        fun navigateForResult(activity: Activity, requestCode: Int) {
+            ARouter.getInstance()
+                .build(path)
+                .with(bundle)
+                .navigation(activity, requestCode)
+        }
+    }
+}
+

+ 158 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/socketio/SocketIOManager.kt

@@ -0,0 +1,158 @@
+package com.narutohuo.xindazhou.common.socketio
+
+import android.app.Application
+import androidx.lifecycle.LifecycleObserver
+import androidx.lifecycle.OnLifecycleEvent
+import com.narutohuo.xindazhou.common.auth.AuthManager
+import com.narutohuo.xindazhou.common.config.ServerConfigManager
+import com.narutohuo.xindazhou.common.log.LogHelper
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/**
+ * SocketIO 连接管理器
+ * 
+ * 负责自动管理 SocketIO 连接的生命周期:
+ * - App 进入前台时自动重连
+ * - Token 过期时自动刷新并重连
+ * - 监听应用生命周期
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 Application.onCreate() 中初始化
+ * SocketIOManager.init(application)
+ * ```
+ */
+object SocketIOManager : LifecycleObserver {
+    
+    private const val TAG = "SocketIOManager"
+    
+    private var application: Application? = null
+    private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
+    private var initialized = false
+    
+    /**
+     * 初始化 SocketIO 管理器
+     * 
+     * 需要在 Application.onCreate() 中调用
+     * 会自动注册生命周期监听
+     * 
+     * @param app Application 实例
+     */
+    fun init(app: Application) {
+        if (initialized) {
+            LogHelper.w(TAG, "SocketIOManager 已初始化,跳过重复初始化")
+            return
+        }
+        
+        application = app
+        initialized = true
+        
+        // 注册 App 生命周期监听(使用反射,避免强依赖)
+        try {
+            val processLifecycleOwnerClass = Class.forName("androidx.lifecycle.ProcessLifecycleOwner")
+            val getMethod = processLifecycleOwnerClass.getMethod("get")
+            val lifecycleOwner = getMethod.invoke(null)
+            val lifecycleField = lifecycleOwner.javaClass.getMethod("getLifecycle")
+            val lifecycle = lifecycleField.invoke(lifecycleOwner)
+            val addObserverMethod = lifecycle.javaClass.getMethod("addObserver", LifecycleObserver::class.java)
+            addObserverMethod.invoke(lifecycle, this)
+            LogHelper.d(TAG, "SocketIOManager 初始化完成")
+        } catch (e: Exception) {
+            LogHelper.e(TAG, "注册生命周期监听失败", e)
+        }
+    }
+    
+    /**
+     * App 进入前台时调用
+     * 
+     * 检查 SocketIO 连接状态,如果断开则自动重连
+     * 如果连接失败(可能是 Token 过期),会自动刷新 Token 后重连
+     */
+    @OnLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_START)
+    fun onAppForeground() {
+        LogHelper.d(TAG, "App 进入前台,检查 SocketIO 连接状态")
+        
+        // 检查是否已登录
+        if (!AuthManager.isLoggedIn()) {
+            LogHelper.d(TAG, "用户未登录,跳过 SocketIO 重连")
+            return
+        }
+        
+        // 检查连接状态,如果断开则重连
+        appScope.launch {
+            try {
+                // 使用反射获取 SocketIORepository,避免强依赖
+                val factoryClass = Class.forName("com.narutohuo.xindazhou.socketio.factory.SocketIORepositoryFactory")
+                val getInstanceMethod = factoryClass.getMethod("getInstance")
+                val socketIORepository = getInstanceMethod.invoke(null)
+                
+                val socketUrl = ServerConfigManager.getSocketIOUrl()
+                var token = AuthManager.getAccessToken()
+                
+                if (token.isNullOrEmpty()) {
+                    LogHelper.w(TAG, "Token 为空,无法重连 SocketIO")
+                    return@launch
+                }
+                
+                // 检查是否已连接
+                val isConnectedMethod = socketIORepository.javaClass.getMethod("isConnected")
+                val isConnected = isConnectedMethod.invoke(socketIORepository) as Boolean
+                
+                if (isConnected) {
+                    LogHelper.d(TAG, "SocketIO 已连接,无需重连")
+                    return@launch
+                }
+                
+                // 先尝试连接
+                LogHelper.d(TAG, "SocketIO 未连接,开始重连...")
+                val checkAndReconnectMethod = socketIORepository.javaClass.getMethod(
+                    "checkAndReconnect",
+                    String::class.java,
+                    String::class.java
+                )
+                checkAndReconnectMethod.invoke(socketIORepository, socketUrl, token)
+                
+                // 等待 2 秒,检查连接是否成功
+                delay(2000)
+                
+                // 如果连接仍然失败,可能是 Token 过期,尝试刷新 Token 后重连
+                val isConnectedAfter = isConnectedMethod.invoke(socketIORepository) as Boolean
+                if (!isConnectedAfter) {
+                    LogHelper.d(TAG, "连接失败,可能是 Token 过期,尝试刷新 Token 后重连")
+                    val refreshedToken = AuthManager.refreshTokenIfNeeded()
+                    if (!refreshedToken.isNullOrEmpty() && refreshedToken != token) {
+                        LogHelper.d(TAG, "Token 已刷新,使用新 Token 重连")
+                        checkAndReconnectMethod.invoke(socketIORepository, socketUrl, refreshedToken)
+                    } else {
+                        LogHelper.w(TAG, "Token 刷新失败或未刷新,无法重连")
+                    }
+                } else {
+                    LogHelper.d(TAG, "SocketIO 重连成功")
+                }
+            } catch (e: ClassNotFoundException) {
+                LogHelper.d(TAG, "SocketIO 模块未引入,跳过重连")
+            } catch (e: Exception) {
+                LogHelper.e(TAG, "SocketIO 重连失败", e)
+            }
+        }
+    }
+    
+    /**
+     * App 进入后台时调用
+     * 
+     * 注意:通常不断开 SocketIO 连接,因为:
+     * 1. SocketIO 连接是轻量级的
+     * 2. 用户可能需要在后台接收消息
+     * 3. 系统会在内存不足时自动清理
+     */
+    @OnLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_STOP)
+    fun onAppBackground() {
+        LogHelper.d(TAG, "App 进入后台")
+        // 不断开连接,保持连接以便接收消息
+    }
+}
+

+ 181 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/storage/StorageManager.kt

@@ -0,0 +1,181 @@
+package com.narutohuo.xindazhou.common.storage
+
+import com.alibaba.android.arouter.launcher.ARouter
+import com.narutohuo.xindazhou.core.log.ILog
+import com.narutohuo.xindazhou.core.storage.IStorage
+
+/**
+ * 存储管理器
+ * 
+ * 业务层封装,提供便捷的存储管理方式
+ * 内部使用 IStorage 接口(由 app 模块实现)
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 键值存储
+ * StorageManager.putString("user_name", "张三")
+ * val userName = StorageManager.getString("user_name", "")
+ * StorageManager.putInt("user_age", 25)
+ * val userAge = StorageManager.getInt("user_age", 0)
+ * 
+ * // 文件存储
+ * StorageManager.saveFile("/path/to/file.txt", data)
+ * val fileData = StorageManager.readFile("/path/to/file.txt")
+ * StorageManager.deleteFile("/path/to/file.txt")
+ * 
+ * // 清除数据
+ * StorageManager.remove("user_name")
+ * StorageManager.clear()
+ * ```
+ */
+object StorageManager {
+    
+    private const val TAG = "StorageManager"
+    
+    /**
+     * 获取 IStorage 实现实例
+     * 通过 ARouter 获取,如果未注册则返回 null
+     */
+    private fun getStorage(): IStorage? {
+        return try {
+            ARouter.getInstance().navigation(IStorage::class.java)
+        } catch (e: Exception) {
+            ILog.w(TAG, "IStorage 未注册,请确保 app 模块已实现并注册 IStorage")
+            null
+        }
+    }
+    
+    // ========== 键值存储 ==========
+    
+    /**
+     * 保存字符串
+     * 
+     * @param key 键
+     * @param value 值
+     */
+    fun putString(key: String, value: String) {
+        getStorage()?.putString(key, value) ?: run {
+            ILog.w(TAG, "putString 失败:IStorage 未注册")
+        }
+    }
+    
+    /**
+     * 获取字符串
+     * 
+     * @param key 键
+     * @param defaultValue 默认值
+     * @return 值
+     */
+    fun getString(key: String, defaultValue: String = ""): String {
+        return getStorage()?.getString(key, defaultValue) ?: defaultValue
+    }
+    
+    /**
+     * 保存整数
+     * 
+     * @param key 键
+     * @param value 值
+     */
+    fun putInt(key: String, value: Int) {
+        getStorage()?.putInt(key, value) ?: run {
+            ILog.w(TAG, "putInt 失败:IStorage 未注册")
+        }
+    }
+    
+    /**
+     * 获取整数
+     * 
+     * @param key 键
+     * @param defaultValue 默认值
+     * @return 值
+     */
+    fun getInt(key: String, defaultValue: Int = 0): Int {
+        return getStorage()?.getInt(key, defaultValue) ?: defaultValue
+    }
+    
+    /**
+     * 保存布尔值
+     * 
+     * @param key 键
+     * @param value 值
+     */
+    fun putBoolean(key: String, value: Boolean) {
+        getStorage()?.putBoolean(key, value) ?: run {
+            ILog.w(TAG, "putBoolean 失败:IStorage 未注册")
+        }
+    }
+    
+    /**
+     * 获取布尔值
+     * 
+     * @param key 键
+     * @param defaultValue 默认值
+     * @return 值
+     */
+    fun getBoolean(key: String, defaultValue: Boolean = false): Boolean {
+        return getStorage()?.getBoolean(key, defaultValue) ?: defaultValue
+    }
+    
+    /**
+     * 删除指定键
+     * 
+     * @param key 键
+     */
+    fun remove(key: String) {
+        getStorage()?.remove(key) ?: run {
+            ILog.w(TAG, "remove 失败:IStorage 未注册")
+        }
+    }
+    
+    /**
+     * 清除所有数据
+     */
+    fun clear() {
+        getStorage()?.clear() ?: run {
+            ILog.w(TAG, "clear 失败:IStorage 未注册")
+        }
+    }
+    
+    // ========== 文件存储 ==========
+    
+    /**
+     * 保存文件
+     * 
+     * @param path 文件路径
+     * @param data 文件数据
+     * @return true 表示保存成功
+     */
+    fun saveFile(path: String, data: ByteArray): Boolean {
+        return getStorage()?.saveFile(path, data) ?: run {
+            ILog.w(TAG, "saveFile 失败:IStorage 未注册")
+            false
+        }
+    }
+    
+    /**
+     * 读取文件
+     * 
+     * @param path 文件路径
+     * @return 文件数据,如果文件不存在或读取失败返回 null
+     */
+    fun readFile(path: String): ByteArray? {
+        return getStorage()?.readFile(path) ?: run {
+            ILog.w(TAG, "readFile 失败:IStorage 未注册")
+            null
+        }
+    }
+    
+    /**
+     * 删除文件
+     * 
+     * @param path 文件路径
+     * @return true 表示删除成功
+     */
+    fun deleteFile(path: String): Boolean {
+        return getStorage()?.deleteFile(path) ?: run {
+            ILog.w(TAG, "deleteFile 失败:IStorage 未注册")
+            false
+        }
+    }
+}
+

+ 40 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/ActivityExtensions.kt

@@ -0,0 +1,40 @@
+package com.narutohuo.xindazhou.common.ui
+
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * 观察 StateFlow 的便捷扩展函数
+ * 
+ * 简化业务层代码,避免每次都写 lifecycleScope.launch { flow.collect { } }
+ * 
+ * 使用方式:
+ * ```kotlin
+ * override fun initObserver() {
+ *     observeStateFlow(viewModel.loginState) { state ->
+ *         when (state) {
+ *             is LoginState.Loading -> showLoading()
+ *             is LoginState.Success -> {
+ *                 hideLoading()
+ *                 showSuccess(state.message)
+ *             }
+ *             is LoginState.Error -> {
+ *                 hideLoading()
+ *                 showError(state.message)
+ *             }
+ *         }
+ *     }
+ * }
+ * ```
+ */
+fun <T> AppCompatActivity.observeStateFlow(
+    stateFlow: StateFlow<T>,
+    action: (T) -> Unit
+) {
+    lifecycleScope.launch {
+        stateFlow.collect { action(it) }
+    }
+}
+

+ 314 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/ActivityManager.kt

@@ -0,0 +1,314 @@
+package com.narutohuo.xindazhou.common.ui
+
+import android.app.Activity
+import android.app.ActivityManager
+import android.content.Context
+import android.content.Intent
+import androidx.appcompat.app.AppCompatActivity
+import java.util.Stack
+
+/**
+ * Activity 管理器
+ * 
+ * 统一管理 Activity 栈,提供 Activity 生命周期管理和导航功能
+ * 
+ * 功能:
+ * - Activity 栈管理(添加、移除、获取当前 Activity)
+ * - 关闭指定 Activity
+ * - 关闭所有 Activity
+ * - 退出应用
+ * - Activity 跳转封装
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 Application 中初始化
+ * class MyApplication : Application() {
+ *     override fun onCreate() {
+ *         super.onCreate()
+ *         ActivityManager.init(this)
+ *     }
+ * }
+ * 
+ * // 在 BaseActivity 中注册
+ * override fun onCreate(savedInstanceState: Bundle?) {
+ *     super.onCreate(savedInstanceState)
+ *     ActivityManager.addActivity(this)
+ * }
+ * 
+ * override fun onDestroy() {
+ *     super.onDestroy()
+ *     ActivityManager.removeActivity(this)
+ * }
+ * 
+ * // 使用
+ * ActivityManager.finishActivity(LoginActivity::class.java)
+ * ActivityManager.finishAll()
+ * ActivityManager.exitApp()
+ * ```
+ */
+object ActivityManager {
+    
+    private val activityStack: Stack<Activity> = Stack()
+    private var applicationContext: Context? = null
+    
+    /**
+     * 初始化(在 Application.onCreate() 中调用)
+     */
+    fun init(context: Context) {
+        applicationContext = context.applicationContext
+    }
+    
+    /**
+     * 添加 Activity 到栈
+     */
+    fun addActivity(activity: Activity) {
+        activityStack.push(activity)
+    }
+    
+    /**
+     * 从栈中移除 Activity
+     */
+    fun removeActivity(activity: Activity) {
+        activityStack.remove(activity)
+    }
+    
+    /**
+     * 获取当前 Activity(栈顶)
+     */
+    fun getCurrentActivity(): Activity? {
+        return if (activityStack.isEmpty()) null else activityStack.peek()
+    }
+    
+    /**
+     * 获取 Activity 栈大小
+     */
+    fun getStackSize(): Int {
+        return activityStack.size
+    }
+    
+    /**
+     * 关闭指定 Activity
+     */
+    fun finishActivity(activity: Activity) {
+        if (activityStack.contains(activity)) {
+            activityStack.remove(activity)
+            activity.finish()
+        }
+    }
+    
+    /**
+     * 关闭指定类型的 Activity
+     */
+    fun finishActivity(clazz: Class<out Activity>) {
+        val iterator = activityStack.iterator()
+        while (iterator.hasNext()) {
+            val activity = iterator.next()
+            if (activity.javaClass == clazz) {
+                iterator.remove()
+                activity.finish()
+                break
+            }
+        }
+    }
+    
+    /**
+     * 关闭所有指定类型的 Activity
+     */
+    fun finishAllActivities(clazz: Class<out Activity>) {
+        val iterator = activityStack.iterator()
+        while (iterator.hasNext()) {
+            val activity = iterator.next()
+            if (activity.javaClass == clazz) {
+                iterator.remove()
+                activity.finish()
+            }
+        }
+    }
+    
+    /**
+     * 关闭除指定 Activity 外的所有 Activity
+     */
+    fun finishAllExcept(activity: Activity) {
+        val iterator = activityStack.iterator()
+        while (iterator.hasNext()) {
+            val current = iterator.next()
+            if (current != activity) {
+                iterator.remove()
+                current.finish()
+            }
+        }
+    }
+    
+    /**
+     * 关闭除指定类型外的所有 Activity
+     */
+    fun finishAllExcept(clazz: Class<out Activity>) {
+        val iterator = activityStack.iterator()
+        while (iterator.hasNext()) {
+            val activity = iterator.next()
+            if (activity.javaClass != clazz) {
+                iterator.remove()
+                activity.finish()
+            }
+        }
+    }
+    
+    /**
+     * 关闭所有 Activity
+     */
+    fun finishAll() {
+        while (!activityStack.isEmpty()) {
+            val activity = activityStack.pop()
+            activity.finish()
+        }
+    }
+    
+    /**
+     * 退出应用
+     */
+    fun exitApp() {
+        finishAll()
+        // 杀死进程
+        android.os.Process.killProcess(android.os.Process.myPid())
+        System.exit(0)
+    }
+    
+    /**
+     * 判断指定 Activity 是否存在
+     */
+    fun isActivityExist(clazz: Class<out Activity>): Boolean {
+        return activityStack.any { it.javaClass == clazz }
+    }
+    
+    /**
+     * 获取指定类型的 Activity
+     */
+    fun getActivity(clazz: Class<out Activity>): Activity? {
+        return activityStack.find { it.javaClass == clazz }
+    }
+    
+    /**
+     * 返回到指定 Activity
+     */
+    fun backToActivity(clazz: Class<out Activity>) {
+        val iterator = activityStack.iterator()
+        val activitiesToFinish = mutableListOf<Activity>()
+        var found = false
+        
+        while (iterator.hasNext()) {
+            val activity = iterator.next()
+            if (activity.javaClass == clazz) {
+                found = true
+                break
+            } else {
+                activitiesToFinish.add(activity)
+            }
+        }
+        
+        if (found) {
+            activitiesToFinish.forEach { activity ->
+                activityStack.remove(activity)
+                activity.finish()
+            }
+        }
+    }
+    
+    /**
+     * 启动 Activity(封装 Intent)
+     */
+    fun startActivity(context: Context, clazz: Class<out Activity>) {
+        val intent = Intent(context, clazz)
+        if (context !is Activity) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        }
+        context.startActivity(intent)
+    }
+    
+    /**
+     * 启动 Activity(带参数)
+     */
+    fun startActivity(
+        context: Context,
+        clazz: Class<out Activity>,
+        block: Intent.() -> Unit
+    ) {
+        val intent = Intent(context, clazz).apply(block)
+        if (context !is Activity) {
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        }
+        context.startActivity(intent)
+    }
+    
+    /**
+     * 启动 Activity 并关闭当前
+     */
+    fun startActivityAndFinish(
+        context: Context,
+        clazz: Class<out Activity>
+    ) {
+        startActivity(context, clazz)
+        if (context is Activity) {
+            context.finish()
+        }
+    }
+    
+    /**
+     * 启动 Activity 并关闭当前(带参数)
+     */
+    fun startActivityAndFinish(
+        context: Context,
+        clazz: Class<out Activity>,
+        block: Intent.() -> Unit
+    ) {
+        startActivity(context, clazz, block)
+        if (context is Activity) {
+            context.finish()
+        }
+    }
+    
+    /**
+     * 启动 Activity 并清空栈
+     */
+    fun startActivityAndClearTask(
+        context: Context,
+        clazz: Class<out Activity>
+    ) {
+        val intent = Intent(context, clazz).apply {
+            addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
+        }
+        context.startActivity(intent)
+        if (context is Activity) {
+            context.finish()
+        }
+    }
+    
+    /**
+     * 启动 Activity 并清空栈(带参数)
+     */
+    fun startActivityAndClearTask(
+        context: Context,
+        clazz: Class<out Activity>,
+        block: Intent.() -> Unit
+    ) {
+        val intent = Intent(context, clazz).apply {
+            addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
+            block()
+        }
+        context.startActivity(intent)
+        if (context is Activity) {
+            context.finish()
+        }
+    }
+    
+    /**
+     * 重启应用
+     */
+    fun restartApp() {
+        val context = applicationContext ?: return
+        val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
+        intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
+        finishAll()
+        context.startActivity(intent)
+    }
+}
+

+ 489 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/BaseActivity.kt

@@ -0,0 +1,489 @@
+package com.narutohuo.xindazhou.common.ui
+
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
+import android.os.Bundle
+import android.view.KeyEvent
+import android.view.View
+import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import androidx.viewbinding.ViewBinding
+import com.narutohuo.xindazhou.common.dialog.DialogHelper
+import com.narutohuo.xindazhou.common.permission.PermissionHelper
+import kotlinx.coroutines.launch
+
+/**
+ * 基础 Activity
+ * 
+ * 封装 UI 通用功能:
+ * - ViewBinding 支持
+ * - ActivityManager 集成(自动注册/注销)
+ * - 屏幕方向统一管理(默认竖屏,子类可重写)
+ * - 统一的加载状态管理
+ * - 统一的错误提示
+ * - 权限请求封装
+ * - Activity 跳转封装
+ * - 返回键处理
+ * - 状态栏管理
+ * - 生命周期管理
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 默认竖屏
+ * class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+ *     override fun initView() {
+ *         binding.btnLogin.setOnClickListener {
+ *             startActivity<MainActivity>()
+ *         }
+ *     }
+ * }
+ * 
+ * // 横屏 Activity
+ * class VideoPlayerActivity : BaseActivity<ActivityVideoPlayerBinding>() {
+ *     override val screenOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+ *     
+ *     override fun onScreenOrientationChanged(orientation: Int) {
+ *         // 处理屏幕方向变更
+ *     }
+ * }
+ * 
+ * // 跟随传感器
+ * class CameraActivity : BaseActivity<ActivityCameraBinding>() {
+ *     override val screenOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR
+ * }
+ * ```
+ */
+abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity() {
+    
+    protected lateinit var binding: VB
+    protected abstract fun getViewBinding(): VB
+    
+    // 是否启用返回键拦截(默认 false)
+    protected open val enableBackKeyIntercept: Boolean = false
+    
+    // 是否启用沉浸式状态栏(默认 false)
+    protected open val enableImmersiveStatusBar: Boolean = false
+    
+    // 屏幕方向设置(子类可重写)
+    // 可选值:
+    // - ActivityInfo.SCREEN_ORIENTATION_PORTRAIT(竖屏,默认)
+    // - ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE(横屏)
+    // - ActivityInfo.SCREEN_ORIENTATION_SENSOR(跟随传感器)
+    // - ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED(跟随系统)
+    // - ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT(竖屏,但允许180度旋转)
+    // - ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE(横屏,但允许180度旋转)
+    // 
+    // 注意:
+    // 1. 如果设置为固定方向(PORTRAIT 或 LANDSCAPE),屏幕旋转时不会触发配置变更
+    // 2. 如果需要处理屏幕旋转,请使用 SENSOR 相关选项,并在 AndroidManifest 中声明 configChanges
+    // 3. 建议在 app 模块的 AndroidManifest.xml 中为所有 Activity 添加:
+    //    android:configChanges="orientation|keyboardHidden|screenSize"
+    protected open val screenOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+    
+    // 权限请求 Launcher
+    private var permissionLauncher: ActivityResultLauncher<Array<String>>? = null
+    
+    // Activity 结果 Launcher
+    private val activityResultLauncher = registerForActivityResult(
+        ActivityResultContracts.StartActivityForResult()
+    ) { result ->
+        onActivityResult(result.resultCode, result.data)
+    }
+    
+    // 加载状态(子类可重写自定义实现)
+    protected open fun showLoading() {
+        // 默认实现:显示加载提示
+        // 子类可重写自定义加载UI
+    }
+    
+    protected open fun hideLoading() {
+        // 默认实现:隐藏加载提示
+        // 子类可重写自定义加载UI
+    }
+    
+    // 错误提示(子类可重写自定义实现)
+    protected open fun showError(message: String) {
+        MessageHelper.showToast(binding.root, message)
+    }
+    
+    protected open fun showSuccess(message: String) {
+        MessageHelper.showToast(binding.root, message)
+    }
+    
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        
+        // 注册到 ActivityManager
+        ActivityManager.addActivity(this)
+        
+        // 设置屏幕方向(必须在 setContentView 之前设置)
+        requestedOrientation = screenOrientation
+        
+        // 初始化 ViewBinding
+        binding = getViewBinding()
+        setContentView(binding.root)
+        
+        // 初始化状态栏
+        if (enableImmersiveStatusBar) {
+            StatusBarHelper.setImmersiveStatusBar(this)
+        }
+        
+        // 初始化权限请求
+        initPermissionLauncher()
+        
+        // 初始化视图和观察者
+        initView()
+        initObserver()
+    }
+    
+    /**
+     * 配置变更处理(屏幕旋转、键盘显示等)
+     * 
+     * 注意:此方法只有在 AndroidManifest.xml 中声明了 configChanges 时才会被调用
+     * 建议在 app 模块的 AndroidManifest.xml 中为所有 Activity 添加:
+     * android:configChanges="orientation|keyboardHidden|screenSize"
+     * 
+     * 如果未声明 configChanges,屏幕旋转时会重建 Activity(onCreate 会重新调用)
+     */
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        super.onConfigurationChanged(newConfig)
+        
+        // 处理屏幕旋转等配置变更
+        onScreenOrientationChanged(newConfig.orientation)
+    }
+    
+    /**
+     * 屏幕方向变更回调(子类可重写)
+     * 
+     * 当屏幕方向改变时调用(需要 AndroidManifest 中声明 configChanges)
+     * 
+     * @param orientation Configuration.ORIENTATION_PORTRAIT(竖屏)或 Configuration.ORIENTATION_LANDSCAPE(横屏)
+     */
+    protected open fun onScreenOrientationChanged(orientation: Int) {
+        // 子类可重写处理屏幕方向变更
+        // 例如:重新布局、刷新数据等
+    }
+    
+    override fun onDestroy() {
+        super.onDestroy()
+        // 从 ActivityManager 中移除
+        ActivityManager.removeActivity(this)
+    }
+    
+    /**
+     * 初始化权限请求 Launcher
+     */
+    private fun initPermissionLauncher() {
+        permissionLauncher = registerForActivityResult(
+            ActivityResultContracts.RequestMultiplePermissions()
+        ) { permissions ->
+            onPermissionResult(permissions)
+        }
+    }
+    
+    /**
+     * 权限请求结果回调(子类可重写)
+     */
+    protected open fun onPermissionResult(permissions: Map<String, Boolean>) {
+        // 子类可重写处理权限结果
+    }
+    
+    /**
+     * Activity 结果回调(子类可重写)
+     */
+    protected open fun onActivityResult(resultCode: Int, data: Intent?) {
+        // 子类可重写处理 Activity 结果
+    }
+    
+    /**
+     * 初始化视图(子类实现)
+     */
+    protected open fun initView() {}
+    
+    /**
+     * 初始化观察者(子类实现)
+     */
+    protected open fun initObserver() {}
+    
+    // ========== Activity 跳转封装 ==========
+    
+    /**
+     * 启动 Activity(泛型方式)
+     */
+    fun <T : AppCompatActivity> startActivity(clazz: Class<T>) {
+        ActivityManager.startActivity(this, clazz)
+    }
+    
+    /**
+     * 启动 Activity(泛型方式,带参数)
+     */
+    fun <T : AppCompatActivity> startActivity(
+        clazz: Class<T>,
+        block: Intent.() -> Unit
+    ) {
+        ActivityManager.startActivity(this, clazz, block)
+    }
+    
+    /**
+     * 启动 Activity 并关闭当前
+     */
+    fun <T : AppCompatActivity> startActivityAndFinish(clazz: Class<T>) {
+        ActivityManager.startActivityAndFinish(this, clazz)
+    }
+    
+    /**
+     * 启动 Activity 并关闭当前(带参数)
+     */
+    fun <T : AppCompatActivity> startActivityAndFinish(
+        clazz: Class<T>,
+        block: Intent.() -> Unit
+    ) {
+        ActivityManager.startActivityAndFinish(this, clazz, block)
+    }
+    
+    /**
+     * 启动 Activity 并清空栈
+     */
+    fun <T : AppCompatActivity> startActivityAndClearTask(clazz: Class<T>) {
+        ActivityManager.startActivityAndClearTask(this, clazz)
+    }
+    
+    /**
+     * 启动 Activity 并清空栈(带参数)
+     */
+    fun <T : AppCompatActivity> startActivityAndClearTask(
+        clazz: Class<T>,
+        block: Intent.() -> Unit
+    ) {
+        ActivityManager.startActivityAndClearTask(this, clazz, block)
+    }
+    
+    /**
+     * 启动 Activity 并获取结果
+     */
+    fun <T : AppCompatActivity> startActivityForResult(
+        clazz: Class<T>,
+        block: Intent.() -> Unit = {}
+    ) {
+        val intent = Intent(this, clazz).apply(block)
+        activityResultLauncher.launch(intent)
+    }
+    
+    // ========== 权限请求封装 ==========
+    
+    /**
+     * 请求权限
+     */
+    protected fun requestPermissions(vararg permissions: String) {
+        permissionLauncher?.launch(arrayOf(*permissions))
+    }
+    
+    /**
+     * 请求权限(带说明)
+     * 注意:此方法需要先注册权限请求器
+     */
+    protected fun requestPermissionsWithRationale(
+        launcher: ActivityResultLauncher<Array<String>>,
+        vararg permissions: String,
+        rationale: String = "需要相关权限才能使用此功能",
+        onResult: (Map<String, Boolean>) -> Unit = {}
+    ) {
+        // 检查权限是否已授予
+        val allGranted = permissions.all { permission ->
+            PermissionHelper.checkPermission(this, permission)
+        }
+        
+        if (allGranted) {
+            val result = permissions.associateWith { true }
+            onPermissionResult(result)
+            onResult(result)
+            return
+        }
+        
+        // 检查是否需要显示说明
+        val needRationale = permissions.any { permission ->
+            PermissionHelper.shouldShowRequestPermissionRationale(this, permission)
+        }
+        
+        val permissionsArray = permissions.toList().toTypedArray()
+        
+        if (needRationale) {
+            // 显示说明对话框
+            showConfirm(
+                title = "权限说明",
+                message = rationale,
+                onConfirm = {
+                    PermissionHelper.requestPermissions(launcher, permissionsArray)
+                },
+                onCancel = {
+                    val result = permissions.associateWith { false }
+                    onPermissionResult(result)
+                    onResult(result)
+                }
+            )
+        } else {
+            // 直接请求权限
+            PermissionHelper.requestPermissions(launcher, permissionsArray)
+        }
+    }
+    
+    // ========== 对话框封装 ==========
+    
+    /**
+     * 显示提示框(只有一个确定按钮)
+     */
+    protected fun showAlert(
+        title: String? = null,
+        message: String,
+        confirmText: String = "确定",
+        onConfirm: (() -> Unit)? = null
+    ) {
+        DialogHelper.showAlert(this, title, message, confirmText, onConfirm)
+    }
+    
+    /**
+     * 显示确认框
+     */
+    protected fun showConfirm(
+        title: String? = null,
+        message: String,
+        confirmText: String = "确定",
+        cancelText: String = "取消",
+        onConfirm: (() -> Unit)? = null,
+        onCancel: (() -> Unit)? = null
+    ) {
+        DialogHelper.showConfirm(this, title, message, confirmText, cancelText, onConfirm ?: {}, onCancel)
+    }
+    
+    // ========== 返回键处理 ==========
+    
+    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
+        if (enableBackKeyIntercept && keyCode == KeyEvent.KEYCODE_BACK) {
+            return onBackKeyPressed() || super.onKeyDown(keyCode, event)
+        }
+        return super.onKeyDown(keyCode, event)
+    }
+    
+    /**
+     * 返回键按下处理(子类可重写)
+     * @return true 表示已处理,false 表示未处理(继续默认行为)
+     */
+    protected open fun onBackKeyPressed(): Boolean {
+        return false
+    }
+    
+    /**
+     * 双击返回键退出(子类可重写实现)
+     */
+    private var lastBackPressTime = 0L
+    protected fun handleDoubleBackToExit(message: String = "再按一次退出应用"): Boolean {
+        val currentTime = System.currentTimeMillis()
+        if (currentTime - lastBackPressTime > 2000) {
+            lastBackPressTime = currentTime
+            showError(message)
+            return true
+        }
+        return false
+    }
+    
+    // ========== 状态栏管理 ==========
+    
+    /**
+     * 设置状态栏颜色
+     */
+    protected fun setStatusBarColor(color: Int) {
+        StatusBarHelper.setStatusBarColor(this, color)
+    }
+    
+    /**
+     * 设置状态栏文字颜色(浅色/深色模式)
+     */
+    protected fun setStatusBarLightMode(isLight: Boolean) {
+        StatusBarHelper.setStatusBarLightMode(this, isLight)
+    }
+    
+    /**
+     * 设置沉浸式状态栏
+     */
+    protected fun setImmersiveStatusBar(isLight: Boolean = false) {
+        StatusBarHelper.setImmersiveStatusBar(this, isLight)
+    }
+    
+    // ========== 网络请求封装 ==========
+    
+    /**
+     * 统一执行网络请求
+     * 
+     * 自动处理加载状态和错误提示
+     */
+    protected fun <T> executeRequest(
+        request: suspend () -> Result<T>,
+        onSuccess: (T) -> Unit,
+        onError: ((String) -> Unit)? = null,
+        showLoading: Boolean = true
+    ) {
+        lifecycleScope.launch {
+            try {
+                if (showLoading) showLoading()
+                
+                request()
+                    .onSuccess { data ->
+                        hideLoading()
+                        onSuccess(data)
+                    }
+                    .onFailure { throwable ->
+                        hideLoading()
+                        val errorMsg = throwable.message ?: "操作失败"
+                        if (onError != null) {
+                            onError(errorMsg)
+                        } else {
+                            showError(errorMsg)
+                        }
+                    }
+            } catch (e: Exception) {
+                hideLoading()
+                val errorMsg = e.message ?: "操作失败"
+                if (onError != null) {
+                    onError(errorMsg)
+                } else {
+                    showError(errorMsg)
+                }
+            }
+        }
+    }
+    
+    // ========== 其他便捷方法 ==========
+    
+    /**
+     * 关闭当前 Activity
+     */
+    protected fun finishActivity() {
+        finish()
+    }
+    
+    /**
+     * 关闭指定类型的 Activity
+     */
+    protected fun finishActivity(clazz: Class<out AppCompatActivity>) {
+        ActivityManager.finishActivity(clazz)
+    }
+    
+    /**
+     * 返回到指定 Activity
+     */
+    protected fun backToActivity(clazz: Class<out AppCompatActivity>) {
+        ActivityManager.backToActivity(clazz)
+    }
+    
+    /**
+     * 退出应用
+     */
+    protected fun exitApp() {
+        ActivityManager.exitApp()
+    }
+}
+

+ 137 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/BaseFragment.kt

@@ -0,0 +1,137 @@
+package com.narutohuo.xindazhou.common.ui
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import androidx.viewbinding.ViewBinding
+import kotlinx.coroutines.launch
+
+/**
+ * 基础 Fragment
+ * 
+ * 封装 UI 通用功能:
+ * - ViewBinding 支持
+ * - 统一的加载状态管理
+ * - 统一的错误提示
+ * - 生命周期管理
+ * 
+ * 使用方式:
+ * ```kotlin
+ * class LoginFragment : BaseFragment<FragmentLoginBinding>() {
+ *     private val viewModel: LoginViewModel by viewModels()
+ *     
+ *     override fun initObserver() {
+ *         // 使用扩展函数简化代码
+ *         observeStateFlow(viewModel.loginState) { state ->
+ *             when (state) {
+ *                 is LoginState.Loading -> showLoading()
+ *                 is LoginState.Success -> {
+ *                     hideLoading()
+ *                     showSuccess(state.message)
+ *                 }
+ *                 is LoginState.Error -> {
+ *                     hideLoading()
+ *                     showError(state.message)
+ *                 }
+ *             }
+ *         }
+ *     }
+ * }
+ * ```
+ */
+abstract class BaseFragment<VB : ViewBinding> : Fragment() {
+    
+    protected lateinit var binding: VB
+    protected abstract fun getViewBinding(inflater: LayoutInflater, container: ViewGroup?): VB
+    
+    // 加载状态(子类可重写自定义实现)
+    protected open fun showLoading() {
+        // 默认实现:显示加载提示
+        // 子类可重写自定义加载UI
+    }
+    
+    protected open fun hideLoading() {
+        // 默认实现:隐藏加载提示
+        // 子类可重写自定义加载UI
+    }
+    
+    // 错误提示(子类可重写自定义实现)
+    protected open fun showError(message: String) {
+        Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
+    }
+    
+    protected open fun showSuccess(message: String) {
+        Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
+    }
+    
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View? {
+        binding = getViewBinding(inflater, container)
+        return binding.root
+    }
+    
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        initView()
+        initObserver()
+    }
+    
+    /**
+     * 初始化视图(子类实现)
+     */
+    protected open fun initView() {}
+    
+    /**
+     * 初始化观察者(子类实现)
+     */
+    protected open fun initObserver() {}
+    
+    /**
+     * 统一执行网络请求
+     * 
+     * 自动处理加载状态和错误提示
+     */
+    protected fun <T> executeRequest(
+        request: suspend () -> Result<T>,
+        onSuccess: (T) -> Unit,
+        onError: ((String) -> Unit)? = null,
+        showLoading: Boolean = true
+    ) {
+        lifecycleScope.launch {
+            try {
+                if (showLoading) showLoading()
+                
+                request()
+                    .onSuccess { data ->
+                        hideLoading()
+                        onSuccess(data)
+                    }
+                    .onFailure { throwable ->
+                        hideLoading()
+                        val errorMsg = throwable.message ?: "操作失败"
+                        if (onError != null) {
+                            onError(errorMsg)
+                        } else {
+                            showError(errorMsg)
+                        }
+                    }
+            } catch (e: Exception) {
+                hideLoading()
+                val errorMsg = e.message ?: "操作失败"
+                if (onError != null) {
+                    onError(errorMsg)
+                } else {
+                    showError(errorMsg)
+                }
+            }
+        }
+    }
+}
+

+ 40 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/FragmentExtensions.kt

@@ -0,0 +1,40 @@
+package com.narutohuo.xindazhou.common.ui
+
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * 观察 StateFlow 的便捷扩展函数
+ * 
+ * 简化业务层代码,避免每次都写 lifecycleScope.launch { flow.collect { } }
+ * 
+ * 使用方式:
+ * ```kotlin
+ * override fun initObserver() {
+ *     observeStateFlow(viewModel.loginState) { state ->
+ *         when (state) {
+ *             is LoginState.Loading -> showLoading()
+ *             is LoginState.Success -> {
+ *                 hideLoading()
+ *                 showSuccess(state.message)
+ *             }
+ *             is LoginState.Error -> {
+ *                 hideLoading()
+ *                 showError(state.message)
+ *             }
+ *         }
+ *     }
+ * }
+ * ```
+ */
+fun <T> Fragment.observeStateFlow(
+    stateFlow: StateFlow<T>,
+    action: (T) -> Unit
+) {
+    lifecycleScope.launch {
+        stateFlow.collect { action(it) }
+    }
+}
+

+ 181 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/MessageHelper.kt

@@ -0,0 +1,181 @@
+package com.narutohuo.xindazhou.common.ui
+
+import android.view.View
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import com.google.android.material.snackbar.Snackbar
+
+/**
+ * 消息提示管理器
+ * 
+ * 统一封装 Toast 和 Snackbar,提供便捷的消息提示方式
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // Toast 提示
+ * MessageHelper.showToast("操作成功")
+ * MessageHelper.showSuccess("保存成功")
+ * MessageHelper.showError("操作失败")
+ * 
+ * // Snackbar 提示
+ * MessageHelper.showSnackbar(view, "操作成功")
+ * MessageHelper.showSnackbar(view, "操作失败", "重试") { /* 重试操作 */ }
+ * ```
+ */
+object MessageHelper {
+    
+    /**
+     * 是否使用 Snackbar(默认使用 Toast)
+     * 可以在 Application 中设置为 true,统一使用 Snackbar
+     */
+    var useSnackbar: Boolean = false
+    
+    /**
+     * Toast 显示时长
+     */
+    var toastDuration: Int = Toast.LENGTH_SHORT
+    
+    /**
+     * Snackbar 显示时长(毫秒)
+     */
+    var snackbarDuration: Int = 2000
+    
+    /**
+     * 显示 Toast 消息
+     * 
+     * @param view View 上下文(Activity 或 Fragment 的 View)
+     * @param message 消息内容
+     */
+    fun showToast(view: View, message: String) {
+        if (useSnackbar) {
+            showSnackbar(view, message)
+        } else {
+            val context = view.context
+            Toast.makeText(context, message, toastDuration).show()
+        }
+    }
+    
+    /**
+     * 显示 Toast 消息(Activity 扩展函数)
+     */
+    fun androidx.appcompat.app.AppCompatActivity.showToast(message: String) {
+        if (useSnackbar) {
+            val rootView = window.decorView.findViewById<View>(android.R.id.content)
+            showSnackbar(rootView, message)
+        } else {
+            Toast.makeText(this, message, toastDuration).show()
+        }
+    }
+    
+    /**
+     * 显示 Toast 消息(Fragment 扩展函数)
+     */
+    fun Fragment.showToast(message: String) {
+        if (useSnackbar) {
+            view?.let { showSnackbar(it, message) }
+        } else {
+            Toast.makeText(requireContext(), message, toastDuration).show()
+        }
+    }
+    
+    /**
+     * 显示成功消息
+     * 
+     * @param view View 上下文
+     * @param message 消息内容
+     */
+    fun showSuccess(view: View, message: String) {
+        showToast(view, message)
+    }
+    
+    /**
+     * 显示成功消息(Activity 扩展函数)
+     */
+    fun androidx.appcompat.app.AppCompatActivity.showSuccess(message: String) {
+        showToast(message)
+    }
+    
+    /**
+     * 显示成功消息(Fragment 扩展函数)
+     */
+    fun Fragment.showSuccess(message: String) {
+        showToast(message)
+    }
+    
+    /**
+     * 显示错误消息
+     * 
+     * @param view View 上下文
+     * @param message 消息内容
+     */
+    fun showError(view: View, message: String) {
+        showToast(view, message)
+    }
+    
+    /**
+     * 显示错误消息(Activity 扩展函数)
+     */
+    fun androidx.appcompat.app.AppCompatActivity.showError(message: String) {
+        showToast(message)
+    }
+    
+    /**
+     * 显示错误消息(Fragment 扩展函数)
+     */
+    fun Fragment.showError(message: String) {
+        showToast(message)
+    }
+    
+    /**
+     * 显示 Snackbar 消息
+     * 
+     * @param view 父 View
+     * @param message 消息内容
+     * @param actionText 操作按钮文本(可选)
+     * @param action 操作按钮点击回调(可选)
+     */
+    fun showSnackbar(
+        view: View,
+        message: String,
+        actionText: String? = null,
+        action: (() -> Unit)? = null
+    ) {
+        val snackbar = Snackbar.make(view, message, snackbarDuration)
+        
+        actionText?.let { text ->
+            action?.let { callback ->
+                snackbar.setAction(text) {
+                    callback()
+                }
+            }
+        }
+        
+        snackbar.show()
+    }
+    
+    /**
+     * 显示 Snackbar 消息(Activity 扩展函数)
+     */
+    fun androidx.appcompat.app.AppCompatActivity.showSnackbar(
+        message: String,
+        actionText: String? = null,
+        action: (() -> Unit)? = null
+    ) {
+        val rootView = window.decorView.findViewById<View>(android.R.id.content)
+        showSnackbar(rootView, message, actionText, action)
+    }
+    
+    /**
+     * 显示 Snackbar 消息(Fragment 扩展函数)
+     */
+    fun Fragment.showSnackbar(
+        message: String,
+        actionText: String? = null,
+        action: (() -> Unit)? = null
+    ) {
+        view?.let {
+            showSnackbar(it, message, actionText, action)
+        }
+    }
+}
+

+ 167 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/StatusBarHelper.kt

@@ -0,0 +1,167 @@
+package com.narutohuo.xindazhou.common.ui
+
+import android.app.Activity
+import android.graphics.Color
+import android.os.Build
+import android.view.View
+import android.view.WindowManager
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsControllerCompat
+
+/**
+ * 状态栏/导航栏管理器
+ * 
+ * 统一封装状态栏和导航栏的设置,提供便捷的状态栏管理方式
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 设置状态栏颜色
+ * StatusBarHelper.setStatusBarColor(activity, Color.BLUE)
+ * 
+ * // 设置状态栏文字颜色(浅色/深色)
+ * StatusBarHelper.setStatusBarLightMode(activity, true)
+ * 
+ * // 设置沉浸式状态栏
+ * StatusBarHelper.setImmersiveStatusBar(activity)
+ * 
+ * // 设置导航栏颜色
+ * StatusBarHelper.setNavigationBarColor(activity, Color.BLACK)
+ * ```
+ */
+object StatusBarHelper {
+    
+    /**
+     * 设置状态栏颜色
+     * 
+     * @param activity Activity 实例
+     * @param color 颜色值(如 Color.BLUE 或 0xFF000000.toInt())
+     */
+    fun setStatusBarColor(activity: Activity, color: Int) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            activity.window.statusBarColor = color
+        }
+    }
+    
+    /**
+     * 设置状态栏文字颜色(浅色/深色模式)
+     * 
+     * @param activity Activity 实例
+     * @param isLight true 表示浅色文字(深色背景),false 表示深色文字(浅色背景)
+     */
+    fun setStatusBarLightMode(activity: Activity, isLight: Boolean) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            // Android 11+ 使用 WindowInsetsController
+            val window = activity.window
+            val insetsController = WindowCompat.getInsetsController(window, window.decorView)
+            insetsController?.isAppearanceLightStatusBars = !isLight
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            // Android 6.0+ 使用系统 UI 标志
+            val window = activity.window
+            var flags = window.decorView.systemUiVisibility
+            flags = if (isLight) {
+                flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+            } else {
+                flags and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
+            }
+            window.decorView.systemUiVisibility = flags
+        }
+    }
+    
+    /**
+     * 设置沉浸式状态栏(状态栏透明,内容延伸到状态栏)
+     * 
+     * @param activity Activity 实例
+     * @param isLight 状态栏文字是否为浅色(默认 false,深色文字)
+     */
+    fun setImmersiveStatusBar(activity: Activity, isLight: Boolean = false) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            val window = activity.window
+            window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
+            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
+            window.statusBarColor = Color.TRANSPARENT
+            
+            // 设置状态栏文字颜色
+            setStatusBarLightMode(activity, isLight)
+        }
+    }
+    
+    /**
+     * 设置导航栏颜色
+     * 
+     * @param activity Activity 实例
+     * @param color 颜色值
+     */
+    fun setNavigationBarColor(activity: Activity, color: Int) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            activity.window.navigationBarColor = color
+        }
+    }
+    
+    /**
+     * 设置导航栏文字颜色(浅色/深色模式)
+     * 
+     * @param activity Activity 实例
+     * @param isLight true 表示浅色文字,false 表示深色文字
+     */
+    fun setNavigationBarLightMode(activity: Activity, isLight: Boolean) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            val window = activity.window
+            val insetsController = WindowCompat.getInsetsController(window, window.decorView)
+            insetsController?.isAppearanceLightNavigationBars = !isLight
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            val window = activity.window
+            var flags = window.decorView.systemUiVisibility
+            flags = if (isLight) {
+                flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+            } else {
+                flags and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()
+            }
+            window.decorView.systemUiVisibility = flags
+        }
+    }
+    
+    /**
+     * 设置全屏沉浸式(隐藏状态栏和导航栏)
+     * 
+     * @param activity Activity 实例
+     */
+    fun setFullScreen(activity: Activity) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            val window = activity.window
+            val decorView = window.decorView
+            var flags = decorView.systemUiVisibility
+            
+            flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+            flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+            flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+            flags = flags or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+            flags = flags or View.SYSTEM_UI_FLAG_FULLSCREEN
+            flags = flags or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+            
+            decorView.systemUiVisibility = flags
+        }
+    }
+    
+    /**
+     * 退出全屏
+     * 
+     * @param activity Activity 实例
+     */
+    fun exitFullScreen(activity: Activity) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+            val window = activity.window
+            val decorView = window.decorView
+            var flags = decorView.systemUiVisibility
+            
+            flags = flags and View.SYSTEM_UI_FLAG_LAYOUT_STABLE.inv()
+            flags = flags and View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION.inv()
+            flags = flags and View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN.inv()
+            flags = flags and View.SYSTEM_UI_FLAG_HIDE_NAVIGATION.inv()
+            flags = flags and View.SYSTEM_UI_FLAG_FULLSCREEN.inv()
+            flags = flags and View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY.inv()
+            
+            decorView.systemUiVisibility = flags
+        }
+    }
+}
+

+ 539 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/UI层封装方案.md

@@ -0,0 +1,539 @@
+# UI 层封装方案
+
+## 一、设计目标
+
+将 UI 层的通用功能封装成基类和扩展函数,统一处理公共逻辑,业务层只需简单调用,减少重复代码。
+
+## 二、架构设计
+
+### 2.1 现有组件
+
+- ✅ `BaseActivity` - 基础 Activity 类(已存在)
+- ✅ `BaseFragment` - 基础 Fragment 类(已存在)
+- ✅ `ActivityExtensions` - Activity 扩展函数(已存在)
+- ✅ `FragmentExtensions` - Fragment 扩展函数(已存在)
+
+### 2.2 核心原则
+
+- **职责分离**:UI 层只负责显示,不处理网络请求
+- **MVVM 架构**:网络请求在 ViewModel 中处理,Activity 只观察状态
+- **统一封装**:通用 UI 功能统一封装,减少重复代码
+- **易于扩展**:子类可重写方法自定义实现
+
+## 三、目录结构
+
+```
+base-common/src/main/java/com/narutohuo/xindazhou/common/
+└── ui/                      # UI 层封装
+    ├── BaseActivity.kt       # 基础 Activity 类
+    ├── BaseFragment.kt      # 基础 Fragment 类
+    ├── ActivityExtensions.kt    # Activity 扩展函数
+    └── FragmentExtensions.kt    # Fragment 扩展函数
+```
+
+## 四、功能说明
+
+### 4.1 BaseActivity - 基础 Activity 类
+
+**职责**:封装 Activity 通用 UI 功能
+
+**功能**:
+- ✅ **ViewBinding 支持**:自动处理 ViewBinding 初始化
+- ✅ **统一的加载状态管理**:`showLoading()` / `hideLoading()`
+- ✅ **统一的错误提示**:`showError()` / `showSuccess()`
+- ✅ **生命周期管理**:自动调用 `initView()` 和 `initObserver()`
+- ✅ **网络请求封装**:`executeRequest()` 方法(可选,简单场景使用)
+
+**使用方式**:
+```kotlin
+class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+    
+    private val viewModel: LoginViewModel by viewModels()
+    
+    override fun getViewBinding(): ActivityLoginBinding {
+        return ActivityLoginBinding.inflate(layoutInflater)
+    }
+    
+    override fun initView() {
+        binding.btnLogin.setOnClickListener {
+            viewModel.login(mobile, password)
+        }
+    }
+    
+    override fun initObserver() {
+        observeStateFlow(viewModel.loginState) { state ->
+            when (state) {
+                is LoginState.Loading -> showLoading()
+                is LoginState.Success -> {
+                    hideLoading()
+                    showSuccess(state.message)
+                }
+                is LoginState.Error -> {
+                    hideLoading()
+                    showError(state.message)
+                }
+            }
+        }
+    }
+}
+```
+
+### 4.2 BaseFragment - 基础 Fragment 类
+
+**职责**:封装 Fragment 通用 UI 功能
+
+**功能**:
+- ✅ **ViewBinding 支持**:自动处理 ViewBinding 初始化
+- ✅ **统一的加载状态管理**:`showLoading()` / `hideLoading()`
+- ✅ **统一的错误提示**:`showError()` / `showSuccess()`
+- ✅ **生命周期管理**:自动调用 `initView()` 和 `initObserver()`
+- ✅ **网络请求封装**:`executeRequest()` 方法(可选,简单场景使用)
+
+**使用方式**:
+```kotlin
+class LoginFragment : BaseFragment<FragmentLoginBinding>() {
+    
+    private val viewModel: LoginViewModel by viewModels()
+    
+    override fun getViewBinding(
+        inflater: LayoutInflater,
+        container: ViewGroup?
+    ): FragmentLoginBinding {
+        return FragmentLoginBinding.inflate(inflater, container, false)
+    }
+    
+    override fun initView() {
+        binding.btnLogin.setOnClickListener {
+            viewModel.login(mobile, password)
+        }
+    }
+    
+    override fun initObserver() {
+        observeStateFlow(viewModel.loginState) { state ->
+            when (state) {
+                is LoginState.Loading -> showLoading()
+                is LoginState.Success -> {
+                    hideLoading()
+                    showSuccess(state.message)
+                }
+                is LoginState.Error -> {
+                    hideLoading()
+                    showError(state.message)
+                }
+            }
+        }
+    }
+}
+```
+
+### 4.3 ActivityExtensions - Activity 扩展函数
+
+**职责**:提供便捷的扩展函数,简化业务层代码
+
+**功能**:
+- ✅ **观察 StateFlow**:`observeStateFlow()` 简化状态观察
+
+**使用方式**:
+```kotlin
+// 之前:繁琐
+override fun initObserver() {
+    lifecycleScope.launch {
+        viewModel.loginState.collect { state ->
+            when (state) { ... }
+        }
+    }
+}
+
+// 现在:简洁
+override fun initObserver() {
+    observeStateFlow(viewModel.loginState) { state ->
+        when (state) { ... }
+    }
+}
+```
+
+### 4.4 FragmentExtensions - Fragment 扩展函数
+
+**职责**:提供便捷的扩展函数,简化业务层代码
+
+**功能**:
+- ✅ **观察 StateFlow**:`observeStateFlow()` 简化状态观察
+
+**使用方式**:同 ActivityExtensions
+
+## 五、完整 MVVM 架构流程
+
+### 5.1 数据流向
+
+```
+用户操作(点击按钮)
+    ↓
+Activity/Fragment(UI 层)
+    ↓ 调用
+ViewModel(业务逻辑层)
+    ↓ 调用
+Repository(数据管理层)
+    ↓ 调用
+RemoteDataSource(网络请求层)
+    ↓ 返回数据
+Repository
+    ↓ 返回数据
+ViewModel(更新 UI 状态)
+    ↓ 状态变化
+Activity/Fragment(观察状态,调用 BaseActivity 方法更新 UI)
+```
+
+### 5.2 完整示例
+
+#### 1. ViewModel 层(处理业务逻辑)
+
+```kotlin
+// LoginViewModel.kt
+class LoginViewModel(
+    application: Application,
+    private val repository: AuthRepository
+) : AndroidViewModel(application) {
+    
+    private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
+    val loginState: StateFlow<LoginState> = _loginState
+    
+    fun login(mobile: String, password: String) {
+        viewModelScope.launch {
+            _loginState.value = LoginState.Loading
+            
+            repository.login(mobile, password)
+                .onSuccess {
+                    _loginState.value = LoginState.Success("登录成功")
+                }
+                .onFailure { e ->
+                    _loginState.value = LoginState.Error(e.message ?: "登录失败")
+                }
+        }
+    }
+}
+```
+
+#### 2. Activity 层(只负责显示)
+
+```kotlin
+// LoginActivity.kt
+class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+    
+    private val viewModel: LoginViewModel by viewModels()
+    
+    override fun getViewBinding(): ActivityLoginBinding {
+        return ActivityLoginBinding.inflate(layoutInflater)
+    }
+    
+    override fun initView() {
+        binding.btnLogin.setOnClickListener {
+            val mobile = binding.etMobile.text.toString()
+            val password = binding.etPassword.text.toString()
+            viewModel.login(mobile, password)  // 触发 ViewModel
+        }
+    }
+    
+    override fun initObserver() {
+        // 使用扩展函数观察状态
+        observeStateFlow(viewModel.loginState) { state ->
+            when (state) {
+                is LoginState.Loading -> showLoading()      // BaseActivity 提供
+                is LoginState.Success -> {
+                    hideLoading()
+                    showSuccess(state.message)              // BaseActivity 提供
+                    // 跳转到主页
+                }
+                is LoginState.Error -> {
+                    hideLoading()
+                    showError(state.message)                // BaseActivity 提供
+                }
+            }
+        }
+    }
+}
+```
+
+## 六、核心功能详解
+
+### 6.1 ViewBinding 支持
+
+**自动处理**:
+- `BaseActivity` 自动初始化 `binding`
+- `BaseFragment` 自动初始化 `binding`
+- 子类只需实现 `getViewBinding()` 方法
+
+**优势**:
+- ✅ 不需要 `findViewById`
+- ✅ 类型安全
+- ✅ 自动空检查
+
+### 6.2 加载状态管理
+
+**方法**:
+- `showLoading()` - 显示加载提示(子类可重写自定义 UI)
+- `hideLoading()` - 隐藏加载提示(子类可重写自定义 UI)
+
+**使用场景**:
+- 网络请求开始时显示加载
+- 网络请求结束时隐藏加载
+
+### 6.3 错误提示管理
+
+**方法**:
+- `showError(message: String)` - 显示错误提示(默认 Toast,子类可重写)
+- `showSuccess(message: String)` - 显示成功提示(默认 Toast,子类可重写)
+
+**使用场景**:
+- 网络请求失败时显示错误
+- 操作成功时显示成功提示
+
+### 6.4 状态观察(扩展函数)
+
+**方法**:
+- `observeStateFlow(stateFlow, action)` - 观察 StateFlow 状态变化
+
+**优势**:
+- ✅ 简化代码:不需要每次都写 `lifecycleScope.launch { flow.collect { } }`
+- ✅ 自动生命周期管理:Activity/Fragment 销毁时自动取消观察
+- ✅ 一行代码搞定状态观察
+
+### 6.5 网络请求封装(可选)
+
+**方法**:
+- `executeRequest(request, onSuccess, onError, showLoading)` - 统一执行网络请求
+
+**使用场景**:
+- 简单业务场景,不需要 ViewModel
+- 直接调用 Repository 的场景
+
+**注意**:
+- ⚠️ 推荐使用 ViewModel 方式(符合 MVVM 架构)
+- ⚠️ 简单场景可以使用此方法
+
+## 七、使用示例
+
+### 7.1 推荐方式:使用 ViewModel(MVVM 架构)
+
+```kotlin
+class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+    
+    private val viewModel: LoginViewModel by viewModels()
+    
+    override fun getViewBinding(): ActivityLoginBinding {
+        return ActivityLoginBinding.inflate(layoutInflater)
+    }
+    
+    override fun initView() {
+        binding.btnLogin.setOnClickListener {
+            viewModel.login(mobile, password)
+        }
+    }
+    
+    override fun initObserver() {
+        observeStateFlow(viewModel.loginState) { state ->
+            when (state) {
+                is LoginState.Loading -> showLoading()
+                is LoginState.Success -> {
+                    hideLoading()
+                    showSuccess(state.message)
+                }
+                is LoginState.Error -> {
+                    hideLoading()
+                    showError(state.message)
+                }
+            }
+        }
+    }
+}
+```
+
+### 7.2 简单方式:直接使用 Repository(简单场景)
+
+```kotlin
+class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+    
+    private val repository = AuthRepository()
+    
+    override fun initView() {
+        binding.btnLogin.setOnClickListener {
+            executeRequest(
+                request = { repository.login(mobile, password) },
+                onSuccess = { response ->
+                    showSuccess("登录成功")
+                    // 处理成功逻辑
+                }
+            )
+        }
+    }
+}
+```
+
+### 7.3 自定义 UI 实现
+
+```kotlin
+class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+    
+    // 自定义加载 UI
+    override fun showLoading() {
+        binding.progressBar.visibility = View.VISIBLE
+        binding.btnLogin.isEnabled = false
+    }
+    
+    override fun hideLoading() {
+        binding.progressBar.visibility = View.GONE
+        binding.btnLogin.isEnabled = true
+    }
+    
+    // 自定义错误提示(使用 Dialog 而不是 Toast)
+    override fun showError(message: String) {
+        AlertDialog.Builder(this)
+            .setTitle("错误")
+            .setMessage(message)
+            .setPositiveButton("确定", null)
+            .show()
+    }
+}
+```
+
+## 八、优势
+
+### 8.1 代码简化
+
+**之前**:
+```kotlin
+class LoginActivity : AppCompatActivity() {
+    private lateinit var binding: ActivityLoginBinding
+    
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        binding = ActivityLoginBinding.inflate(layoutInflater)
+        setContentView(binding.root)
+        
+        // 初始化视图
+        binding.btnLogin.setOnClickListener { ... }
+        
+        // 观察状态
+        lifecycleScope.launch {
+            viewModel.loginState.collect { state ->
+                when (state) {
+                    is LoginState.Loading -> {
+                        // 显示加载
+                        binding.progressBar.visibility = View.VISIBLE
+                    }
+                    is LoginState.Success -> {
+                        // 隐藏加载
+                        binding.progressBar.visibility = View.GONE
+                        Toast.makeText(this@LoginActivity, state.message, Toast.LENGTH_SHORT).show()
+                    }
+                    is LoginState.Error -> {
+                        // 隐藏加载
+                        binding.progressBar.visibility = View.GONE
+                        Toast.makeText(this@LoginActivity, state.message, Toast.LENGTH_SHORT).show()
+                    }
+                }
+            }
+        }
+    }
+}
+```
+
+**现在**:
+```kotlin
+class LoginActivity : BaseActivity<ActivityLoginBinding>() {
+    
+    override fun getViewBinding() = ActivityLoginBinding.inflate(layoutInflater)
+    
+    override fun initView() {
+        binding.btnLogin.setOnClickListener { ... }
+    }
+    
+    override fun initObserver() {
+        observeStateFlow(viewModel.loginState) { state ->
+            when (state) {
+                is LoginState.Loading -> showLoading()
+                is LoginState.Success -> { hideLoading(); showSuccess(state.message) }
+                is LoginState.Error -> { hideLoading(); showError(state.message) }
+            }
+        }
+    }
+}
+```
+
+**代码减少**:约 60%+
+
+### 8.2 统一 UI 风格
+
+- ✅ 所有 Activity 的加载、错误提示保持一致
+- ✅ 统一的生命周期管理
+- ✅ 统一的代码结构
+
+### 8.3 易于维护
+
+- ✅ 通用功能修改只需改基类
+- ✅ 子类可重写方法自定义实现
+- ✅ 职责清晰,易于理解
+
+### 8.4 符合 MVVM 架构
+
+- ✅ UI 层只负责显示
+- ✅ 业务逻辑在 ViewModel 中处理
+- ✅ 网络请求在 Repository 中处理
+- ✅ 职责分离,解耦清晰
+
+## 九、注意事项
+
+### 9.1 必须实现的方法
+
+- `getViewBinding()` - 必须实现,返回 ViewBinding 实例
+
+### 9.2 可选实现的方法
+
+- `initView()` - 初始化视图(可选)
+- `initObserver()` - 初始化观察者(可选)
+- `showLoading()` / `hideLoading()` - 自定义加载 UI(可选)
+- `showError()` / `showSuccess()` - 自定义提示 UI(可选)
+
+### 9.3 推荐使用方式
+
+- ✅ **推荐**:使用 ViewModel + StateFlow + `observeStateFlow()` 扩展函数
+- ⚠️ **可选**:简单场景可以使用 `executeRequest()` 方法
+
+### 9.4 网络请求处理
+
+- ✅ **推荐**:网络请求在 ViewModel 中处理,Activity 只观察状态
+- ⚠️ **可选**:简单场景可以在 Activity 中使用 `executeRequest()` 方法
+
+## 十、完整架构层次
+
+```
+UI 层(View)
+├── BaseActivity / BaseFragment          # UI 基类
+│   ├── ViewBinding 支持
+│   ├── 加载状态管理
+│   ├── 错误提示
+│   ├── 生命周期管理
+│   └── executeRequest() 便捷方法(可选)
+│
+├── ActivityExtensions / FragmentExtensions  # 扩展函数
+│   └── observeStateFlow() 简化状态观察
+│
+└── Activity / Fragment                  # 业务 UI 组件
+    └── 继承 BaseActivity / BaseFragment
+
+业务逻辑层(ViewModel)
+└── ViewModel                           # 处理业务逻辑
+    └── 使用 Repository
+
+数据层(Data)
+├── Repository                          # 数据仓库
+│   └── 继承 ApiBaseRepository
+│
+└── RemoteDataSource                    # 远程数据源
+    └── 继承 ApiBaseRemoteDataSource
+```
+
+## 十一、参考文档
+
+- [网络请求 MVVM 封装方案](../network/网络请求MVVM封装方案.md) - 网络层封装方案
+- [Android MVVM 架构指南](https://developer.android.com/topic/architecture)
+

+ 156 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/util/UtilManager.kt

@@ -0,0 +1,156 @@
+package com.narutohuo.xindazhou.common.util
+
+import com.alibaba.android.arouter.launcher.ARouter
+import com.narutohuo.xindazhou.core.log.ILog
+import com.narutohuo.xindazhou.core.util.IUtil
+
+/**
+ * 工具管理器
+ * 
+ * 业务层封装,提供便捷的工具方法调用方式
+ * 内部使用 IUtil 接口(由 app 模块实现)
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 时间处理
+ * val timeStr = UtilManager.formatTime(System.currentTimeMillis(), "yyyy-MM-dd HH:mm:ss")
+ * val timestamp = UtilManager.parseTime("2024-01-01 12:00:00", "yyyy-MM-dd HH:mm:ss")
+ * 
+ * // 字符串处理
+ * val encrypted = UtilManager.encryptString("原始数据")
+ * val decrypted = UtilManager.decryptString(encrypted)
+ * val md5Hash = UtilManager.md5("原始数据")
+ * 
+ * // 数据转换
+ * val json = UtilManager.toJson(user)
+ * val user = UtilManager.fromJson<User>(json, User::class.java)
+ * ```
+ */
+object UtilManager {
+    
+    internal const val TAG = "UtilManager"
+    
+    /**
+     * 获取 IUtil 实现实例
+     * 通过 ARouter 获取,如果未注册则返回 null
+     */
+    fun getUtil(): IUtil? {
+        return try {
+            ARouter.getInstance().navigation(IUtil::class.java)
+        } catch (e: Exception) {
+            ILog.w(TAG, "IUtil 未注册,请确保 app 模块已实现并注册 IUtil")
+            null
+        }
+    }
+    
+    // ========== 时间处理 ==========
+    
+    /**
+     * 格式化时间
+     * 
+     * @param timestamp 时间戳(毫秒)
+     * @param pattern 格式模式(如 "yyyy-MM-dd HH:mm:ss")
+     * @return 格式化后的时间字符串
+     */
+    fun formatTime(timestamp: Long, pattern: String): String {
+        return getUtil()?.formatTime(timestamp, pattern) ?: run {
+            ILog.w(TAG, "formatTime 失败:IUtil 未注册")
+            ""
+        }
+    }
+    
+    /**
+     * 解析时间字符串
+     * 
+     * @param timeString 时间字符串
+     * @param pattern 格式模式(如 "yyyy-MM-dd HH:mm:ss")
+     * @return 时间戳(毫秒),如果解析失败返回 null
+     */
+    fun parseTime(timeString: String, pattern: String): Long? {
+        return getUtil()?.parseTime(timeString, pattern) ?: run {
+            ILog.w(TAG, "parseTime 失败:IUtil 未注册")
+            null
+        }
+    }
+    
+    // ========== 字符串处理 ==========
+    
+    /**
+     * 加密字符串
+     * 
+     * @param data 原始数据
+     * @return 加密后的字符串
+     */
+    fun encryptString(data: String): String {
+        return getUtil()?.encryptString(data) ?: run {
+            ILog.w(TAG, "encryptString 失败:IUtil 未注册")
+            data
+        }
+    }
+    
+    /**
+     * 解密字符串
+     * 
+     * @param data 加密后的数据
+     * @return 解密后的字符串
+     */
+    fun decryptString(data: String): String {
+        return getUtil()?.decryptString(data) ?: run {
+            ILog.w(TAG, "decryptString 失败:IUtil 未注册")
+            data
+        }
+    }
+    
+    /**
+     * MD5 哈希
+     * 
+     * @param data 原始数据
+     * @return MD5 哈希值
+     */
+    fun md5(data: String): String {
+        return getUtil()?.md5(data) ?: run {
+            ILog.w(TAG, "md5 失败:IUtil 未注册")
+            ""
+        }
+    }
+    
+    // ========== 数据转换 ==========
+    
+    /**
+     * 对象转 JSON
+     * 
+     * @param obj 对象
+     * @return JSON 字符串
+     */
+    inline fun <reified T> toJson(obj: T): String {
+        return getUtil()?.toJson(obj) ?: run {
+            ILog.w("UtilManager", "toJson 失败:IUtil 未注册")
+            ""
+        }
+    }
+    
+    /**
+     * JSON 转对象
+     * 
+     * @param json JSON 字符串
+     * @param clazz 目标类型
+     * @return 对象,如果解析失败返回 null
+     */
+    inline fun <reified T> fromJson(json: String, clazz: Class<T>): T? {
+        return getUtil()?.fromJson(json, clazz) ?: run {
+            ILog.w("UtilManager", "fromJson 失败:IUtil 未注册")
+            null
+        }
+    }
+    
+    /**
+     * JSON 转对象(简化版,使用 reified 泛型)
+     * 
+     * @param json JSON 字符串
+     * @return 对象,如果解析失败返回 null
+     */
+    inline fun <reified T> fromJson(json: String): T? {
+        return fromJson(json, T::class.java)
+    }
+}
+

+ 44 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/version/VersionCheckActivity.kt

@@ -0,0 +1,44 @@
+package com.narutohuo.xindazhou.common.version
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.narutohuo.xindazhou.common.ui.ActivityManager
+
+/**
+ * 版本检查 Activity(通用)
+ * 
+ * 职责:
+ * 1. 启动时自动检查版本更新
+ * 2. 显示更新对话框
+ * 3. 处理强制更新
+ * 
+ * 使用方式:
+ * 在 app 模块中继承此类,或直接使用
+ * 
+ * 注意:版本检查逻辑已封装在 VersionUpdateManager 中
+ */
+open class VersionCheckActivity : AppCompatActivity() {
+    
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        
+        // 注册到 ActivityManager
+        ActivityManager.addActivity(this)
+        
+        // 自动检查版本更新
+        VersionUpdateManager.checkUpdate(
+            activity = this,
+            onForceUpdateDismiss = {
+                // 强制更新时,关闭对话框后退出应用
+                finish()
+            }
+        )
+    }
+    
+    override fun onDestroy() {
+        super.onDestroy()
+        // 从 ActivityManager 中移除
+        ActivityManager.removeActivity(this)
+    }
+}
+

+ 200 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/version/VersionUpdateManager.kt

@@ -0,0 +1,200 @@
+package com.narutohuo.xindazhou.common.version
+
+import android.app.Activity
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.common.network.ApiServiceFactory
+import com.narutohuo.xindazhou.common.version.datasource.remote.VersionRemoteDataSourceImpl
+import com.narutohuo.xindazhou.common.version.repository.VersionRepository
+import com.narutohuo.xindazhou.common.version.ui.UpdateDialogFragment
+import kotlinx.coroutines.launch
+
+/**
+ * 版本更新管理器
+ * 
+ * 封装版本检查的完整逻辑,包括:
+ * - 获取当前版本号
+ * - 检查服务器版本
+ * - 显示更新对话框
+ * - 处理强制更新
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 Application 中初始化
+ * VersionUpdateManager.init(context)
+ * 
+ * // 在 Activity 中检查更新(自动获取版本号)
+ * VersionUpdateManager.checkUpdate(activity)
+ * 
+ * // 或者手动指定版本号
+ * VersionUpdateManager.checkUpdate(activity, platform = 1, versionCode = 100)
+ * ```
+ */
+object VersionUpdateManager {
+    
+    private const val PLATFORM_ANDROID = 1 // 平台类型:1-安卓,2-鸿蒙,3-iOS
+    
+    /**
+     * 初始化版本更新管理器
+     * 
+     * 需要在 Application.onCreate() 中调用
+     * 
+     * @param context 上下文
+     * @param platform 平台类型(默认 1-安卓)
+     */
+    fun init(context: Context, platform: Int = PLATFORM_ANDROID) {
+        // 初始化 ApiServiceFactory 的 baseUrlProvider(如果还未设置)
+        if (ApiServiceFactory.baseUrlProvider == null) {
+            // 这里需要从 ServerConfigManager 获取,但为了避免循环依赖,
+            // 建议在 Application 中先设置 ApiServiceFactory.baseUrlProvider
+            LogHelper.w("VersionUpdateManager", "ApiServiceFactory.baseUrlProvider 未设置,版本检查可能失败")
+        }
+    }
+    
+    /**
+     * 检查版本更新(自动获取当前版本号)
+     * 
+     * @param activity Activity 实例(用于显示对话框和获取版本号)
+     * @param platform 平台类型(默认 1-安卓)
+     * @param onForceUpdateDismiss 强制更新时,用户关闭对话框的回调(可选,默认退出应用)
+     */
+    fun checkUpdate(
+        activity: FragmentActivity,
+        platform: Int = PLATFORM_ANDROID,
+        onForceUpdateDismiss: (() -> Unit)? = null
+    ) {
+        val currentVersionCode = getCurrentVersionCode(activity)
+        checkUpdate(activity, platform, currentVersionCode, onForceUpdateDismiss)
+    }
+    
+    /**
+     * 检查版本更新(手动指定版本号)
+     * 
+     * @param activity Activity 实例(用于显示对话框)
+     * @param platform 平台类型(默认 1-安卓)
+     * @param currentVersionCode 当前版本号
+     * @param onForceUpdateDismiss 强制更新时,用户关闭对话框的回调(可选,默认退出应用)
+     */
+    fun checkUpdate(
+        activity: FragmentActivity,
+        platform: Int,
+        currentVersionCode: Int,
+        onForceUpdateDismiss: (() -> Unit)? = null
+    ) {
+        // 使用 LifecycleOwner 的 lifecycleScope,自动管理协程生命周期
+        activity.lifecycleScope.launch {
+            try {
+                // 创建数据源和 Repository
+                val remoteDataSource = com.narutohuo.xindazhou.common.version.datasource.remote.VersionRemoteDataSourceImpl()
+                val versionRepository = VersionRepository(remoteDataSource)
+                
+                // 检查版本(如果没有版本,返回 null,不影响启动)
+                val versionResponse = versionRepository.checkVersion(platform, currentVersionCode)
+                
+                // 如果有新版本,显示更新对话框
+                if (versionResponse != null && versionRepository.hasUpdate(versionResponse)) {
+                    val dialog = UpdateDialogFragment.create(
+                        versionResponse = versionResponse,
+                        onDismiss = {
+                            // 对话框关闭后的回调
+                            if (versionRepository.isForceUpdate(versionResponse)) {
+                                // 强制更新时,如果用户关闭对话框,执行回调或退出应用
+                                if (onForceUpdateDismiss != null) {
+                                    onForceUpdateDismiss()
+                                } else {
+                                    activity.finish()
+                                }
+                            }
+                        }
+                    )
+                    dialog.show(activity.supportFragmentManager, "UpdateDialogFragment")
+                }
+            } catch (e: Exception) {
+                // 版本检查失败不影响应用启动
+                // 可以记录日志,但不显示错误提示
+                LogHelper.e("VersionUpdateManager", "版本检查失败", e)
+            }
+        }
+    }
+    
+    /**
+     * 检查版本更新(使用 LifecycleOwner,适用于 Fragment)
+     * 
+     * @param lifecycleOwner LifecycleOwner(Activity 或 Fragment)
+     * @param activity Activity 实例(用于显示对话框和获取版本号)
+     * @param platform 平台类型(默认 1-安卓)
+     * @param onForceUpdateDismiss 强制更新时,用户关闭对话框的回调(可选,默认退出应用)
+     */
+    fun checkUpdate(
+        lifecycleOwner: LifecycleOwner,
+        activity: FragmentActivity,
+        platform: Int = PLATFORM_ANDROID,
+        onForceUpdateDismiss: (() -> Unit)? = null
+    ) {
+        val currentVersionCode = getCurrentVersionCode(activity)
+        lifecycleOwner.lifecycleScope.launch {
+            try {
+                // 创建数据源和 Repository
+                val remoteDataSource = VersionRemoteDataSourceImpl()
+                val versionRepository = VersionRepository(remoteDataSource)
+                
+                // 检查版本(如果没有版本,返回 null,不影响启动)
+                val versionResponse = versionRepository.checkVersion(platform, currentVersionCode)
+                
+                // 如果有新版本,显示更新对话框
+                if (versionResponse != null && versionRepository.hasUpdate(versionResponse)) {
+                    val dialog = UpdateDialogFragment.create(
+                        versionResponse = versionResponse,
+                        onDismiss = {
+                            // 对话框关闭后的回调
+                            if (versionRepository.isForceUpdate(versionResponse)) {
+                                // 强制更新时,如果用户关闭对话框,执行回调或退出应用
+                                if (onForceUpdateDismiss != null) {
+                                    onForceUpdateDismiss()
+                                } else {
+                                    activity.finish()
+                                }
+                            }
+                        }
+                    )
+                    dialog.show(activity.supportFragmentManager, "UpdateDialogFragment")
+                }
+            } catch (e: Exception) {
+                // 版本检查失败不影响应用启动
+                // 可以记录日志,但不显示错误提示
+                LogHelper.e("VersionUpdateManager", "版本检查失败", e)
+            }
+        }
+    }
+    
+    /**
+     * 获取当前应用的版本号
+     * 
+     * @param context 上下文
+     * @return 版本号,如果获取失败返回 1
+     */
+    fun getCurrentVersionCode(context: Context): Int {
+        return try {
+            val packageManager = context.packageManager
+            val packageInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+                // API 28+ 使用 longVersionCode
+                packageManager.getPackageInfo(context.packageName, 0).longVersionCode.toInt()
+            } else {
+                // API 26-27 使用 versionCode
+                @Suppress("DEPRECATION")
+                packageManager.getPackageInfo(context.packageName, 0).versionCode
+            }
+            packageInfo
+        } catch (e: PackageManager.NameNotFoundException) {
+            // 记录异常
+            LogHelper.e("VersionUpdateManager", "无法获取版本号", e)
+            1 // 默认版本号
+        }
+    }
+}
+

+ 27 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/version/datasource/remote/VersionApi.kt

@@ -0,0 +1,27 @@
+package com.narutohuo.xindazhou.common.version.datasource.remote
+
+import com.narutohuo.xindazhou.common.network.ApiCommonResult
+import com.narutohuo.xindazhou.common.version.model.VersionResponse
+import retrofit2.Response
+import retrofit2.http.GET
+import retrofit2.http.Query
+
+/**
+ * 版本检查 API 接口
+ */
+interface VersionApi {
+    
+    /**
+     * 获取最新版本
+     * 
+     * @param platform 平台类型:1-安卓,2-鸿蒙,3-iOS
+     * @param currentVersionCode 当前版本号(可选,用于判断是否有更新)
+     * @return 版本响应数据
+     */
+    @GET("version/latest")
+    suspend fun getLatestVersion(
+        @Query("platform") platform: Int,
+        @Query("currentVersionCode") currentVersionCode: Int? = null
+    ): Response<ApiCommonResult<VersionResponse>>
+}
+

+ 44 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/version/datasource/remote/VersionRemoteDataSource.kt

@@ -0,0 +1,44 @@
+package com.narutohuo.xindazhou.common.version.datasource.remote
+
+import com.narutohuo.xindazhou.common.network.ApiBaseRemoteDataSource
+import com.narutohuo.xindazhou.common.version.model.VersionResponse
+
+/**
+ * 版本远程数据源接口
+ */
+interface VersionRemoteDataSource {
+    /**
+     * 获取最新版本
+     */
+    suspend fun getLatestVersion(
+        platform: Int,
+        currentVersionCode: Int?
+    ): Result<VersionResponse>
+}
+
+/**
+ * 版本远程数据源实现
+ * 
+ * 继承 ApiBaseRemoteDataSource,自动获得:
+ * - 统一的错误处理
+ * - 自动日志记录
+ * - 网络异常友好提示
+ * - 线程自动切换
+ */
+class VersionRemoteDataSourceImpl : ApiBaseRemoteDataSource(), VersionRemoteDataSource {
+    
+    private val versionApi: VersionApi by lazy {
+        com.narutohuo.xindazhou.common.network.ApiServiceFactory.create<VersionApi>()
+    }
+    
+    override suspend fun getLatestVersion(
+        platform: Int,
+        currentVersionCode: Int?
+    ): Result<VersionResponse> {
+        return executeRequest(
+            request = { versionApi.getLatestVersion(platform, currentVersionCode) },
+            errorMessage = "版本检查失败"
+        )
+    }
+}
+

+ 17 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/version/model/VersionResponse.kt

@@ -0,0 +1,17 @@
+package com.narutohuo.xindazhou.common.version.model
+
+/**
+ * 版本检查响应数据模型
+ */
+data class VersionResponse(
+    val hasUpdate: Boolean = false,           // 是否有新版本
+    val forceUpdate: Boolean = false,         // 是否强制更新
+    val versionName: String? = null,          // 版本名称(如:1.0.0)
+    val versionCode: Int? = null,             // 版本号(内部版本号)
+    val versionDesc: String? = null,          // 版本描述
+    val downloadUrl: String? = null,          // 下载地址
+    val packageSize: Long? = null,            // 包大小(字节)
+    val updateLog: String? = null,            // 更新日志
+    val publishTime: String? = null           // 发布时间
+)
+

+ 71 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/version/repository/VersionRepository.kt

@@ -0,0 +1,71 @@
+package com.narutohuo.xindazhou.common.version.repository
+
+import com.narutohuo.xindazhou.common.log.LogHelper
+import com.narutohuo.xindazhou.common.network.ApiBaseRepository
+import com.narutohuo.xindazhou.common.version.datasource.remote.VersionRemoteDataSource
+import com.narutohuo.xindazhou.common.version.model.VersionResponse
+
+/**
+ * 版本管理 Repository
+ * 
+ * 继承 ApiBaseRepository,自动获得:
+ * - 统一的错误处理
+ * - 日志记录
+ * - 数据转换扩展点
+ */
+class VersionRepository(
+    private val remoteDataSource: VersionRemoteDataSource
+) : ApiBaseRepository() {
+    
+    /**
+     * 检查版本更新
+     * 
+     * @param platform 平台类型:1-安卓,2-鸿蒙,3-iOS
+     * @param currentVersionCode 当前版本号
+     * @return 版本信息,如果没有版本或没有更新则返回 null
+     */
+    suspend fun checkVersion(
+        platform: Int,
+        currentVersionCode: Int
+    ): VersionResponse? {
+        val result = remoteDataSource.getLatestVersion(platform, currentVersionCode)
+        
+        return result.getOrNull()?.let { versionResponse ->
+            // 如果没有更新,返回 null
+            if (!versionResponse.hasUpdate) {
+                null
+            } else {
+                versionResponse
+            }
+        } ?: run {
+            // 如果请求失败,记录日志但不抛出异常
+            // 这样客户端可以正常启动,不会因为版本检查失败而阻塞
+            result.onFailure { throwable ->
+                LogHelper.e("VersionRepository", "版本检查失败", throwable)
+                handleError(throwable)
+            }
+            null
+        }
+    }
+    
+    /**
+     * 检查版本更新
+     * 
+     * @param versionResponse 版本响应数据
+     * @return 是否需要更新(true 表示需要更新)
+     */
+    fun hasUpdate(versionResponse: VersionResponse?): Boolean {
+        return versionResponse != null && versionResponse.hasUpdate
+    }
+    
+    /**
+     * 判断是否为强制更新
+     * 
+     * @param versionResponse 版本响应数据
+     * @return 是否为强制更新
+     */
+    fun isForceUpdate(versionResponse: VersionResponse?): Boolean {
+        return versionResponse?.forceUpdate == true
+    }
+}
+

+ 77 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/version/ui/UpdateDialogFragment.kt

@@ -0,0 +1,77 @@
+package com.narutohuo.xindazhou.common.version.ui
+
+import android.app.AlertDialog
+import android.app.Dialog
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import com.narutohuo.xindazhou.common.version.model.VersionResponse
+
+/**
+ * 版本更新对话框 Fragment
+ */
+class UpdateDialogFragment : DialogFragment() {
+    
+    private var versionResponse: VersionResponse? = null
+    private var onDismiss: (() -> Unit)? = null
+    
+    companion object {
+        fun create(
+            versionResponse: VersionResponse,
+            onDismiss: (() -> Unit)? = null
+        ): UpdateDialogFragment {
+            return UpdateDialogFragment().apply {
+                this.versionResponse = versionResponse
+                this.onDismiss = onDismiss
+            }
+        }
+    }
+    
+    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+        val info = versionResponse ?: return super.onCreateDialog(savedInstanceState)
+        val builder = AlertDialog.Builder(requireContext())
+        builder.setTitle("发现新版本")
+        builder.setMessage("版本号: ${info.versionName}\n更新内容: ${info.versionDesc ?: "优化体验"}")
+        
+        if (info.forceUpdate == true) {
+            // 强制更新,只能点击更新
+            builder.setCancelable(false)
+            builder.setPositiveButton("立即更新") { _, _ ->
+                openDownloadUrl(info.downloadUrl)
+                onDismiss?.invoke()
+            }
+        } else {
+            // 可选更新
+            builder.setPositiveButton("立即更新") { _, _ ->
+                openDownloadUrl(info.downloadUrl)
+                onDismiss?.invoke()
+            }
+            builder.setNegativeButton("稍后更新") { _, _ ->
+                onDismiss?.invoke()
+            }
+        }
+        
+        return builder.create()
+    }
+    
+    /**
+     * 打开下载链接
+     */
+    private fun openDownloadUrl(downloadUrl: String?) {
+        if (!downloadUrl.isNullOrEmpty()) {
+            try {
+                val intent = Intent(Intent.ACTION_VIEW, Uri.parse(downloadUrl))
+                startActivity(intent)
+            } catch (e: Exception) {
+                // 如果无法打开链接,忽略错误
+            }
+        }
+    }
+    
+    override fun onDismiss(dialog: android.content.DialogInterface) {
+        super.onDismiss(dialog)
+        onDismiss?.invoke()
+    }
+}
+

+ 128 - 0
base-common/src/main/res/layout/fragment_login.xml

@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="24dp"
+    android:background="@android:color/white">
+
+    <!-- Logo 或应用名称 -->
+    <TextView
+        android:id="@+id/tvAppName"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="新大洲本田"
+        android:textSize="32sp"
+        android:textStyle="bold"
+        android:textColor="@android:color/black"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="80dp" />
+
+    <TextView
+        android:id="@+id/tvSubtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="欢迎登录"
+        android:textSize="16sp"
+        android:textColor="@android:color/darker_gray"
+        app:layout_constraintTop_toBottomOf="@id/tvAppName"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="8dp" />
+
+    <!-- 手机号输入框 -->
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/tilMobile"
+        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="手机号"
+        app:layout_constraintTop_toBottomOf="@id/tvSubtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="48dp">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:id="@+id/etMobile"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="phone"
+            android:maxLines="1" />
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <!-- 密码输入框 -->
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/tilPassword"
+        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="密码"
+        app:passwordToggleEnabled="true"
+        app:layout_constraintTop_toBottomOf="@id/tilMobile"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:id="@+id/etPassword"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textPassword"
+            android:maxLines="1" />
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <!-- 登录按钮 -->
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnLogin"
+        android:layout_width="0dp"
+        android:layout_height="56dp"
+        android:text="登录"
+        android:textSize="16sp"
+        app:layout_constraintTop_toBottomOf="@id/tilPassword"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="32dp" />
+
+    <!-- 注册按钮 -->
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnRegister"
+        style="@style/Widget.MaterialComponents.Button.TextButton"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:text="还没有账号?立即注册"
+        app:layout_constraintTop_toBottomOf="@id/btnLogin"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp" />
+
+    <!-- 加载指示器 -->
+    <ProgressBar
+        android:id="@+id/progressBar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        app:layout_constraintTop_toBottomOf="@id/btnLogin"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp" />
+
+    <!-- 【测试功能】服务器配置按钮(左下角) -->
+    <ImageView
+        android:id="@+id/ivServerConfig"
+        android:layout_width="40dp"
+        android:layout_height="40dp"
+        android:src="@android:drawable/ic_menu_preferences"
+        android:background="?attr/selectableItemBackgroundBorderless"
+        android:padding="8dp"
+        android:contentDescription="服务器配置(测试)"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        android:layout_margin="16dp"
+        android:alpha="0.6"
+        tools:ignore="ContentDescription" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+

+ 134 - 0
base-common/src/main/res/layout/fragment_register.xml

@@ -0,0 +1,134 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="24dp"
+    android:background="@android:color/white">
+
+    <!-- Logo 或应用名称 -->
+    <TextView
+        android:id="@+id/tvAppName"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="新大洲本田"
+        android:textSize="32sp"
+        android:textStyle="bold"
+        android:textColor="@android:color/black"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="80dp" />
+
+    <TextView
+        android:id="@+id/tvSubtitle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="欢迎注册"
+        android:textSize="16sp"
+        android:textColor="@android:color/darker_gray"
+        app:layout_constraintTop_toBottomOf="@id/tvAppName"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="8dp" />
+
+    <!-- 手机号输入框 -->
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/tilMobile"
+        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="手机号"
+        app:layout_constraintTop_toBottomOf="@id/tvSubtitle"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="48dp">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:id="@+id/etMobile"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="phone"
+            android:maxLines="1" />
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <!-- 密码输入框 -->
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/tilPassword"
+        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="密码"
+        app:passwordToggleEnabled="true"
+        app:layout_constraintTop_toBottomOf="@id/tilMobile"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:id="@+id/etPassword"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textPassword"
+            android:maxLines="1" />
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <!-- 确认密码输入框 -->
+    <com.google.android.material.textfield.TextInputLayout
+        android:id="@+id/tilConfirmPassword"
+        style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:hint="确认密码"
+        app:passwordToggleEnabled="true"
+        app:layout_constraintTop_toBottomOf="@id/tilPassword"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp">
+
+        <com.google.android.material.textfield.TextInputEditText
+            android:id="@+id/etConfirmPassword"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textPassword"
+            android:maxLines="1" />
+    </com.google.android.material.textfield.TextInputLayout>
+
+    <!-- 注册按钮 -->
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnRegister"
+        android:layout_width="0dp"
+        android:layout_height="56dp"
+        android:text="注册"
+        android:textSize="16sp"
+        app:layout_constraintTop_toBottomOf="@id/tilConfirmPassword"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="32dp" />
+
+    <!-- 返回登录按钮 -->
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnBackToLogin"
+        style="@style/Widget.MaterialComponents.Button.TextButton"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:text="已有账号?返回登录"
+        app:layout_constraintTop_toBottomOf="@id/btnRegister"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp" />
+
+    <!-- 加载指示器 -->
+    <ProgressBar
+        android:id="@+id/progressBar"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        app:layout_constraintTop_toBottomOf="@id/btnRegister"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        android:layout_marginTop="16dp" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
+