# CLAUDE.md

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 基类体系
- **BaseActivity**: 提供协程支持、Loading管理、扫码功能、键盘控制
- **BaseBindingActivity**: DataBinding自动绑定、ViewModel生命周期管理
- **BaseViewModel**: Loading管理、Activity结果处理
- **BasePageViewModel**: 分页列表专用,集成PageModel自动处理分页
- **CommonAdapter + BaseViewHolder**: 统一列表适配器封装

#### 组件化模块划分
- **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、时间戳,统一错误处理

### 关键目录结构

```
aerologic-app/
├── app/src/main/java/com/lukouguoji/aerologic/
│   ├── ui/viewModel/          # ViewModel文件
│   ├── ui/fragment/           # Fragment文件 (HomeFragment, MineFragment等)
│   └── 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/       # 国内进港业务代码
└── 其他业务模块...
```

### 开发规范

#### 命名约定
- **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 使用要点
- 布局文件使用 `<layout>` 标签包裹
- 定义 `<variable>` 绑定 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 <IP>:<PORT>
adb connect <IP>:<PORT>
```

---

## 详细开发指南

### 标准代码模板

#### Activity 模板

```kotlin
@Route(path = ARouterConstants.ACTIVITY_URL_XXX)
class XxxActivity : BaseBindingActivity<ActivityXxxBinding, XxxViewModel>() {
    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<String>()
    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<XxxBean>()

    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<String>(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
<layout>
    <data>
        <variable
            name="viewModel"
            type="com.lukouguoji.xxx.XxxViewModel" />
    </data>

    <!-- LiveData 自动解包:直接访问 value -->
    <TextView
        android:text="@{viewModel.dataBean.name}" />

    <!-- 条件判断 -->
    <View
        android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" />

    <!-- 空值处理 -->
    <TextView
        android:text="@{viewModel.dataBean.name ?? `默认值`}" />

    <!-- 字符串拼接(使用反引号) -->
    <TextView
        android:text="@{`姓名:` + viewModel.dataBean.name}" />
</layout>
```

**2. 双向绑定 `@={}`(可编辑,UI ↔ ViewModel)**

```xml
<!-- EditText 双向绑定 -->
<EditText
    android:text="@={viewModel.searchText}" />

<!-- PadSearchLayout 双向绑定 -->
<PadSearchLayout
    type="@{SearchLayoutType.INPUT}"
    value="@={viewModel.waybillNo}" />

<!-- PadDataLayout 双向绑定 -->
<PadDataLayout
    type="@{DataLayoutType.INPUT}"
    value="@={viewModel.dataBean.name}" />
```

**双向绑定要求**:
- 字段必须是 `MutableLiveData`
- 用户输入时自动更新 ViewModel 的值
- ViewModel 更新值时自动更新 UI

**3. 点击事件绑定**

```xml
<!-- Lambda 表达式(推荐) -->
<Button
    android:onClick="@{() -> viewModel.submit()}" />

<!-- 带参数 -->
<Button
    android:onClick="@{(v) -> viewModel.onItemClick(v, 1)}" />

<!-- 自定义监听器 -->
<PadSearchLayout
    setOnIconClickListener="@{(v) -> viewModel.scanWaybill()}" />
```

#### DataBinding 常见错误与解决方法

**错误 1: 忘记设置 lifecycleOwner**

```kotlin
// ❌ 错误:LiveData 变化但 UI 不更新
override fun initOnCreate(savedInstanceState: Bundle?) {
    binding.viewModel = viewModel
    // 忘记设置 lifecycleOwner
}

// ✅ 正确:必须设置
override fun initOnCreate(savedInstanceState: Bundle?) {
    binding.viewModel = viewModel
    binding.lifecycleOwner = this  // 关键!
}
```

**错误 2: 字符串未使用反引号**

```xml
<!-- ❌ 错误:普通引号会被识别为 XML 属性 -->
<TextView
    android:text="@{"姓名:" + viewModel.name}" />

<!-- ✅ 正确:使用反引号 ` -->
<TextView
    android:text="@{`姓名:` + viewModel.name}" />
```

**错误 3: 访问 LiveData 的 value 属性**

```xml
<!-- ❌ 错误:DataBinding 会自动解包,不需要 .value -->
<TextView
    android:text="@{viewModel.dataBean.value.name}" />

<!-- ✅ 正确:直接访问 -->
<TextView
    android:text="@{viewModel.dataBean.name}" />
```

**错误 4: 修改对象属性后 UI 不更新**

```kotlin
// ❌ 错误:修改对象内部属性,LiveData 不会触发更新
val bean = dataBean.value
bean?.name = "新名称"
// UI 不会更新,因为 LiveData 的引用没变

// ✅ 正确:重新赋值 LiveData
val bean = dataBean.value?.copy(name = "新名称")
dataBean.value = bean
```

### 核心 UI 组件详细使用

#### PadSearchLayout - 搜索输入框

```xml
<!-- 文本输入+扫码 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
    type="@{SearchLayoutType.INPUT}"
    value="@={viewModel.waybillNo}"
    hint="@{`请输入运单号`}"
    icon="@{@mipmap/scan_code}"
    setOnIconClickListener="@{(v)-> viewModel.scanWaybill()}" />

<!-- 日期选择 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
    type="@{SearchLayoutType.DATE}"
    value="@={viewModel.date}"
    icon="@{@mipmap/calendar}" />

<!-- 下拉选择 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
    type="@{SearchLayoutType.SPINNER}"
    list="@{viewModel.statusList}"
    value="@={viewModel.status}" />
```

**类型**: `INPUT` / `INTEGER` / `SPINNER` / `DATE`

#### PadDataLayout - 数据展示/编辑

```xml
<!-- 文本输入 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
    type="@{DataLayoutType.INPUT}"
    title='@{"运单号:"}'
    titleLength="@{5}"
    value='@={viewModel.bean.waybillNo}'
    enable="@{viewModel.pageType != DetailsPageType.Details}"
    required="@{true}"
    maxLength="@{11}" />

<!-- 下拉选择 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
    type="@{DataLayoutType.SPINNER}"
    title='@{"状态:"}'
    list="@{viewModel.statusList}"
    value='@={viewModel.bean.status}' />

<!-- 多行输入 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
    type="@{DataLayoutType.INPUT}"
    inputHeight="@{100}"
    value='@={viewModel.bean.remark}' />
```

**类型**: `INPUT` / `SPINNER` / `DATE`
**注意**: 使用 PadDataLayout 时,`titleLength` 通常设置为 5

#### PadDataLayoutNew - 输入完成回调

**使用场景**: 当需要在用户完成输入（失去焦点）时触发自动查询或其他操作

**正确用法**: 使用方法引用语法 `viewModel::methodName`

```xml
<!-- 输入完成后自动查询 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
    android:id="@+id/carIdInput"
    hint='@{"请输入架子车号"}'
    setRefreshCallBack="@{viewModel::onCarIdInputComplete}"
    title='@{"架子车号"}'
    titleLength="@{5}"
    type="@{DataLayoutType.INPUT}"
    value='@={viewModel.carId}'
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="1" />

<!-- 日期选择完成后触发回调 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
    setRefreshCallBack="@{viewModel::onFlightDateInputComplete}"
    title='@{"航班日期"}'
    titleLength="@{5}"
    type="@{DataLayoutType.DATE}"
    value='@={viewModel.flightDate}'
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_weight="1" />
```

**ViewModel 中的实现**:

```kotlin
/**
 * 架子车号输入完成时调用
 */
fun onCarIdInputComplete() {
    val id = carId.value
    if (!id.isNullOrEmpty() && id != lastQueriedCarId) {
        lastQueriedCarId = id
        queryFlatcarInfo(id)
    }
}

/**
 * 航班日期选择完成时调用
 */
fun onFlightDateInputComplete() {
    // 清除查询标记，以便重新查询
    lastQueriedFlight = ""
    queryFlightIfReady()
}
```

**关键要点**:
- ✅ **正确**: `setRefreshCallBack="@{viewModel::methodName}"` - 使用方法引用
- ❌ **错误**: `setRefreshCallBack="@{() -> viewModel.methodName()}"` - Lambda 表达式会导致编译错误
- 回调在输入框失去焦点时触发 (INPUT/SPINNER 类型)
- 回调在日期选择完成后触发 (DATE 类型)
- 适合实现输入完成后的自动查询功能

### 开发检查清单

#### ⚠️ 重要提醒

**新建 Activity 后必须在 AndroidManifest.xml 中注册,否则会报 ActivityNotFoundException 错误!**

#### 列表页开发 (8步)

1. **创建Bean** (`module_base/.../bean/XxxBean.kt`)
2. **添加API接口** (`Api.kt` → `getXxxList()`)
3. **创建ViewHolder** (继承`BaseViewHolder`)
4. **创建ViewModel** (继承`BasePageViewModel`)
5. **创建Activity** (继承`BaseBindingActivity`)
6. **创建Layout** (`activity_xxx_list.xml` + `item_xxx.xml`)
7. **注册路由** (`ARouterConstants`)
8. **⚠️ 在AndroidManifest.xml中注册Activity** (`app/src/main/AndroidManifest.xml`)

**AndroidManifest.xml注册示例:**

```xml
<!-- 在app/src/main/AndroidManifest.xml的<application>标签内添加 -->
<activity
    android:name="com.lukouguoji.gnc.page.xxx.XxxActivity"
    android:configChanges="orientation|keyboardHidden"
    android:exported="false"
    android:screenOrientation="userLandscape" />
```

**关键代码:**

```kotlin
// Activity中绑定分页
viewModel.pageModel.bindSmartRefreshLayout(
    binding.srl, binding.recyclerView, viewModel, this
)
binding.recyclerView.addOnItemClickListener(viewModel)
```

#### 详情页开发 (5步)

1. **添加API接口** (`getXxxDetails()`)
2. **创建ViewModel** (继承`BaseViewModel`)
3. **创建Activity** (含`companion object`静态start方法)
4. **创建Layout**
5. **⚠️ 在AndroidManifest.xml中注册Activity**

**静态启动方法:**

```kotlin
companion object {
    @JvmStatic
    fun start(context: Context, id: String) {
        val starter = Intent(context, XxxDetailsActivity::class.java)
            .putExtra(Constant.Key.ID, id)
        context.startActivity(starter)
    }
}
```

#### 编辑页开发 (6步)

1. **添加API接口** (`saveXxx()` + `getXxxDetails()`)
2. **创建ViewModel** (`pageType`使用`MutableLiveData`)
3. **创建Activity** (多个静态start方法: `startForAdd/Edit/Details`)
4. **创建Layout** (根据`pageType`控制enable)
5. **FlowBus发送刷新事件**
6. **⚠️ 在AndroidManifest.xml中注册Activity**

**Activity多入口:**

```kotlin
companion object {
    @JvmStatic
    fun startForAdd(context: Context) {
        val starter = Intent(context, XxxAddActivity::class.java)
            .putExtra(Constant.Key.PAGE_TYPE, DetailsPageType.Add.name)
        context.startActivity(starter)
    }

    @JvmStatic
    fun startForEdit(context: Context, id: String) {
        val starter = Intent(context, XxxAddActivity::class.java)
            .putExtra(Constant.Key.PAGE_TYPE, DetailsPageType.Modify.name)
            .putExtra(Constant.Key.ID, id)
        context.startActivity(starter)
    }
}
```

### 列表查询+多选+批量处理页面开发 (完整指南)

这是项目中最常见的业务场景之一：列表查询、多选Item、批量操作。本指南基于`IntExpOutHandoverActivity`(国际出港-出库交接)的实际案例总结。

#### 典型场景特征

- ✅ 顶部多条件搜索区域
- ✅ 列表支持多选(图片切换表示选择状态)
- ✅ 底部全选按钮+统计信息+批量操作按钮
- ✅ 分页加载数据

#### 开发步骤总览 (8步)

1. 修改/创建Bean (添加ObservableBoolean选择状态)
2. 定义API接口 (列表查询+统计查询+批量操作)
3. 创建ViewHolder (处理选择图标点击)
4. 创建ViewModel (继承BasePageViewModel)
5. 创建Activity布局 (搜索区+列表+底部栏)
6. 创建列表项布局 (使用completeSpace对齐)
7. 创建Activity (绑定数据+观察全选状态)
8. 在AndroidManifest.xml中注册

---

#### 步骤1: 修改/创建Bean

**关键点**: 使用`ObservableBoolean`支持实时UI更新

```kotlin
import androidx.databinding.ObservableBoolean

class GjcUldUseBean {
    // ... 业务字段 ...

    // ========== UI扩展字段 ==========
    val checked: ObservableBoolean = ObservableBoolean(false)  // 选中状态

    // 兼容现有API的isSelected属性
    var isSelected: Boolean
        get() = checked.get()
        set(value) = checked.set(value)
}
```

**为什么用ObservableBoolean而不是Boolean?**
- DataBinding会自动观察ObservableBoolean的变化
- 调用`checked.set(true)`会立即触发UI刷新
- 普通Boolean需要手动调用`notifyDataSetChanged()`

---

#### 步骤2: 定义API接口

```kotlin
// Api.kt
/**
 * 列表查询(分页)
 */
@POST("IntExpOutHandover/pageQuery")
suspend fun getIntExpOutHandoverList(@Body data: RequestBody): BaseListBean<GjcUldUseBean>

/**
 * 统计查询(合计信息)
 */
@POST("IntExpOutHandover/pageQueryTotal")
suspend fun getIntExpOutHandoverTotal(@Body data: RequestBody): BaseResultBean<ManifestTotalDto>

/**
 * 批量操作(交接完成)
 */
@POST("IntExpOutHandover/handover")
suspend fun completeHandover(@Body data: RequestBody): BaseResultBean<Boolean>
```

---

#### 步骤3: 创建ViewHolder

**关键点**: 添加选择图标点击事件

```kotlin
class IntExpOutHandoverViewHolder(view: View) :
    BaseViewHolder<GjcUldUseBean, ItemIntExpOutHandoverBinding>(view) {

    override fun onBind(item: Any?, position: Int) {
        val bean = getItemBean(item) ?: return
        binding.bean = bean
        binding.position = position
        binding.executePendingBindings()

        // 添加图标点击事件 - 切换选择状态
        binding.ivIcon.setOnClickListener {
            // 反转checked状态
            bean.checked.set(!bean.checked.get())

            // 立即更新UI (图片自动切换)
            binding.executePendingBindings()
        }
    }
}
```

---

#### 步骤4: 创建ViewModel

**关键点**: 继承`BasePageViewModel`，实现全选逻辑

```kotlin
class IntExpOutHandoverViewModel : BasePageViewModel() {

    // ========== 搜索条件 ==========
    val flightDate = MutableLiveData("")
    val flightNo = MutableLiveData("")
    val fdest = MutableLiveData("")
    val uldNo = MutableLiveData("")

    // ========== 统计信息 ==========
    val totalCount = MutableLiveData("0")
    val totalPc = MutableLiveData("0")
    val totalWeight = MutableLiveData("0")

    // ========== 全选状态 ==========
    val isAllChecked = MutableLiveData(false)

    init {
        // 监听全选状态，自动更新所有列表项
        isAllChecked.observeForever { checked ->
            val list = pageModel.rv?.commonAdapter()?.items as? List<GjcUldUseBean> ?: return@observeForever
            list.forEach { it.checked.set(checked) }
            pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
        }
    }

    // ========== 适配器配置 ==========
    val itemViewHolder = IntExpOutHandoverViewHolder::class.java
    val itemLayoutId = R.layout.item_int_exp_out_handover

    /**
     * 搜索按钮点击
     */
    fun searchClick() {
        refresh()
    }

    /**
     * 全选按钮点击 (切换全选状态)
     */
    fun checkAllClick() {
        val list = pageModel.rv?.commonAdapter()?.items as? List<GjcUldUseBean> ?: return

        // 切换全选状态
        val shouldCheckAll = !isAllChecked.value!!
        list.forEach { it.checked.set(shouldCheckAll) }
        isAllChecked.value = shouldCheckAll

        pageModel.rv?.commonAdapter()?.notifyDataSetChanged()
    }

    /**
     * 扫码ULD
     */
    fun scanUld() {
        ScanModel.startScan(getTopActivity(), Constant.RequestCode.ULD)
    }

    /**
     * 完成交接 (批量操作)
     */
    fun completeHandover() {
        val list = pageModel.rv?.commonAdapter()?.items as? List<GjcUldUseBean> ?: return
        val selectedItems = list.filter { it.isSelected }

        if (selectedItems.isEmpty()) {
            showToast("请选择要交接的ULD")
            return
        }

        val requestData = selectedItems.toRequestBody()

        launchLoadingCollect({ NetApply.api.completeHandover(requestData) }) {
            onSuccess = {
                showToast("交接完成")
                viewModelScope.launch {
                    FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
                }
                refresh()
            }
        }
    }

    /**
     * 获取数据 (重写BasePageViewModel)
     */
    override fun getData() {
        // 构建搜索条件
        val filterParams = mapOf(
            "fdate" to flightDate.value?.ifEmpty { null },
            "fno" to flightNo.value?.ifEmpty { null },
            "fdest" to fdest.value?.ifEmpty { null },
            "uld" to uldNo.value?.ifEmpty { null }
        )

        // 列表参数 (含分页)
        val listParams = (filterParams + mapOf(
            "pageNum" to pageModel.page,
            "pageSize" to pageModel.limit
        )).toRequestBody()

        // 统计参数 (无分页)
        val totalParams = filterParams.toRequestBody()

        // 获取列表 (带Loading)
        launchLoadingCollect({ NetApply.api.getIntExpOutHandoverList(listParams) }) {
            onSuccess = { pageModel.handleListBean(it) }
        }

        // 获取统计信息 (后台请求，不阻塞列表)
        launchCollect({ NetApply.api.getIntExpOutHandoverTotal(totalParams) }) {
            onSuccess = { result ->
                val data = result.data
                totalCount.value = (data?.wbNumber ?: 0).toString()
                totalPc.value = (data?.totalPc ?: 0).toString()
                totalWeight.value = (data?.totalWeight ?: 0.0).toString()
            }
        }
    }
}
```

---

#### 步骤5: 创建Activity布局

**关键要素**: 搜索区 + 列表 + 底部栏

```xml
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- 标题栏 -->
    <include layout="@layout/title_tool_bar" />

    <!-- 搜索区域 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <!-- 航班日期 -->
        <com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
            android:layout_width="0dp"
            android:layout_weight="1"
            type="@{SearchLayoutType.DATE}"
            value="@={viewModel.flightDate}" />

        <!-- 航班号 -->
        <com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
            android:layout_width="0dp"
            android:layout_weight="1"
            type="@{SearchLayoutType.INPUT}"
            value="@={viewModel.flightNo}" />

        <!-- ULD编号 (带扫码) -->
        <com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
            android:layout_width="0dp"
            android:layout_weight="1"
            type="@{SearchLayoutType.INPUT}"
            value="@={viewModel.uldNo}"
            icon="@{@drawable/scan_code}"
            setOnIconClickListener="@{(v)-> viewModel.scanUld()}" />

        <!-- 搜索按钮 -->
        <ImageView
            style="@style/iv_search_action"
            android:onClick="@{()-> viewModel.searchClick()}"
            android:src="@drawable/img_search" />

    </LinearLayout>

    <!-- 列表 -->
    <com.scwang.smart.refresh.layout.SmartRefreshLayout
        android:id="@+id/srl"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            itemLayoutId="@{viewModel.itemLayoutId}"
            viewHolder="@{viewModel.itemViewHolder}" />

    </com.scwang.smart.refresh.layout.SmartRefreshLayout>

    <!-- 底部栏: 全选 + 统计 + 操作按钮 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:background="@color/color_bottom_layout"
        android:gravity="center_vertical">

        <!-- 全选按钮 (图标+文字) -->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center_vertical"
            android:onClick="@{()-> viewModel.checkAllClick()}">

            <ImageView
                android:id="@+id/checkIcon"
                android:layout_width="24dp"
                android:layout_height="24dp"
                android:src="@drawable/img_check_all" />

            <TextView
                android:text="全选"
                android:textColor="@color/white"
                android:textSize="18sp" />

        </LinearLayout>

        <!-- 统计信息 -->
        <LinearLayout
            android:layout_width="0dp"
            android:layout_weight="1"
            android:orientation="horizontal">

            <TextView
                android:text='@{"合计："+viewModel.totalCount+"票"}'
                android:textColor="@color/white" />

            <TextView
                android:text='@{"总件数："+viewModel.totalPc}'
                android:textColor="@color/white" />

            <TextView
                android:text='@{"总重量："+viewModel.totalWeight}'
                android:textColor="@color/white" />

        </LinearLayout>

        <!-- 批量操作按钮 -->
        <TextView
            style="@style/tv_bottom_btn"
            android:onClick="@{()-> viewModel.completeHandover()}"
            android:text="交接完成" />

    </LinearLayout>

</LinearLayout>
```

---

#### 步骤6: 创建列表项布局

**关键点**: 使用`completeSpace`属性实现Key左对齐

```xml
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/bg_white_radius_8"
    android:orientation="horizontal">

    <!-- 选中图标 (根据checked状态切换图片) -->
    <ImageView
        android:id="@+id/iv_icon"
        android:layout_width="40dp"
        android:layout_height="40dp"
        loadImage="@{bean.checked.get() ? @drawable/img_plane_s : @drawable/img_plane}"
        android:src="@drawable/img_plane" />

    <!-- 数据展示区域 -->
    <LinearLayout
        android:layout_width="0dp"
        android:layout_weight="1"
        android:orientation="vertical">

        <!-- 第一行数据 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <!-- ULD编号 (weight=1.5) -->
            <LinearLayout
                android:layout_width="0dp"
                android:layout_weight="1.5"
                android:orientation="horizontal">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    completeSpace="@{5}"
                    android:text="ULD编号："
                    android:textColor="@color/text_normal" />

                <TextView
                    android:text="@{bean.uld}"
                    android:textColor="@color/colorPrimary"
                    android:textStyle="bold" />

            </LinearLayout>

            <!-- 架子车号 (weight=1) -->
            <LinearLayout
                android:layout_width="0dp"
                android:layout_weight="1"
                android:orientation="horizontal">

                <TextView
                    completeSpace="@{5}"
                    android:text="架子车号："
                    android:textColor="@color/text_normal" />

                <TextView
                    android:text="@{String.valueOf(bean.carId)}"
                    android:textColor="@color/text_normal" />

            </LinearLayout>

            <!-- 总重 (weight=1) -->
            <LinearLayout
                android:layout_width="0dp"
                android:layout_weight="1"
                android:orientation="horizontal">

                <TextView
                    completeSpace="@{3}"
                    android:text="总重："
                    android:textColor="@color/text_normal" />

                <TextView
                    android:text="@{String.valueOf((int)bean.totalWeight)}"
                    android:textColor="@color/text_normal" />

            </LinearLayout>

            <!-- 装机重量 (weight=1) -->
            <!-- 货重 (weight=1.5) -->
            <!-- ... 其他字段 ... -->

        </LinearLayout>

        <!-- 第二行数据 -->
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:orientation="horizontal">

            <!-- 航班日期 (weight=1.5) -->
            <!-- 航班号 (weight=1) -->
            <!-- 目的港 (weight=1) -->
            <!-- 交接人 (weight=1) -->
            <!-- 交接时间 (weight=1.5) -->
            <!-- ... -->

        </LinearLayout>

    </LinearLayout>

</LinearLayout>
```

**权重分配原则**:
- 较长字段(如"ULD编号"、"交接时间")使用较大权重(1.5)
- 较短字段(如"总重"、"航班号")使用较小权重(1.0)
- `completeSpace`根据文字字数设置(3-5个字符宽度)

---

#### 步骤7: 创建Activity

```kotlin
@Route(path = ARouterConstants.ACTIVITY_URL_INT_EXP_OUT_HANDOVER)
class IntExpOutHandoverActivity :
    BaseBindingActivity<ActivityIntExpOutHandoverBinding, IntExpOutHandoverViewModel>() {

    override fun layoutId() = R.layout.activity_int_exp_out_handover
    override fun viewModelClass() = IntExpOutHandoverViewModel::class.java

    override fun initOnCreate(savedInstanceState: Bundle?) {
        setBackArrow("出库交接")
        binding.viewModel = viewModel

        // 观察全选状态，更新图标透明度
        viewModel.isAllChecked.observe(this) { isAllChecked ->
            binding.checkIcon.alpha = if (isAllChecked) 1.0f else 0.5f
        }

        // 绑定分页
        viewModel.pageModel.bindSmartRefreshLayout(binding.srl, binding.rv, viewModel, this)

        // 设置item点击监听
        binding.rv.addOnItemClickListener(viewModel)

        // 监听刷新事件
        FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) {
            viewModel.refresh()
        }

        // 初始加载数据
        viewModel.refresh()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == Constant.RequestCode.ULD && resultCode == Activity.RESULT_OK) {
            viewModel.uldNo.value = data?.getStringExtra(Constant.Result.CODED_CONTENT)
            viewModel.searchClick()
        }
    }
}
```

---

#### 步骤8: 注册Activity

```xml
<!-- app/src/main/AndroidManifest.xml -->
<activity
    android:name="com.lukouguoji.gjc.activity.IntExpOutHandoverActivity"
    android:configChanges="orientation|keyboardHidden"
    android:exported="false"
    android:screenOrientation="userLandscape" />
```

---

#### 关键技术点总结

##### 1. ObservableBoolean vs Boolean

| 特性 | ObservableBoolean | Boolean |
|------|------------------|---------|
| DataBinding支持 | ✅ 自动观察 | ❌ 不支持 |
| UI实时更新 | ✅ 调用set()自动刷新 | ❌ 需手动notifyDataSetChanged() |
| 代码简洁性 | ✅ 更简洁 | ❌ 需额外代码 |

##### 2. 全选交互逻辑

```
用户点击全选按钮
    ↓
checkAllClick() 被调用
    ↓
遍历列表，调用 bean.checked.set(shouldCheckAll)
    ↓
ObservableBoolean触发DataBinding更新
    ↓
列表项图片自动切换 (img_plane ↔ img_plane_s)
```

##### 3. 图片资源切换

```xml
<!-- loadImage 是自定义BindingAdapter -->
loadImage="@{bean.checked.get() ? @drawable/img_plane_s : @drawable/img_plane}"
```

**图片资源**:
- `img_plane.png` - 未选中状态
- `img_plane_s.png` - 已选中状态 (通常是高亮/彩色版本)
- `img_check_all.png` - 全选图标

##### 4. completeSpace属性原理

`completeSpace`是自定义BindingAdapter，用于实现Key左对齐:

```kotlin
// TextViewAdapter.kt
@BindingAdapter("completeSpace")
fun completeSpace(tv: TextView, count: Int) {
    // 1. 根据count个"一"字宽度设置TextView宽度
    val s = StringBuilder()
    (1..count).forEach { _ -> s.append("一") }
    val measureText = tv.paint.measureText(s.toString())
    ViewUtils.setWidth(tv, measureText.roundToInt())

    // 2. 自动填充全角空格使文本均匀分布
    // 确保"航班日期："与"航班号："的冒号位置对齐
}
```

**使用示例**:
- `completeSpace="@{5}"` - 5个"一"字宽度 (适合"ULD编号："、"航班日期：")
- `completeSpace="@{4}"` - 4个"一"字宽度 (适合"航班号："、"交接人：")
- `completeSpace="@{3}"` - 3个"一"字宽度 (适合"总重："、"货重：")

##### 5. 分页处理

BasePageViewModel自动处理分页逻辑:
- `pageModel.page` - 当前页码
- `pageModel.limit` - 每页条数
- `pageModel.handleListBean(it)` - 自动处理列表数据和分页状态

##### 6. 批量操作最佳实践

```kotlin
fun completeHandover() {
    // 1. 获取列表
    val list = pageModel.rv?.commonAdapter()?.items as? List<GjcUldUseBean> ?: return

    // 2. 过滤选中项
    val selectedItems = list.filter { it.isSelected }

    // 3. 验证
    if (selectedItems.isEmpty()) {
        showToast("请选择要交接的ULD")
        return
    }

    // 4. 转换为RequestBody
    val requestData = selectedItems.toRequestBody()

    // 5. 发起请求
    launchLoadingCollect({ NetApply.api.completeHandover(requestData) }) {
        onSuccess = {
            showToast("交接完成")
            // 6. 发送刷新事件
            viewModelScope.launch {
                FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
            }
            // 7. 刷新当前页
            refresh()
        }
    }
}
```

---

#### 常见错误与解决方案

##### 错误1: 点击图标不切换

```kotlin
// ❌ 错误: 使用普通Boolean
var isSelected: Boolean = false

// ✅ 正确: 使用ObservableBoolean
val checked: ObservableBoolean = ObservableBoolean(false)
```

##### 错误2: 全选不生效

```kotlin
// ❌ 错误: 直接修改isSelected
list.forEach { it.isSelected = checked }

// ✅ 正确: 调用ObservableBoolean的set方法
list.forEach { it.checked.set(checked) }
```

##### 错误3: 布局不对齐

```xml
<!-- ❌ 错误: 直接拼接key和value -->
<TextView android:text='@{"航班日期 " + bean.fdate}' />

<!-- ✅ 正确: 使用completeSpace属性 -->
<LinearLayout>
    <TextView completeSpace="@{5}" android:text="航班日期：" />
    <TextView android:text="@{bean.fdate}" />
</LinearLayout>
```

##### 错误4: 忘记观察全选状态

```kotlin
// ❌ 错误: 没有观察isAllChecked

// ✅ 正确: 在Activity中观察全选状态
viewModel.isAllChecked.observe(this) { isAllChecked ->
    binding.checkIcon.alpha = if (isAllChecked) 1.0f else 0.5f
}
```

---

#### 参考示例

**完整实现参考**:
- Activity: `module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpOutHandoverActivity.kt`
- ViewModel: `module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpOutHandoverViewModel.kt`
- ViewHolder: `module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpOutHandoverViewHolder.kt`
- Activity布局: `module_gjc/src/main/res/layout/activity_int_exp_out_handover.xml`
- Item布局: `module_gjc/src/main/res/layout/item_int_exp_out_handover.xml`
- Bean: `module_base/src/main/java/com/lukouguoji/module_base/bean/GjcUldUseBean.kt`

**其他类似实现**:
- `GjcAssembleAllocateActivity` - 国际出港组装分配

---

### 常见业务场景

#### 扫码

```kotlin
fun scanWaybill() {
    ScanModel.startScan(getTopActivity(), Constant.RequestCode.WAYBILL)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == Constant.RequestCode.WAYBILL && resultCode == Activity.RESULT_OK) {
        waybillNo.value = data?.getStringExtra(Constant.Result.CODED_CONTENT)
        search()
    }
}
```

#### 图片上传

```kotlin
val result = UploadUtil.upload(filePath)
if (result.verifySuccess()) {
    val imageUrl = result.data?.newName ?: ""  // 注意是newName不是url
}
```

#### 列表刷新事件

```kotlin
// 发送事件(在ViewModel中)
viewModelScope.launch {
    FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}

// 接收事件(在Activity中)
import com.lukouguoji.module_base.impl.observe  // 必须导入
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) {
    viewModel.refresh()
}
```

### 常用扩展函数

```kotlin
// Toast
showToast("提示信息")

// 验证非空
if (text.verifyNullOrEmpty("请输入内容")) return

// 空处理
val text = nullableString.noNull("默认值")

// 日期格式化
val dateStr = Date().formatDate()  // "2025-11-12"

// 权限申请
permission(Manifest.permission.CAMERA) { openCamera() }
```

### 常见编译错误及解决方案

#### 1. DetailsPageType 包名错误

```xml
<!-- ❌ 错误 -->
<import type="com.lukouguoji.module_base.constant.DetailsPageType" />

<!-- ✅ 正确 -->
<import type="com.lukouguoji.module_base.common.DetailsPageType" />
```

#### 2. DataLayoutType 枚举值错误

```xml
<!-- ❌ 错误: INTEGER不存在 -->
type="@{DataLayoutType.INTEGER}"

<!-- ✅ 正确: 使用INPUT -->
type="@{DataLayoutType.INPUT}"
```

**可用类型**: `INPUT` / `SPINNER` / `DATE`

#### 3. DetailsPageType 枚举值错误

```kotlin
// ❌ 错误: Edit不存在
DetailsPageType.Edit

// ✅ 正确: 使用Modify
DetailsPageType.Modify
```

**可用类型**: `Add` / `Modify` / `Details`

#### 4. IOnItemClickListener 包名错误

```kotlin
// ❌ 错误
import com.lukouguoji.module_base.impl.IOnItemClickListener

// ✅ 正确
import com.lukouguoji.module_base.interfaces.IOnItemClickListener
```

#### 5. FlowBus 使用错误

```kotlin
// ❌ 错误: observe需要单独导入
import com.lukouguoji.module_base.impl.FlowBus

// ✅ 正确
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.impl.observe

// ❌ 错误: emit必须在协程中
FlowBus.with<String>(event).emit("data")

// ✅ 正确
viewModelScope.launch {
    FlowBus.with<String>(event).emit("data")
}
```

#### 6. 图片上传字段错误

```kotlin
// ❌ 错误: UploadBean没有url字段
val imageUrl = result.data?.url

// ✅ 正确: 使用newName字段
val imageUrl = result.data?.newName
```

#### 7. pageType 必须用 LiveData

```kotlin
// ❌ 错误: DataBinding无法绑定
var pageType: DetailsPageType = DetailsPageType.Add

// ✅ 正确: 使用LiveData
val pageType = MutableLiveData(DetailsPageType.Add)
```

#### 8. RecyclerView 不支持 items 属性

```xml
<!-- ❌ 错误: items属性会导致编译错误 -->
<RecyclerView
    items="@{viewModel.list}" />

<!-- ✅ 正确: 在Activity中手动更新 -->
<RecyclerView android:id="@+id/recyclerView" />
```

```kotlin
// Activity中
viewModel.list.observe(this) { data ->
    binding.recyclerView.commonAdapter()?.refresh(data)
}
```

#### 9. 资源引用错误 (最常见的编译失败原因)

```xml
<!-- ❌ 错误: 引用不存在的资源会导致资源合并失败 -->
<TextView
    android:background="@drawable/bg_custom"
    android:textColor="@color/custom_color"
    android:text="@string/custom_text" />
```

**问题原因**:
- 在布局文件中引用了项目中不存在的 `drawable`、`color`、`string` 等资源
- 导致构建时资源合并失败,无法生成R文件
- 报错信息: `Resource compilation failed` 或 `AAPT: error: resource ... not found`

**正确做法**:

1. **使用已存在的资源** - 先检查资源是否存在

```bash
# 查找drawable资源
find module_base/src/main/res/drawable -name "bg_custom*"

# 查找color定义
grep "custom_color" module_base/src/main/res/values/colors.xml

# 查找string定义
grep "custom_text" module_base/src/main/res/values/strings.xml
```

2. **主动创建缺失的资源** - 如果不存在则创建

```xml
<!-- 创建 drawable: module_base/src/main/res/drawable/bg_custom.xml -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/white"/>
    <corners android:radius="4dp"/>
</shape>

<!-- 添加 color: module_base/src/main/res/values/colors.xml -->
<color name="custom_color">#333333</color>

<!-- 添加 string: module_base/src/main/res/values/strings.xml -->
<string name="custom_text">自定义文本</string>
```

3. **使用项目现有资源** - 避免重复创建

常用资源列表:
- **背景**: `bg_white_radius_8`, `bg_gray_radius_4`, `bg_primary_radius_4`
- **颜色**: `white`, `black`, `colorPrimary`, `text_normal`, `text_gray`, `text_red`
- **文字**: 优先直接写中文字符串,少用 string 资源

#### 10. DataBinding中View类未导入

```xml
<!-- ❌ 错误: 使用View.VISIBLE但未导入View -->
<data>
    <variable name="viewModel" type="..." />
</data>

<View android:visibility="@{viewModel.isVisible ? View.VISIBLE : View.GONE}" />

<!-- ✅ 正确: 必须导入View类 -->
<data>
    <import type="android.view.View" />
    <variable name="viewModel" type="..." />
</data>

<View android:visibility="@{viewModel.isVisible ? View.VISIBLE : View.GONE}" />
```

#### 11. DataBinding中textStyle属性错误

```xml
<!-- ❌ 错误: textStyle不支持DataBinding字符串 -->
<TextView
    android:textStyle="@{viewModel.isBold ? `bold` : `normal`}" />

<!-- ✅ 正确: 直接使用固定值或删除该属性 -->
<TextView
    android:textStyle="bold" />
```

**原因**: `textStyle`属性只接受整数值(如`Typeface.BOLD`),不接受字符串

### 常用Import路径速查表

#### ⚠️ 重要提醒

在创建新的Activity、ViewModel、Fragment时,以下import路径**最容易出错**。务必使用正确的包名:

#### 基础类Import (module_base)

```kotlin
// ==================== 基类 ====================
import com.lukouguoji.module_base.base.BaseActivity              // Activity基类
import com.lukouguoji.module_base.base.BaseBindingActivity      // DataBinding Activity基类
import com.lukouguoji.module_base.base.BaseViewModel            // ViewModel基类 ⚠️ 不是service.viewModel!
import com.lukouguoji.module_base.base.BasePageViewModel        // 分页列表ViewModel基类
import com.lukouguoji.module_base.base.BaseFragment             // Fragment基类
import com.lukouguoji.module_base.base.BaseBindingFragment      // DataBinding Fragment基类
import com.lukouguoji.module_base.base.BaseViewHolder           // ViewHolder基类
import com.lukouguoji.module_base.base.CustomVP2Adapter         // ViewPager2适配器

// ==================== 常量类 ====================
import com.lukouguoji.module_base.common.Constant               // 常量类 ⚠️ 不是根包下的Constant!
import com.lukouguoji.module_base.common.DetailsPageType        // 详情页类型(Add/Modify/Details)
import com.lukouguoji.module_base.common.ConstantEvent          // 事件常量

// ==================== 网络相关 ====================
import com.lukouguoji.module_base.http.net.NetApply             // API调用入口
import com.lukouguoji.module_base.http.net.Api                  // API接口定义
import com.lukouguoji.module_base.bean.BaseResultBean           // 通用返回结果
import com.lukouguoji.module_base.bean.BaseListBean             // 列表返回结果
import com.lukouguoji.module_base.bean.PageInfo                 // 分页信息

// ==================== Kotlin扩展函数 ====================
import com.lukouguoji.module_base.ktx.launchCollect             // 协程扩展(无Loading)
import com.lukouguoji.module_base.ktx.launchLoadingCollect      // 协程扩展(带Loading)
import com.lukouguoji.module_base.ktx.showToast                 // Toast扩展
import com.lukouguoji.module_base.ktx.toRequestBody             // Map转RequestBody ⚠️ 不是ext包!
import com.lukouguoji.module_base.ktx.verifyNullOrEmpty         // 非空验证
import com.lukouguoji.module_base.ktx.noNull                    // 空值处理
import com.lukouguoji.module_base.ktx.formatDate                // 日期格式化

// ==================== 事件总线 ====================
import com.lukouguoji.module_base.impl.FlowBus                  // FlowBus事件总线
import com.lukouguoji.module_base.impl.observe                  // FlowBus观察扩展 ⚠️ 必须单独导入!

// ==================== 接口 ====================
import com.lukouguoji.module_base.interfaces.IOnItemClickListener  // 列表项点击接口 ⚠️ 不是impl包!

// ==================== 路由 ====================
import com.lukouguoji.module_base.router.ARouterConstants       // 路由常量
import com.alibaba.android.arouter.facade.annotation.Route      // ARouter注解

// ==================== UI组件 ====================
import com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew      // 数据展示组件
import com.lukouguoji.module_base.ui.weight.data.layout.DataLayoutType        // 数据组件类型
import com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout     // 搜索组件
import com.lukouguoji.module_base.ui.weight.search.layout.SearchLayoutType    // 搜索组件类型
```

#### Android标准库Import

```kotlin
// ==================== Activity & Fragment ====================
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment

// ==================== Lifecycle & ViewModel ====================
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope

// ==================== DataBinding ====================
import androidx.databinding.DataBindingUtil

// ==================== ViewPager2 ====================
import androidx.viewpager2.widget.ViewPager2

// ==================== Coroutines ====================
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

// ==================== View ====================
import android.view.View
import android.view.LayoutInflater
import android.view.ViewGroup
```

#### 业务模块Bean Import示例

```kotlin
// 国际出港模块
import com.lukouguoji.module_base.bean.GjcMaWb           // 国际出港主单
import com.lukouguoji.module_base.bean.GjcHaWb           // 国际出港分单
import com.lukouguoji.module_base.bean.GjcStorageUse     // 库位使用

// 国内出港模块
import com.lukouguoji.module_base.bean.GncMaWb           // 国内出港主单
```

#### 常见错误对照表

| ❌ 错误写法 | ✅ 正确写法 | 说明 |
|------------|------------|------|
| `com.lukouguoji.module_base.Constant` | `com.lukouguoji.module_base.common.Constant` | Constant在common包下 |
| `com.lukouguoji.module_base.service.viewModel.BaseViewModel` | `com.lukouguoji.module_base.base.BaseViewModel` | BaseViewModel在base包下 |
| `com.lukouguoji.module_base.ext.toRequestBody` | `com.lukouguoji.module_base.ktx.toRequestBody` | 扩展函数在ktx包下 |
| `com.lukouguoji.module_base.impl.IOnItemClickListener` | `com.lukouguoji.module_base.interfaces.IOnItemClickListener` | 接口在interfaces包下 |
| `com.lukouguoji.module_base.constant.DetailsPageType` | `com.lukouguoji.module_base.common.DetailsPageType` | 枚举在common包下 |

#### 快速查找正确Import路径

```bash
# 查找类的完整路径
find module_base/src/main/java -name "Constant.kt"
find module_base/src/main/java -name "BaseViewModel.kt"

# 查找函数定义位置
grep -r "fun.*toRequestBody" module_base/src/main/java --include="*.kt"
grep -r "class BaseViewModel" module_base/src/main/java --include="*.kt"

# 查找接口定义
find module_base/src/main/java -name "IOnItemClickListener.kt"
```

#### Activity/ViewModel/Fragment模板

**ViewModel模板 (带正确import):**

```kotlin
package com.lukouguoji.xxx.viewModel

import android.content.Intent
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.module_base.base.BaseViewModel                    // ⚠️ 正确路径
import com.lukouguoji.module_base.common.Constant                       // ⚠️ 正确路径
import com.lukouguoji.module_base.http.net.NetApply
import com.lukouguoji.module_base.ktx.launchLoadingCollect
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.ktx.toRequestBody                     // ⚠️ 正确路径

class XxxViewModel : BaseViewModel() {
    val data = MutableLiveData<Any>()

    fun loadData() {
        val params = mapOf("key" to "value").toRequestBody()
        launchLoadingCollect({ NetApply.api.getXxx(params) }) {
            onSuccess = { data.value = it.data }
        }
    }
}
```

**Activity模板 (带正确import):**

```kotlin
package com.lukouguoji.xxx.activity

import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.xxx.R
import com.lukouguoji.xxx.databinding.ActivityXxxBinding
import com.lukouguoji.xxx.viewModel.XxxViewModel
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.common.Constant                       // ⚠️ 正确路径
import com.lukouguoji.module_base.router.ARouterConstants

@Route(path = ARouterConstants.ACTIVITY_URL_XXX)
class XxxActivity : BaseBindingActivity<ActivityXxxBinding, XxxViewModel>() {
    override fun layoutId() = R.layout.activity_xxx
    override fun viewModelClass() = XxxViewModel::class.java

    override fun initOnCreate(savedInstanceState: Bundle?) {
        setBackArrow("标题")
        binding.viewModel = viewModel
    }
}
```

**Fragment模板 (带正确import):**

```kotlin
package com.lukouguoji.xxx.fragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import com.lukouguoji.xxx.R
import com.lukouguoji.xxx.databinding.FragmentXxxBinding
import com.lukouguoji.xxx.viewModel.XxxViewModel

class XxxFragment : Fragment() {
    private lateinit var binding: FragmentXxxBinding
    private lateinit var viewModel: XxxViewModel

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = DataBindingUtil.inflate(
            inflater,
            R.layout.fragment_xxx,
            container,
            false
        )
        binding.lifecycleOwner = viewLifecycleOwner
        binding.viewModel = viewModel
        return binding.root
    }

    companion object {
        @JvmStatic
        fun newInstance(vm: XxxViewModel) =
            XxxFragment().apply { viewModel = vm }
    }
}
```

### 错误排查流程

1. **Import错误 (Unresolved reference)** → 参考上方"常用Import路径速查表",使用正确包名
2. **资源引用错误** → 检查drawable/color/string是否存在,主动创建缺失资源
3. **DataBinding错误** → 检查import包名、枚举值、是否导入View类
4. **suspend function错误** → 在`viewModelScope.launch`中调用
5. **仍有问题** → `./gradlew clean` 后重新构建

### 快速修复命令

```bash
# 查找DetailsPageType位置
grep -r "enum class DetailsPageType" module_base/src --include="*.kt"

# 查找IOnItemClickListener位置
find module_base/src -name "IOnItemClickListener.kt"

# 查找DataLayoutType枚举值
grep -A 5 "enum class DataLayoutType" module_base/src --include="*.kt"
```

### 标题栏统一规范

**重要规则**: 所有 Activity 布局必须使用统一的 title_tool_bar 组件,禁止手动编写 Toolbar。

#### 正确做法

**布局文件** (`activity_xxx.xml`):
```xml
<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- 标题栏 -->
    <include layout="@layout/title_tool_bar" />

    <!-- 其他内容 -->
    ...
</LinearLayout>
```

**Activity 文件** (`XxxActivity.kt`):
```kotlin
override fun initOnCreate(savedInstanceState: Bundle?) {
    setBackArrow("页面标题")  // 自动设置标题和返回事件
    binding.viewModel = viewModel
    // 其他初始化...
}
```

#### 错误做法

❌ **不要手动编写 Toolbar**:
```xml
<!-- 错误:手动配置 Toolbar -->
<androidx.appcompat.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="@color/colorPrimary">

    <LinearLayout android:id="@+id/tool_back">...</LinearLayout>
    <TextView android:id="@+id/title_name">...</TextView>
</androidx.appcompat.widget.Toolbar>
```

❌ **不要手动查找 tool_back 并设置点击事件**:
```kotlin
// 错误:手动处理返回按钮
binding.root.findViewById<LinearLayout>(R.id.tool_back)?.setOnClickListener {
    finish()
}
```

#### title_tool_bar 工作原理

`title_tool_bar.xml` 包含三个关键 ID:
- `toolbar` - BaseBindingActivity 自动查找
- `tool_back` - 自动绑定 finish() 点击事件
- `title_name` - 通过 `setBackArrow()` 设置文字

**优点**:
- 统一视觉风格
- 减少重复代码
- 自动处理返回逻辑
- 维护简单 (修改一处,全局生效)

#### 参考示例

- `module_gjc/src/main/res/layout/activity_gjc_inspection.xml` - 第21行
- `module_gjc/src/main/res/layout/activity_gjc_box_weighing_details.xml` - 第21行
- `module_gjc/src/main/res/layout/activity_gjc_query_details.xml` - 第19行

### 布局最佳实践参考

参考以下文件进行布局设计:
- `module_gjc/src/main/res/layout/activity_gjc_weighing_record_details.xml`
- `module_gjc/src/main/res/layout/item_gjc_check_in_record.xml`
- `module_gjc/src/main/res/layout/activity_gjc_box_weighing_details.xml`
- `module_gjc/src/main/res/layout/activity_gjc_inspection.xml`

### 开发原则

- ✅ **资源引用必须存在** - 创建/修改布局前,确保drawable/color/string资源真实存在或主动创建
- ✅ **标题栏统一使用 title_tool_bar** - 禁止手动编写 Toolbar,必须使用 `<include layout="@layout/title_tool_bar" />`,Activity 中调用 `setBackArrow("标题")`
- ✅ **必须设置 lifecycleOwner** - Activity 中 `binding.lifecycleOwner = this`(BaseBindingActivity 已自动设置)
- ✅ **新建Activity后必须在AndroidManifest.xml中注册**
- ✅ 优先使用项目现有基类和封装
- ✅ 充分利用PadDataLayout和PadSearchLayout组件
- ✅ 遵循统一命名规范
- ✅ pageType用LiveData不用普通变量
- ✅ XML中字符串拼接使用反引号,不访问LiveData的.value属性
- ✅ 修改对象属性后重新赋值LiveData才能触发UI更新
- ✅ FlowBus.emit()必须在协程中调用
- ✅ 图片上传使用newName字段
- ✅ RecyclerView手动更新adapter不用items属性
- ✅ 在每个页面布局时,如有截图,务必尽可能还原图片上的页面设计,而不是推测假想。如有困难(图片看不清、不明白的地方)一律要询问,禁止自己想象。

---

## Universal Development Guidelines

### Code Quality Standards
- Write clean, readable, and maintainable code
- Follow consistent naming conventions across the project
- Use meaningful variable and function names
- Keep functions focused and single-purpose
- Add comments for complex logic and business rules

### Git Workflow
- Use descriptive commit messages following conventional commits format
- Create feature branches for new development
- Keep commits atomic and focused on single changes
- Use pull requests for code review before merging
- Maintain a clean commit history

### Documentation
- Keep README.md files up to date
- Document public APIs and interfaces
- Include usage examples for complex features
- Maintain inline code documentation
- Update documentation when making changes

### Testing Approach
- Write tests for new features and bug fixes
- Maintain good test coverage
- Use descriptive test names that explain the expected behavior
- Organize tests logically by feature or module
- Run tests before committing changes

### Security Best Practices
- Never commit sensitive information (API keys, passwords, tokens)
- Use environment variables for configuration
- Validate input data and sanitize outputs
- Follow principle of least privilege
- Keep dependencies updated

## Project Structure Guidelines

### File Organization
- Group related files in logical directories
- Use consistent file and folder naming conventions
- Separate source code from configuration files
- Keep build artifacts out of version control
- Organize assets and resources appropriately

### Configuration Management
- Use configuration files for environment-specific settings
- Centralize configuration in dedicated files
- Use environment variables for sensitive or environment-specific data
- Document configuration options and their purposes
- Provide example configuration files

## Development Workflow

### Before Starting Work
1. Pull latest changes from main branch
2. Create a new feature branch
3. Review existing code and architecture
4. Plan the implementation approach

### During Development
1. Make incremental commits with clear messages
2. Run tests frequently to catch issues early
3. Follow established coding standards
4. Update documentation as needed

### Before Submitting
1. Run full test suite
2. Check code quality and formatting
3. Update documentation if necessary
4. Create clear pull request description

## Common Patterns

### Error Handling
- Use appropriate error handling mechanisms for the language
- Provide meaningful error messages
- Log errors appropriately for debugging
- Handle edge cases gracefully
- Don't expose sensitive information in error messages

### Performance Considerations
- Profile code for performance bottlenecks
- Optimize database queries and API calls
- Use caching where appropriate
- Consider memory usage and resource management
- Monitor and measure performance metrics

### Code Reusability
- Extract common functionality into reusable modules
- Use dependency injection for better testability
- Create utility functions for repeated operations
- Design interfaces for extensibility
- Follow DRY (Don't Repeat Yourself) principle

## Review Checklist

Before marking any task as complete:
- [ ] Code follows established conventions
- [ ] Tests are written and passing
- [ ] Documentation is updated
- [ ] Security considerations are addressed
- [ ] Performance impact is considered
- [ ] Code is reviewed for maintainability