Files
aerologic-app/.claude/skills/new-page/SKILL.md
2026-04-12 20:23:26 +08:00

466 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: new-page
description: 根据设计截图和菜单入口名称,在 AirLogistics 项目中创建新的业务页面。自动分析截图判断页面类型6种典型类型之一规划所需文件然后完整实现Kotlin + XML + 路由注册 + 菜单接入)。当用户说"创建页面"、"新建页面"、"实现这个页面"、"做这个页面"或提供设计截图要求开发时触发。
---
# 新建业务页面
根据设计截图和菜单入口名称,在 AirLogistics 项目中完整创建一个新的业务页面。
## 前置要求
用户需要提供:
1. **设计截图**(必须)— 页面 UI 设计图
2. **菜单入口名称**(必须)— 页面在首页菜单中显示的名称
3. **所属模块**(可选)— 如 module_gjj国际进港、module_gjc国际出港可从截图标题推断
## 执行步骤
### 第 1 步:分析截图,判定页面类型
仔细阅读截图,提取以下信息:
1. **页面标题**(标题栏文字)
2. **搜索区字段**(每个搜索控件的 hint 文本、类型:日期/输入/下拉/扫码)
3. **列表项字段**(每个数据字段的标签名和显示格式)
4. **底部操作栏**(统计项 + 操作按钮)
5. **特殊交互**(全选、展开/收起、右箭头、子列表等)
然后对照 CLAUDE.md 中的 **6 种典型页面类型**判定:
| 类型 | 关键特征 | 判定依据 |
|------|----------|----------|
| **类型 1列表查询页** | 搜索 + 分页列表 + 底部统计 | 无全选、无勾选,纯查看 |
| **类型 2多选列表 + 批量操作页** | 类型 1 + 全选按钮 + 飞机图标选中态 + 操作按钮 | 有全选、有批量操作按钮(如"清除提货" |
| **类型 3嵌套多选列表页** | 类型 2 + 子列表(展开/收起)+ 主子联动全选 | 有展开按钮、列表项内含子 RecyclerView |
| **类型 4Tab 详情页** | 自定义 Tab 栏 + ViewPager2 + 多 Fragment | 有 Tab 切换、无列表搜索 |
| **类型 5编辑表单页** | ScrollView + PadDataLayoutNew 表单 + 保存/取消 | 有可编辑字段、有保存按钮 |
| **类型 6添加表单页** | 类型 5 + 输入回调 + 实时计算 | 有 setRefreshCallBack 联动 |
**向用户确认**判定结果,格式:
```
📋 页面分析结果:
页面标题XXX
页面类型:类型 N — XXXX
所属模块module_xxx
搜索条件:
1. XXXDATE
2. XXXSPINNER
3. XXXINPUT + 扫码)
列表字段第一行XXX | XXX | XXX
列表字段第二行XXX | XXX | XXX
底部操作:全选 + 统计(合计/总件数/总重量) + [操作按钮名称]
是否正确?确认后开始实现。
```
### 第 2 步:查找参考模板并提取 UI 设计规范
根据判定的页面类型,在项目中找到**同类型的最新参考实现**,并严格提取 UI 设计规范。
**查找策略**(优先级从高到低):
1. 优先查找**最近提交的同类型页面**(最新的页面代表最新的 UI 规范)
2. 其次在**同模块**内查找同类型页面
3. 最后在 **module_gjc**(国际出港)查找
**最新典型参考页面**2024年后新增代表当前 UI 规范):
| 页面类型 | 参考页面 | 布局文件 |
|----------|----------|----------|
| 列表查询页 | 航班查询列表 | `activity_flight_query_list.xml` / `item_flight_query_list.xml` |
| 列表查询页 | 日志查询页 | `activity_log_query.xml` / `item_log_query.xml` |
| 详情页 | 航班查询详情 | `activity_flight_query_details.xml` |
| 详情页 | 日志详情 | `activity_log_detail.xml` |
**必须读取的参考文件**
- 上述最新典型参考页面中**与当前页面类型匹配的布局 XML**Activity 布局 + Item 布局)
- 对应的 Activity、ViewModel、ViewHolder Kotlin 代码
- Dialog如有批量操作弹窗
**UI 设计规范提取(必做)**
读取参考布局后,必须逐一确认以下规范项,并在开发计划中明确列出:
| 规范项 | 必须确认的内容 |
|--------|---------------|
| 页面背景色 | 根容器 `background` 属性(新规范:`@color/color_f2` |
| 搜索区样式 | 使用的搜索控件类型、间距、布局方式 |
| 搜索按钮样式 | 图标资源、尺寸、style新规范`@drawable/img_search`36dp |
| 底部栏背景色 | 背景色 + 文字颜色(新规范有两种变体) |
| 底部栏文字样式 | 字号、粗细、颜色 |
| 列表项背景 | 背景、间距、padding |
| 详情页卡片样式 | 背景、圆角、padding、行间距 |
| 表单控件 | 使用 PadDataLayoutNew非旧版 PadDataLayout |
**同时检查**
- 该页面需要的**下拉列表数据源 API** 是否已存在(代理人、特码等)
- 该页面需要的 **Bean 类**是否已存在,或需要新建
- `ARouterConstants` 中是否已有对应路由常量
- `Constant.AuthName` 中是否已有对应权限名
### 第 3 步:制定文件清单
列出所有需要**新建**和**修改**的文件。
**新建文件清单**(根据页面类型调整):
| 类别 | 文件 | 路径 |
|------|------|------|
| Bean | `XxxBean.kt`(如需) | `module_base/.../bean/` |
| Activity | `XxxActivity.kt` | `module_xxx/.../activity/` |
| ViewModel | `XxxViewModel.kt` | `module_xxx/.../viewModel/` |
| ViewHolder | `XxxViewHolder.kt` | `module_xxx/.../holder/` |
| Dialog | `XxxDialogModel.kt`(如需) | `module_xxx/.../dialog/` |
| Activity 布局 | `activity_xxx.xml` | `module_xxx/.../res/layout/` |
| Item 布局 | `item_xxx.xml` | `module_xxx/.../res/layout/` |
| Dialog 布局 | `dialog_xxx.xml`(如需) | `module_xxx/.../res/layout/` |
**修改文件清单**(固定):
| 文件 | 修改内容 |
|------|----------|
| `Api.kt` | 添加 API 接口方法 + import |
| `ARouterConstants.kt` | 添加路由常量(如不存在) |
| `Constant.kt` | 添加 AuthName 常量(如不存在) |
| `AndroidManifest.xml` | 注册 Activity |
| `HomeFragment.kt` | 添加菜单项 + 点击路由处理 |
| 旧版 Activity如有 | 注释掉 `@Route` 注解避免冲突 |
### 第 4 步:创建 Bean如需
如果截图中的列表字段与现有 Bean 不匹配,创建新的 Bean 类。
**规则**
- 放在 `module_base/.../bean/` 目录下
- 如果是类型 2/3多选必须包含 `ObservableBoolean` 选中状态:
```kotlin
val checked: ObservableBoolean = ObservableBoolean(false)
var isSelected: Boolean
get() = checked.get()
set(value) = checked.set(value)
```
- 字段类型映射:数字用 `Int/Long/Double`,文本用 `String = ""`,时间用 `String = ""`
-`Api.kt` 的 import 区按字母顺序添加 import
### 第 5 步:添加 API 接口
`Api.kt` 中添加 API 方法。
**标准 API 组合**(根据页面需要选取):
- `getXxxList(@Body)` — 分页查询,返回 `PageInfo<XxxBean>`
- `getXxxTotal(@Body)` — 分页合计,返回 `BaseResultBean<ManifestTotalDto>`
- 批量操作 API — 返回 `BaseResultBean<Boolean>`
- 下拉列表 API代理人、特码等— 如不存在则添加
**API 路径命名**:先使用 `ModuleName/methodName` 格式占位(如 `IntImpPickUpRecord/pageQuery`),后续由第 13 步替换为真实接口路径
### 第 6 步:添加路由和权限常量
1.`ARouterConstants.kt` 添加路由(如不存在):
```kotlin
const val ACTIVITY_URL_XXX = "/module/XxxActivity"
```
2.`Constant.kt``AuthName` 中添加权限名(如不存在):
```kotlin
const val XxxPage = "AppXxxPage"
```
### 第 7 步:创建 ViewHolder
根据 item 布局创建 ViewHolder。
**类型 1**:基础绑定
**类型 2**:增加图标点击切换 `checked` 状态
**类型 3**:增加子列表 `setCommonAdapter` + 展开按钮 + 父子联动
### 第 8 步:创建 ViewModel
**必须包含的元素**(根据截图):
- 搜索条件 `MutableLiveData`(与搜索区对应)
- 下拉列表数据源 `MutableLiveData<List<KeyValue>>`
- 统计字段 `MutableLiveData<String>`
- 适配器配置:`itemViewHolder` + `itemLayoutId`
- `searchClick()` 方法
- `getData()` override调用列表 API + 统计 API
**类型 2 额外**`isAllChecked` + `checkAllClick()` + 批量操作方法
**类型 3 额外**`isAllExpanded` + `toggleAllExpand()` + 联动全选逻辑
**下拉列表初始化**
- 代理人:`DictUtils.getAgentList()` / `NetApply.api.getIntImpAgentList()` / `getIntExpAgentList()`
- 特码:`DictUtils.getSpecialCodeList(flag, ieFlag, parentcode)`
- 其他字典:`DictUtils` 或自定义 API
### 第 9 步:创建布局文件
**⚠️ 重要:所有布局必须严格遵循最新 UI 设计规范,参照第 2 步中提取的规范项。**
---
#### 新版 UI 设计规范(强制执行)
**1. 页面背景色**
```xml
<!-- 根容器必须使用 color_f2 背景色 -->
<LinearLayout
android:background="@color/color_f2"
android:orientation="vertical">
```
**2. 搜索区规范**
```xml
<!-- 搜索区marginHorizontal=10dp, marginTop=10dp, gravity=center_vertical -->
<LinearLayout
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
android:gravity="center_vertical">
<!-- 搜索控件使用 PadSearchLayout -->
<PadSearchLayout
type="@{SearchLayoutType.DATE}"
hint='@{"请选择航班日期"}'
icon="@{@drawable/img_date}"
value="@={viewModel.date}"
android:layout_width="0dp"
android:layout_weight="1" />
<!-- 搜索按钮img_search 图标36x36dppadding=2dp -->
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:gravity="center_vertical|start"
android:paddingHorizontal="24dp">
<ImageView
android:layout_width="36dp"
android:layout_height="36dp"
android:padding="2dp"
android:onClick="@{()-> viewModel.searchClick()}"
android:src="@drawable/img_search" />
</LinearLayout>
</LinearLayout>
```
**3. 底部统计栏规范**(两种变体,根据截图选择):
**变体 A — 深蓝色底部栏**(多数列表页使用):
```xml
<LinearLayout
android:layout_height="50dp"
android:background="@color/color_bottom_layout"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:text='@{"合计:"+viewModel.count+"条"}'
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
```
**变体 B — 白色底部栏**
```xml
<LinearLayout
android:layout_height="50dp"
android:background="@color/white"
android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<TextView
android:text='@{"合计:" + viewModel.count + "条"}'
android:textColor="@color/bottom_tool_tips_text_color"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
```
**4. 详情页/表单页规范**
- 必须使用 **PadDataLayoutNew**(非旧版 PadDataLayout
- 卡片背景:`@drawable/bg_white_radius_8`padding `15dp`
- 行间距:`marginTop="8dp"`
- 三列标准布局,每列 `layout_weight="1"`
```xml
<ScrollView android:fillViewport="true">
<LinearLayout android:padding="15dp" android:orientation="vertical">
<LinearLayout android:background="@drawable/bg_white_radius_8"
android:padding="15dp" android:orientation="vertical">
<LinearLayout android:orientation="horizontal">
<PadDataLayoutNew layout_weight="1" enable="@{false}"
title='@{"航班日期"}' type="@{DataLayoutType.INPUT}"
value='@{viewModel.dataBean.fdate}' />
<!-- 更多列... -->
</LinearLayout>
<LinearLayout android:orientation="horizontal" android:layout_marginTop="8dp">
<!-- 第二行... -->
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>
```
---
#### Activity 布局完整结构
```xml
<layout>
<data>
<import SearchLayoutType />
<variable viewModel />
<variable activity /> <!-- 类型 3 或有 Dialog 操作时 -->
</data>
<LinearLayout background="@color/color_f2" vertical> <!-- ← 新规范:背景色 -->
<include title_tool_bar />
<!-- 搜索区marginHorizontal=10dp, marginTop=10dp -->
<LinearLayout horizontal marginHorizontal="10dp" marginTop="10dp">
PadSearchLayout × N + 搜索按钮(img_search, 36dp) <!-- ← 新规范:搜索按钮 -->
</LinearLayout>
<!-- 列表 -->
<SmartRefreshLayout> <RecyclerView /> </SmartRefreshLayout>
<!-- 底部栏50dp根据截图选择深蓝/白色变体 --> <!-- ← 新规范:底部栏 -->
<LinearLayout height="50dp" background="..." paddingHorizontal="15dp">
<TextView textSize="18sp" textStyle="bold" />
</LinearLayout>
</LinearLayout>
</layout>
```
#### Item 布局(从截图精确还原每一行每一列)
- 逐行对照截图中的字段顺序和标签文本
- 使用 `completeSpace` 对齐 Key 文本
- 运单号等关键字段用 `@color/colorPrimary`
- 类型 2/3 左侧有飞机图标:`loadImage="@{bean.checked.get() ? @drawable/img_plane_s : @drawable/img_plane}"`
- 列表项背景统一使用 `@drawable/bg_item`
- 间距统一:`marginHorizontal="15dp"`, `marginVertical="5dp"`, `padding="10dp"`
**关键原则:务必尽可能还原截图上的页面设计,不推测不假想。**
### 第 10 步:创建 Activity
**固定结构**
```kotlin
@Route(path = ARouterConstants.ACTIVITY_URL_XXX)
class XxxActivity : BaseBindingActivity<XxxBinding, XxxViewModel>() {
override fun layoutId() = R.layout.activity_xxx
override fun viewModelClass() = XxxViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("页面标题") // 与截图标题一致
binding.viewModel = viewModel
binding.activity = this // 类型 3 或有 Dialog 时
// 类型 2/3观察全选状态
viewModel.isAllChecked.observe(this) { binding.checkIcon.alpha = if (it) 1.0f else 0.5f }
// 绑定分页
viewModel.pageModel.bindSmartRefreshLayout(binding.srl, binding.rv, viewModel, this)
// 监听刷新事件
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) { viewModel.refresh() }
// 初始化下拉列表(如有)
viewModel.initAgentList()
viewModel.initSpecialCodeList()
viewModel.refresh()
}
}
```
### 第 11 步:注册 Activity + 菜单入口
1. **AndroidManifest.xml**:在其他 gjj Activity 注册附近添加:
```xml
<activity android:name="com.lukouguoji.xxx.activity.XxxActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false" android:screenOrientation="userLandscape" />
```
2. **HomeFragment.kt**
- 在对应模块的菜单列表区添加 `RightMenu` 项(图标 + 标题)
- 在 onClick 处理区添加路由跳转
3. **旧版 Activity**(如有):注释掉 `@Route` 注解
### 第 12 步:编译验证
```bash
./gradlew assembleDebug
```
编译必须通过0 errors。如有错误立即修复后重新编译。
### 第 13 步:查找并对接 API 接口
页面创建并编译通过后,通过"空港集团 - API 文档"Apifox MCP 工具查找真实接口,替换第 5 步中的占位路径。
**A. 基础接口查找(每次必查):**
1. 根据页面所属业务路径(如"国际进港 → 原始舱单"),在 Apifox MCP 中按模块目录搜索
2. 查找以下基础接口:
- **列表接口**(分页查询,如 `pageQuery``list`
- **合计接口**(统计,如 `total``count``statistics`
- **修改/保存接口**(如 `update``save``edit`
- **删除接口**(如 `delete``remove`
**B. 智能接口匹配(根据页面分析):**
3. 根据**页面类型特性**查找对应接口:
- 类型 2多选批量操作→ 查找批量操作接口(如批量删除、批量确认等)
- 类型 3嵌套列表→ 查找子列表相关接口
- 类型 5/6表单页→ 查找详情查询接口、下拉选项字典接口
- 类型 4Tab 详情)→ 查找各 Tab 对应的数据接口
4. 根据**页面中的按钮和文案**,逐一匹配对应接口:
- 例如页面有"审核"按钮 → 查找审核接口
- 例如页面有"导出"按钮 → 查找导出接口
- 例如页面有"打印"按钮 → 查找打印相关接口
- 例如底部栏有"清除提货"按钮 → 查找清除提货接口
- 例如搜索区有下拉框(代理人、状态等)→ 查找对应的字典/下拉数据接口
5. 根据**截图中可见的交互元素**,推断可能需要的接口:
- 列表项有右箭头 → 可能需要详情接口
- 列表项有编辑图标 → 可能需要编辑/更新接口
- 有扫码图标 → 可能需要扫码查询接口
**C. 对接与校准:**
6. 找到接口后,更新 `Api.kt` 中的占位路径为真实路径
7. 根据接口的请求参数和返回字段结构,校准 Bean 类的字段名和类型
8. 遵循 memory 中的 API 搜索原则:不跨模块混用接口,遇到不确定的接口询问用户确认
### 第 14 步:重新编译验证
对接真实 API 后重新编译,确保无错误:
```bash
./gradlew assembleDebug
```
编译必须通过0 errors。如有错误立即修复后重新编译。
## 注意事项
### UI 设计规范(强制)
- **必须使用最新 UI 规范**:不管是新增页面、覆盖旧页面还是修改旧页面,都必须完全采用最新典型参考页面的 UI 设计规范
- **页面背景色**:根容器必须使用 `@color/color_f2``#F2F2F2`),禁止使用旧的白色或其他背景
- **搜索按钮**:使用 `@drawable/img_search` 图标36x36dp + padding 2dp不使用旧的 `iv_search_action` style
- **底部栏**:高度 50dp文字 18sp bold根据截图选择深蓝色`@color/color_bottom_layout` + 白字)或白色(`@color/white` + `@color/bottom_tool_tips_text_color`
- **详情页/表单页**:必须使用 **PadDataLayoutNew**(非旧版 PadDataLayout卡片用 `bg_white_radius_8`
- **严格匹配典型页面**:在写开发计划前,必须先读取最新典型参考页面(如航班查询列表、日志查询页等),总结并列出 UI 设计规范要点
### 通用规则
- **资源引用必须存在**:使用 drawable/color/mipmap 前确认资源存在,不存在则换用已有资源
- **import 路径查阅 CLAUDE.md**:基类和扩展函数的正确 import 路径参见开发指南的 Import 速查表
- **不创建不需要的文件**:如果截图中没有 Dialog 弹窗,不要创建 Dialog 文件
- **Bean 复用优先**:如果现有 Bean 的字段足以覆盖截图需求,直接复用
- **API 接口对接**:页面创建完成后,通过空港集团 API 文档 MCP 查找真实接口并替换占位路径。遵循 API 搜索原则,按业务模块目录查找,不跨模块混用
- **DataBinding 规则**:遵循 CLAUDE.md 中的 DataBinding 关键规则双向绑定、字符串拼接、View 导入等)