# CLAUDE.md 项目开发指南 - 航空物流App ## 项目概述 **AirLogistics** - Android原生应用,航空物流全流程管理 - **包名**: com.lukouguoji.aerologic - **版本**: 1.7.9 (API 24-30) - **架构**: MVVM + 组件化 + Kotlin + DataBinding - **屏幕**: 横屏 1152dp × 720dp ## 快速构建 ```bash ./gradlew assembleDebug # 构建Debug版本 ./gradlew clean # 清理构建 ``` ## 核心架构 ### MVVM层级 ``` Activity → BaseBindingActivity → ViewModel → BaseViewModel/BasePageViewModel → API ``` ### 关键基类 - **BaseBindingActivity**: DataBinding + ViewModel自动绑定 - **BaseViewModel**: Loading管理、协程支持 - **BasePageViewModel**: 分页列表(含PageModel) - **CommonAdapter + BaseViewHolder**: 列表适配器 - **PadSearchLayout**: 搜索区域输入控件 - **PadDataLayout**: 数据展示/编辑控件 ### 标准Activity模板 ```kotlin @Route(path = ARouterConstants.ACTIVITY_URL_XXX) class XxxActivity : BaseBindingActivity() { override fun layoutId() = R.layout.activity_xxx override fun viewModelClass() = XxxViewModel::class.java override fun initOnCreate(savedInstanceState: Bundle?) { setBackArrow("页面标题") binding.viewModel = viewModel // 初始化UI } } ``` ### 标准ViewModel模板 **列表页ViewModel:** ```kotlin class XxxListViewModel : BasePageViewModel() { val searchText = MutableLiveData() val itemLayoutId = R.layout.item_xxx val itemViewHolder = XxxViewHolder::class.java override fun getData() { val params = mapOf( "page" to pageModel.page, "limit" to pageModel.limit, "searchText" to searchText.value ).toRequestBody() launchLoadingCollect({ NetApply.api.getXxxList(params) }) { onSuccess = { pageModel.handleListBean(it) } } } override fun onItemClick(position: Int, type: Int) { val bean = pageModel.rv!!.commonAdapter()!!.getItem(position) as XxxBean // 跳转详情 } } ``` **详情页ViewModel:** ```kotlin class XxxDetailsViewModel : BaseViewModel() { var id = "" val dataBean = MutableLiveData() fun initOnCreated(intent: Intent) { id = intent.getStringExtra(Constant.Key.ID) ?: "" getData() } private fun getData() { launchLoadingCollect({ NetApply.api.getXxxDetails(id) }) { onSuccess = { dataBean.value = it.data ?: XxxBean() } } } } ``` **编辑页ViewModel:** ```kotlin class XxxAddViewModel : BaseViewModel() { val pageType = MutableLiveData(DetailsPageType.Add) // 必须用LiveData var id = "" val dataBean = MutableLiveData(XxxBean()) fun initOnCreated(intent: Intent) { pageType.value = DetailsPageType.valueOf( intent.getStringExtra(Constant.Key.PAGE_TYPE) ?: DetailsPageType.Add.name ) if (pageType.value != DetailsPageType.Add) { id = intent.getStringExtra(Constant.Key.ID) ?: "" loadData() } } fun submit() { val bean = dataBean.value ?: return if (bean.name.verifyNullOrEmpty("请输入名称")) return launchLoadingCollect({ val params = mapOf("id" to id, "name" to bean.name) .toRequestBody(removeEmptyOrNull = true) NetApply.api.saveXxx(params) }) { onSuccess = { showToast("保存成功") viewModelScope.launch { FlowBus.with(ConstantEvent.EVENT_REFRESH).emit("refresh") } getTopActivity().finish() } } } } ``` ## 网络请求 ### 请求方法 ```kotlin // 带Loading请求 launchLoadingCollect({ NetApply.api.saveXxx(params) }) { onSuccess = { /* 成功处理 */ } onFailed = { code, msg -> /* 失败处理 */ } } // 无Loading请求(后台刷新) launchCollect({ NetApply.api.getXxx() }) { onSuccess = { /* 成功处理 */ } } // 参数转换 val params = mapOf("key" to "value").toRequestBody(removeEmptyOrNull = true) ``` ### API接口定义 ```kotlin // 位置: module_base/.../http/net/Api.kt @POST("api/xxx/list") suspend fun getXxxList(@Body data: RequestBody): BaseListBean @POST("api/xxx/details") suspend fun getXxxDetails(@Query("id") id: String): BaseResultBean @POST("api/xxx/save") suspend fun saveXxx(@Body data: RequestBody): BaseResultBean ``` ## DataBinding + LiveData + ViewModel 核心知识 ### 🎯 最关键的设置(最常见错误) **必须在 Activity 中设置 lifecycleOwner,否则 XML 中的 LiveData 不会自动更新 UI!** ```kotlin override fun initOnCreate(savedInstanceState: Bundle?) { setBackArrow("页面标题") binding.viewModel = viewModel // ⚠️ 关键:必须设置,否则 LiveData 无法自动更新 UI binding.lifecycleOwner = this } ``` **BaseBindingActivity 已自动设置**,但如果手动使用 DataBinding 时务必记住! ### 📖 ViewModel 中 LiveData 的定义规范 ```kotlin class XxxViewModel : BaseViewModel() { // ✅ 推荐:对外暴露不可变的 LiveData private val _dataBean = MutableLiveData() val dataBean: LiveData = _dataBean // ✅ 简化写法:直接使用 MutableLiveData(项目常用) val searchText = MutableLiveData() val pageType = MutableLiveData(DetailsPageType.Add) fun loadData() { // 主线程更新 _dataBean.value = XxxBean() // 子线程更新(协程中不需要,已在主线程) // _dataBean.postValue(XxxBean()) } } ``` ### 📝 XML 中 LiveData 的绑定方式 #### 1. 单向绑定 `@{}`(只显示,ViewModel → UI) ```xml ``` #### 2. 双向绑定 `@={}`(可编辑,UI ↔ ViewModel) ```xml ``` **双向绑定要求**: - 字段必须是 `MutableLiveData` - 用户输入时自动更新 ViewModel 的值 - ViewModel 更新值时自动更新 UI #### 3. 点击事件绑定 ```xml