466 lines
19 KiB
Markdown
466 lines
19 KiB
Markdown
---
|
||
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 |
|
||
| **类型 4:Tab 详情页** | 自定义 Tab 栏 + ViewPager2 + 多 Fragment | 有 Tab 切换、无列表搜索 |
|
||
| **类型 5:编辑表单页** | ScrollView + PadDataLayoutNew 表单 + 保存/取消 | 有可编辑字段、有保存按钮 |
|
||
| **类型 6:添加表单页** | 类型 5 + 输入回调 + 实时计算 | 有 setRefreshCallBack 联动 |
|
||
|
||
**向用户确认**判定结果,格式:
|
||
|
||
```
|
||
📋 页面分析结果:
|
||
|
||
页面标题:XXX
|
||
页面类型:类型 N — XXXX
|
||
所属模块:module_xxx
|
||
|
||
搜索条件:
|
||
1. XXX(DATE)
|
||
2. XXX(SPINNER)
|
||
3. XXX(INPUT + 扫码)
|
||
|
||
列表字段(第一行):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 图标,36x36dp,padding=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(表单页)→ 查找详情查询接口、下拉选项字典接口
|
||
- 类型 4(Tab 详情)→ 查找各 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 导入等)
|