diff --git a/CLAUDE.md b/CLAUDE.md index dc66175..459b19f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,207 +2,67 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Android 项目特定说明 - -### 项目概况 +## 项目概况 **项目名称**: AirLogistics - 航空物流信息管理系统 -**项目类型**: Android 原生应用 -**架构模式**: MVVM + 组件化 -**开发语言**: Kotlin 1.6.21 + Java -**当前版本**: 1.8.4 (versionCode 84) - -**SDK 版本要求**: -- minSdkVersion: 24 (Android 7.0) -- targetSdkVersion: 30 (Android 10) -- compileSdkVersion: 31 +**架构模式**: MVVM + 组件化 | **语言**: Kotlin 1.6.21 + Java | **版本**: 1.8.4 (versionCode 84) +**SDK**: minSdk 24 / targetSdk 30 / compileSdk 31 | **Gradle**: 7.3.3 | **JDK**: 1.8 ### 核心架构 -#### MVVM 基类体系 -- **BaseActivity**: 提供协程支持、Loading管理、扫码功能、键盘控制 -- **BaseBindingActivity**: DataBinding自动绑定、ViewModel生命周期管理 -- **BaseViewModel**: Loading管理、Activity结果处理 -- **BasePageViewModel**: 分页列表专用,集成PageModel自动处理分页 -- **CommonAdapter + BaseViewHolder**: 统一列表适配器封装 +- **MVVM 基类**: `BaseActivity` → `BaseBindingActivity`(DataBinding)、`BaseViewModel` → `BasePageViewModel`(分页列表) +- **适配器**: `CommonAdapter` + `BaseViewHolder` 统一列表封装 +- **路由**: ARouter 1.5.2 | **事件**: FlowBus(Flow)+ EventBus 3.1.1 +- **网络**: Retrofit 2.6.1 + OkHttp 3.12.12 + Coroutines + - `launchCollect`:无 Loading 后台请求 + - `launchLoadingCollect`:带 Loading 请求 + - `toRequestBody`:Map/Bean 转 JSON -#### 组件化模块划分 -- **app/**: 应用壳层,整合所有业务模块 -- **module_base/**: 核心基础库(MVVM基类、网络框架、UI组件) -- **module_gnc/**: 国内出港业务模块 -- **module_gnj/**: 国内进港业务模块 -- **module_gjc/**: 国际出港业务模块 -- **module_gjj/**: 国际进港业务模块 -- **module_hangban/**: 航班管理模块 -- **module_cargo/**: 货物追踪模块 -- **module_mit/**: 监装监卸管理模块 -- **module_p/**: PDA专用功能模块 -- **Printer/**: 蓝牙打印模块 -- **MPChartLib/**: 图表库模块 +### 组件化模块 -#### 模块间通信 -- **路由**: ARouter 1.5.2 实现模块间页面跳转 -- **事件总线**: FlowBus(基于Flow) + EventBus 3.1.1 -- **依赖注入**: 基于ServiceLoader的服务发现机制 - -#### 网络请求框架 -- **技术栈**: Retrofit 2.6.1 + OkHttp 3.12.12 + Kotlin Coroutines -- **扩展函数**: - - `launchCollect`: 无Loading的后台请求 - - `launchLoadingCollect`: 带Loading的关键操作 - - `toRequestBody`: Map/Bean自动转JSON -- **拦截器**: 自动添加Token、时间戳,统一错误处理 +| 模块 | 说明 | 模块 | 说明 | +|------|------|------|------| +| `app/` | 应用壳层 | `module_base/` | 核心基础库 | +| `module_gnc/` | 国内出港 | `module_gnj/` | 国内进港 | +| `module_gjc/` | 国际出港 | `module_gjj/` | 国际进港 | +| `module_hangban/` | 航班管理 | `module_cargo/` | 货物追踪 | +| `module_mit/` | 监装监卸 | `module_p/` | PDA 功能 | +| `Printer/` | 蓝牙打印 | `MPChartLib/` | 图表库 | ### 关键目录结构 ``` aerologic-app/ ├── app/src/main/java/com/lukouguoji/aerologic/ -│ ├── ui/viewModel/ # ViewModel文件 -│ ├── ui/fragment/ # Fragment文件 (HomeFragment, MineFragment等) +│ ├── ui/viewModel/ # ViewModel +│ ├── ui/fragment/ # Fragment │ └── page/ # 业务页面 ├── module_base/src/main/java/com/lukouguoji/module_base/ -│ ├── BaseActivity.kt # 基础Activity类 -│ ├── BaseFragment.kt # 基础Fragment类 -│ ├── bean/ # 数据模型 (BaseResultBean, BaseListBean) -│ ├── service/viewModel/ # ViewModel层 -│ ├── ui/page/ # UI页面 -│ ├── ui/weight/ # 自定义UI组件 (PadSearchLayout, PadDataLayout) -│ ├── http/ # 网络请求框架 -│ └── ktx/ # Kotlin扩展函数 -├── module_gnc/src/main/ # 国内出港业务代码 -├── module_gnj/src/main/ # 国内进港业务代码 +│ ├── base/ # 基类 (BaseActivity, BaseViewModel, BaseViewHolder, BaseDialogModel) +│ ├── bean/ # 数据模型 +│ ├── common/ # 常量 (Constant, DetailsPageType, ConstantEvent) +│ ├── http/net/ # 网络 (NetApply, Api) +│ ├── ktx/ # 扩展函数 +│ ├── impl/ # FlowBus, observe +│ ├── interfaces/ # IOnItemClickListener +│ ├── router/ # ARouterConstants +│ └── ui/weight/ # UI 组件 (PadSearchLayout, PadDataLayout, PadDataLayoutNew) +├── module_gjc/src/main/ # 国际出港(典型参考模块) └── 其他业务模块... ``` -### 开发规范 - -#### 命名约定 -- **Activity**: `XxxActivity` (例: `LoginActivity`) -- **Fragment**: `XxxFragment` (例: `HomeFragment`) -- **ViewModel**: `XxxViewModel` (例: `LoginViewModel`) -- **Adapter**: `XxxAdapter` (例: `CargoListAdapter`) -- **ViewHolder**: `XxxViewHolder` (例: `CargoItemViewHolder`) -- **Layout文件**: `activity_xxx.xml`, `fragment_xxx.xml`, `item_xxx.xml` - -#### 文件组织规范 -- 业务页面放在对应模块的 `ui/page/` 目录下 -- ViewModel放在 `service/viewModel/` 目录下 -- 数据模型放在 `bean/` 目录下 -- 适配器放在 `adapter/` 目录下 - -#### DataBinding 使用要点 -- 布局文件使用 `` 标签包裹 -- 定义 `` 绑定 ViewModel -- 使用 `@{}` 表达式进行数据绑定 -- Activity/Fragment 中使用 `DataBindingUtil` 或自动生成的 Binding 类 - -#### 协程使用规范 -- 在 ViewModel 中使用 `viewModelScope` 启动协程 -- 网络请求使用 `launchCollect` 或 `launchLoadingCollect` 扩展函数 -- Flow 用于响应式数据流处理 -- 使用 `withContext(Dispatchers.IO)` 进行IO操作 - -### 常用构建命令 - -```bash -# 清理构建缓存 -./gradlew clean - -# 构建 Debug APK -./gradlew assembleDebug - -# 构建 Release APK (已签名) -./gradlew assembleRelease - -# 安装到设备 -./gradlew installDebug - -# 运行 Lint 检查 -./gradlew lint - -# 查看已连接设备 -adb devices -l - -# 查看应用日志 -adb logcat | grep "com.lukouguoji.aerologic" -``` - -### 快捷命令 - -项目已配置以下快捷命令 (在 `.claude/commands/` 目录): -- `/build-debug` - 构建 Debug APK -- `/build-release` - 构建 Release APK -- `/install` - 安装到设备 -- `/clean-build` - 清理并构建 -- `/check-modules` - 检查所有模块 -- `/lint` - 运行代码检查 -- `/devices` - 列出已连接设备 -- `/logs` - 查看应用日志 - -### 组件化开发模式 - -项目支持模块独立运行调试: - -1. 编辑 `gradle.properties` -2. 设置 `isBuildModule=true` (独立模式) 或 `false` (集成模式) -3. Sync项目并运行对应模块 - -**注意**: 独立模式下,各模块作为独立应用运行;集成模式下,所有模块整合到app壳层。 - -### 环境配置 - -#### 开发环境要求 -- **IDE**: Android Studio Arctic Fox (2020.3.1) 或更高版本 -- **JDK**: 1.8 -- **Gradle**: 7.3.3 -- **Kotlin**: 1.6.21 - -#### 服务器配置 -- **配置文件**: `module_base/src/main/res/values/strings.xml` -- **主服务器**: `system_url_inner` -- **地磅服务器**: `weight_url` -- **运行时**: 可通过 SharedPreferences 动态修改IP地址 - -#### 签名配置 -- **KeyStore**: `key.jks` (项目根目录) -- **Store密码**: `123321` -- **Key密码**: `123321` -- **别名**: `key` - -### 常见问题解决 - -#### 依赖下载失败 -1. 检查网络连接 -2. 使用阿里云Maven镜像 (已在 build.gradle 中配置) -3. 如需手动配置 Gradle,参考 README.md 中的依赖配置章节 - -#### 模块编译错误 -1. 执行 `./gradlew clean` -2. 检查 `gradle.properties` 中的 `isBuildModule` 设置 -3. Sync Project with Gradle Files - -#### ADB 连接问题 -```bash -# 重启 ADB 服务 -adb kill-server && adb start-server - -# 查看设备连接状态 -adb devices -l - -# 无线调试 (Android 11+) -adb pair : -adb connect : -``` - --- -## 详细开发指南 +## 6 种典型页面类型 -### 标准代码模板 +基于 `module_gjc`(国际出港)模块归纳,覆盖项目中所有常见页面模式。 -#### Activity 模板 +### 类型 1:列表查询页 +**代表**: `GjcBoxWeighingActivity` / `GjcInspectionActivity` +**结构**: 搜索条件区 + SmartRefreshLayout 分页列表 + 底部统计/操作栏 + +**Activity 骨架**: ```kotlin @Route(path = ARouterConstants.ACTIVITY_URL_XXX) class XxxActivity : BaseBindingActivity() { @@ -212,1851 +72,661 @@ class XxxActivity : BaseBindingActivity() { override fun initOnCreate(savedInstanceState: Bundle?) { setBackArrow("页面标题") binding.viewModel = viewModel - // 初始化UI + + // 绑定分页 + viewModel.pageModel.bindSmartRefreshLayout(binding.srl, binding.rv, viewModel, this) + + // 监听刷新事件 + FlowBus.with(ConstantEvent.EVENT_REFRESH).observe(this) { viewModel.refresh() } + + viewModel.refresh() } } ``` -#### ViewModel 模板 - -**列表页 ViewModel:** - +**ViewModel 骨架**: ```kotlin -class XxxListViewModel : BasePageViewModel() { - val searchText = MutableLiveData() - val itemLayoutId = R.layout.item_xxx +class XxxViewModel : BasePageViewModel() { + // 搜索条件 + val flightDate = MutableLiveData(DateUtils.getCurrentTime().formatDate()) + val flightNo = MutableLiveData("") + + // 适配器配置 val itemViewHolder = XxxViewHolder::class.java + val itemLayoutId = R.layout.item_xxx + + // 统计数据 + val totalCount = MutableLiveData("0") + + fun searchClick() { refresh() } override fun getData() { val params = mapOf( - "page" to pageModel.page, - "limit" to pageModel.limit, - "searchText" to searchText.value + "pageNum" to pageModel.page, "pageSize" to pageModel.limit, + "fdate" to flightDate.value?.ifEmpty { null }, + "fno" to flightNo.value?.ifEmpty { null } ).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() } + // 统计(无 Loading,不阻塞列表) + launchCollect({ NetApply.api.getXxxTotal(totalParams) }) { + onSuccess = { totalCount.value = (it.data?.count ?: 0).toString() } } } } ``` -**编辑页 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() - } - } - } -} -``` - -### DataBinding + LiveData 核心知识 - -#### 最关键的设置 (最常见错误) - -**必须在 Activity 中设置 lifecycleOwner,否则 XML 中的 LiveData 不会自动更新 UI!** - -```kotlin -override fun initOnCreate(savedInstanceState: Bundle?) { - setBackArrow("页面标题") - binding.viewModel = viewModel - - // ⚠️ 关键:必须设置,否则 LiveData 无法自动更新 UI - binding.lifecycleOwner = this -} -``` - -**BaseBindingActivity 已自动设置**,但如果手动使用 DataBinding 时务必记住! - -#### XML 中 LiveData 的绑定方式 - -**1. 单向绑定 `@{}`(只显示,ViewModel → UI)** - +**布局结构**: ```xml - - - - + + - - + + + + + + - - + + + + - - - - - - + + + + + ``` -**2. 双向绑定 `@={}`(可编辑,UI ↔ ViewModel)** - -```xml - - - - - - - - -``` - -**双向绑定要求**: -- 字段必须是 `MutableLiveData` -- 用户输入时自动更新 ViewModel 的值 -- ViewModel 更新值时自动更新 UI - -**3. 点击事件绑定** - -```xml - -