fix: 出库交接交接状态参数名改为handoverState并完成内网验证
- 按 api-doc 接口文档修正交接状态入参 hoState → handoverState(原参数被后端 静默忽略,两个 Tab 返回同一批数据);内网实机验证 0/1 过滤与 App 端到端链路 均生效,平板端回归无影响 - 新增《后端接口对接问题清单.xlsx》:按模块/页面/接口定义/接口地址索引的后端 对接文档,含 5 项待处理缺口(移库缺航班字段/操作人姓名/数据不回填、交接缺 交接人入参、pageQueryTotal 出参恒 0)+ 1 项已验证归档 - phone-adapt skill 新增 references/api-doc.md(api-doc MCP 核对流程 + 内网 直调验证技巧),阶段 2 改为「先查接口文档再问用户」,修正 verification.md 中「后端不认 hoState」的过时误判结论 - CLAUDE.md / CHANGELOG.md 同步走查与验证结论 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -54,12 +54,22 @@ python3 $S/design_shots.py --html "…/出库交接.html" --out /tmp/design \
|
||||
把设计稿的每个筛选项/Tab/字段,对照平板 ViewModel 已有的字段:
|
||||
|
||||
- **已有字段** → 直接复用(如出港移库的 Tab 直接绑既有 `moveState`)
|
||||
- **平板没有的新维度**(新 Tab 状态、新下拉、新排序)→ **停下来问用户**要用什么接口参数名,
|
||||
连带问数据源(如「交接人」用 `DictUtils.getWHSUserList()`)
|
||||
- **平板没有的新维度**(新 Tab 状态、新下拉、新排序)→ **先查 api-doc MCP 拿权威参数名**,
|
||||
流程见 `references/api-doc.md`(连接方式 + 5 个工具 + 逐接口核对清单)。
|
||||
文档里查不到、或业务语义拿不准(如数据源该用哪个 `DictUtils` 方法)才用 `AskUserQuestion` 问用户
|
||||
|
||||
这一步不能猜。出库交接就是实测发现后端 `pageQuery` 根本不认 `hoState`,两个 Tab 返回同一批数据——
|
||||
参数名对不对只有后端知道。用 `AskUserQuestion` 一次问清,并在阶段 5 用真实请求体验证是否生效;
|
||||
**没生效就在交付说明里如实标注**,不要让它看起来是通的。
|
||||
这一步不能猜,也**不要拿「实测不生效」当结论**。出库交接第一版猜了 `hoState`,实测两个 Tab
|
||||
返回同一批数据,一度误判为「后端不支持」;后查 api-doc 才发现正确参数名是 `handoverState`——
|
||||
后端对不认识的参数是**静默忽略**,不报错,实测只能证明「这个名字不对」,证不出「后端没这能力」。
|
||||
|
||||
核对完把结论落两处(模板与格式见 `references/api-doc.md`):
|
||||
|
||||
- 后端真实缺口(缺入参/缺出参/数据不回填)→ 记入 `documents/后端接口对接问题清单.xlsx`,
|
||||
这是与后端对接的正式文档,按「模块 → 页面 → 接口定义 → 接口地址」索引,**每个页面适配完都要维护**
|
||||
- 参数名/字段绑定错误这类前端问题 → 直接改代码
|
||||
|
||||
阶段 5 仍要用真实请求体验证新参数确实生效;没生效就在清单文档与交付说明里如实标注,
|
||||
不要让它看起来是通的。
|
||||
|
||||
### 开工前对齐方案
|
||||
|
||||
@@ -121,7 +131,7 @@ visibility 即可(Bean 上加个只读计算属性如 `isHandovered` 让 XML
|
||||
val handoverState = MutableLiveData("") // 平板布局无对应控件 → 恒为空 → 不进请求
|
||||
val filterParams = mapOf(
|
||||
/* 原有字段… */
|
||||
"hoState" to handoverState.value?.ifEmpty { null },
|
||||
"handoverState" to handoverState.value?.ifEmpty { null }, // 参数名以 api-doc 为准
|
||||
)
|
||||
|
||||
// 手机专属的额外请求(Tab 角标计数、新下拉字典)用内部开关控制
|
||||
@@ -252,11 +262,13 @@ binding.rv.postDelayed({
|
||||
|
||||
## 收尾:文档与交付说明
|
||||
|
||||
代码之外还要落三处文档,否则下一个人会重复踩坑:
|
||||
代码之外还要落四处文档,否则下一个人会重复踩坑:
|
||||
|
||||
- 新增了公共组件/资源 → 更新 `CLAUDE.md`「手机版公共组件库」表 + `references/components.md`
|
||||
- 踩到新坑(崩溃、静默失效、环境问题)→ 写进 `CLAUDE.md`「常见编译错误速查」和本 skill 的
|
||||
对应 reference,把**定位手法**也写上,不只写结论
|
||||
- 接口有后端缺口(阶段 2 / 阶段 5 发现的)→ 维护 `documents/后端接口对接问题清单.xlsx`;
|
||||
已确认的字段语义追加到 `references/api-doc.md` 的「已核对过的语义」,别让下个页面再查一遍
|
||||
- `CHANGELOG.md` 追加条目(新增 / 改进 / 修复 / 验证 四段),记忆文件
|
||||
`phone-5.5inch-adaptation.md` 同步
|
||||
|
||||
@@ -270,6 +282,7 @@ binding.rv.postDelayed({
|
||||
|
||||
## 参考文件
|
||||
|
||||
- `references/api-doc.md` — api-doc MCP 连接与逐接口核对流程、后端缺口记录规范、已核对字段语义(阶段 2 前读)
|
||||
- `references/components.md` — 7 个手机版公共组件的属性表、用法与封装新组件的规矩(写布局前读)
|
||||
- `references/pad-safety.md` — 布局变体约束、Pad 零破坏模式、易崩点(写代码前读)
|
||||
- `references/verification.md` — 双端 adb 命令、登录与直启、已知环境坑(验证前读)
|
||||
|
||||
86
.claude/skills/phone-adapt/references/api-doc.md
Normal file
86
.claude/skills/phone-adapt/references/api-doc.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# api-doc MCP:接口定义核对流程(阶段 2 必读)
|
||||
|
||||
适配页面前,**所有用到的接口都要在 api-doc(AgentFox)里核对一遍入参/出参**,
|
||||
不要靠平板代码或猜测推断参数名——出库交接就因为猜了 `hoState`(实际是 `handoverState`)
|
||||
白白折腾了一轮实机排查。
|
||||
|
||||
## 连接方式
|
||||
|
||||
项目 `.mcp.json` 已配置 `api-doc` 远程 HTTP MCP。若会话内 MCP 工具可直接调用
|
||||
(`mcp__api-doc__*`),直接用;**若工具没有注入会话**(ToolSearch 搜不到),用 curl 走
|
||||
MCP 协议手工调用,三步:
|
||||
|
||||
```bash
|
||||
URL=$(python3 -c "import json;print(json.load(open('.mcp.json'))['mcpServers']['api-doc']['url'])")
|
||||
AUTH="Authorization: $(python3 -c "import json;print(json.load(open('.mcp.json'))['mcpServers']['api-doc']['headers']['Authorization'])")"
|
||||
|
||||
# 1) initialize:从响应头 mcp-session-id 取会话 id
|
||||
curl -s -D /tmp/mcp_headers.txt -X POST "$URL" -H "$AUTH" -H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-code","version":"1.0"}}}' > /dev/null
|
||||
SID=$(grep -i 'mcp-session-id' /tmp/mcp_headers.txt | tr -d '\r' | awk '{print $2}')
|
||||
|
||||
# 2) initialized 通知(必须发,否则 tools/* 报 session 未初始化)
|
||||
curl -s -X POST "$URL" -H "$AUTH" -H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" -H "mcp-session-id: $SID" \
|
||||
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}' > /dev/null
|
||||
|
||||
# 3) tools/call(响应是 SSE,`sed 's/^data: //'` 后按 JSON 解析)
|
||||
curl -s -X POST "$URL" -H "$AUTH" -H "Content-Type: application/json" \
|
||||
-H "Accept: application/json, text/event-stream" -H "mcp-session-id: $SID" \
|
||||
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_endpoints","arguments":{"keyword":"IntExpMove"}}}'
|
||||
```
|
||||
|
||||
## 可用工具(5 个)
|
||||
|
||||
| 工具 | 用途 |
|
||||
|------|------|
|
||||
| `get_project_overview` | 项目概览 + 模块清单(首次调用) |
|
||||
| `list_modules` | 全部模块含描述 |
|
||||
| `list_endpoints` | 单模块内接口列表(分页,`moduleId` + `cursor`) |
|
||||
| `search_endpoints` | 按关键字搜接口(`keyword` 用控制器前缀最准,如 `IntExpOutHandover`) |
|
||||
| `get_endpoint_detail` | **核心**:单接口完整契约,schema 已拍平成字段表(`endpointId`) |
|
||||
|
||||
## 核对清单(每个页面做一遍)
|
||||
|
||||
对 ViewModel 里每个 `NetApply.api.xxx` 调用:
|
||||
|
||||
1. 从 `module_base/.../http/net/Api.kt` 拿到 `@POST` 路径 → `search_endpoints` 找到接口 → `get_endpoint_detail`
|
||||
2. **入参**:前端传的每个 key 必须能在文档字段表里找到**同名字段**;找不到 = 传了也白传(后端静默忽略,不报错)
|
||||
3. **类型**:状态类字段(`moveState`/`handoverState`/`outState`)文档均为 integer,前端传 `"0"` 字符串靠
|
||||
Jackson 隐式转换(实测可用);新代码尽量传数字
|
||||
4. **出参**:设计稿卡片/详情页的每个展示字段,都要能对上出参字段;对不上就是后端缺口
|
||||
5. **结论落两处**:
|
||||
- 后端缺口(缺入参/缺出参/数据不回填)→ 记入 `documents/后端接口对接问题清单.xlsx`
|
||||
(按「模块 → 页面 → 接口定义 → 接口地址」格式,含前端现状与期望,状态标 ⏳/🔍)
|
||||
- 前端可自行解决的(参数名写错、字段绑错)→ 直接改代码,别记成后端问题
|
||||
|
||||
## 已核对过的语义(别再猜)
|
||||
|
||||
- `IntExpOutHandover`:交接状态入参 = `handoverState`(不是 `hoState`),**2026-07-29 内网实测过滤生效**
|
||||
(传 0/1 分别返回纯净的待/已交接集合,字符串 `"0"`/`"1"` 同样被接受);`ldId` 是**组装人**不是交接人;
|
||||
交接人姓名出参 `hoUserName`,编码 `hoId`;查询入参**没有**交接人筛选(后端缺口 #4)
|
||||
- `IntExpOutHandover/pageQueryTotal`:**出参恒 0**(任何条件下 totalPc/totalWeight/wbNumber 均为 0,
|
||||
与同条件 pageQuery 不一致,后端缺口 #6)——依赖它的 Tab 角标/底部统计显示 0 不是前端问题
|
||||
- `IntExpMove`:出参无航班字段(缺口 #1)、无操作人姓名(缺口 #2);`opId`/`opdate`/`businessType`
|
||||
文档有定义但实测恒 null(缺口 #3);`pageQueryTotal` 正常(与 pageQuery 口径一致,`moveState` 过滤生效)
|
||||
- `typeCode/getSpecialCodeByParam`:`flag=1` 国际,`ieFlag` 必须传空串(传 `"E"` 返回空数组)
|
||||
|
||||
## 内网直调验证技巧(比驱动 UI 快得多)
|
||||
|
||||
登录态 token 存在 SharedPreference 里,取出后可以直接 curl 后端做行为验证
|
||||
(**注意:不要把 token 打印到输出里**,落临时文件引用):
|
||||
|
||||
```bash
|
||||
adb -s <serial> shell "run-as com.lukouguoji.aerologic cat \
|
||||
/data/data/com.lukouguoji.aerologic/shared_prefs/data.xml" \
|
||||
| grep -o '<string name="token">[^<]*' | sed 's/.*>//' > /tmp/air_token
|
||||
|
||||
curl -s -X POST "http://192.168.1.250:8093/IntExpOutHandover/pageQuery" \
|
||||
-H "Authorization: $(cat /tmp/air_token)" -H "Content-Type: application/json" \
|
||||
-d '{"pageNum":1,"pageSize":50,"handoverState":0}'
|
||||
```
|
||||
|
||||
用它做「参数是否生效」的 A/B 对比(同条件只改一个参数看 total 变化),
|
||||
再回 App UI 走一遍确认端到端链路。UI 里的滚轮日期选择器可用
|
||||
`input swipe`(慢速 500ms、每次 3 格 ≈ 396px)驱动,滑完截图校准。
|
||||
@@ -101,11 +101,14 @@ $P <serial> texts | grep -c "全选" # 用文字变化确认点击确实生效
|
||||
$P <serial> req pageQuery
|
||||
```
|
||||
|
||||
OkHttp 日志是多行 pretty JSON。看到请求体里有你的字段(如 `"hoState": "0"`)只说明客户端对了。
|
||||
OkHttp 日志是多行 pretty JSON。看到请求体里有你的字段(如 `"handoverState": "0"`)只说明客户端对了。
|
||||
**服务端是否生效要用行为验证**:切到另一个 Tab,看返回数据是否真的变了。
|
||||
|
||||
本次实测:请求正确携带 `hoState:"0"/"1"`,但两个 Tab 返回同一批数据 → 后端没做过滤。
|
||||
这种情况**必须在交付说明里如实标注**,不能让它看起来是通的。
|
||||
⚠️ 行为验证失败时**先怀疑参数名,再怀疑后端**:出库交接第一版传 `hoState`,两个 Tab 返回同一批数据,
|
||||
一度误判为「后端没做过滤」;实际是参数名错了(api-doc 里是 `handoverState`),后端对不认识的参数
|
||||
**静默忽略**。正确顺序:查 `references/api-doc.md` 核对参数名 → 用 token 直调后端做 A/B 对比
|
||||
(同条件只改一个参数看 total 变化)→ 仍不生效才定性为后端缺陷并记入
|
||||
`documents/后端接口对接问题清单.xlsx`。
|
||||
|
||||
## 无数据时怎么核对 UI
|
||||
|
||||
@@ -136,7 +139,8 @@ for i in $(seq 1 10); do adb -s <phone> shell dumpsys window | grep -m1 -oE "mRo
|
||||
```
|
||||
|
||||
平板则应首屏就是横屏(截图宽 > 高)。若手机出现横屏帧,检查该 Activity 的 Manifest
|
||||
是否漏写 `android:screenOrientation="@integer/screen_orientation"`。
|
||||
是否写成了别的值——**必须是 `android:screenOrientation="unspecified"`**(写死 `userLandscape`
|
||||
或用 `@integer/...` 资源引用都会出问题,详见 SKILL.md 阶段 4)。
|
||||
|
||||
## Pad 端回归(必做)
|
||||
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -5,6 +5,22 @@
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### 修复 (Fixed)
|
||||
- 出库交接手机版「待/已交接」Tab 与角标计数的过滤参数名修正:`hoState` → `handoverState`
|
||||
(以 api-doc 接口文档为准;此前误传 `hoState` 被后端静默忽略,两个 Tab 返回同一批数据)。
|
||||
2026-07-29 内网实机验证通过:fdate=2026-07-16 下待交接/已交接 Tab 分别精确返回对应板箱,与后端直调结果一致。
|
||||
验证中发现 `IntExpOutHandover/pageQueryTotal` 出参恒 0(后端缺陷,致 Tab 角标与平板底部统计为 0,已记录问题清单 #6)
|
||||
|
||||
### 新增 (Added)
|
||||
- 新增《后端接口对接问题清单》(`documents/后端接口对接问题清单.xlsx`):手机端适配走查发现的接口缺口
|
||||
(移库列表缺航班字段、缺操作人姓名、`opId/opdate/businessType` 不回填、出库交接查询缺「交接人」入参),
|
||||
按模块/页面/接口定义/接口地址索引,作为与后端对接的持续维护文档
|
||||
- phone-adapt skill 新增 `references/api-doc.md`:api-doc MCP 连接与逐接口核对流程,接口对齐从「问用户」升级为「先查接口文档」
|
||||
|
||||
---
|
||||
|
||||
## [1.9.0] - 2026-07-29
|
||||
|
||||
### 新增 (Added)
|
||||
|
||||
10
CLAUDE.md
10
CLAUDE.md
@@ -1089,12 +1089,18 @@ override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) =
|
||||
|
||||
- ✅ **已完成**:适配框架 + 手机端公共资源 + 7 个手机版公共组件
|
||||
- ✅ **已完成页面**:
|
||||
- 「国际出港移库」`IntExpMoveActivity`(框架参考页,筛选弹层内暂仍用 `PadSearchLayout`)
|
||||
- 「国际出港移库」`IntExpMoveActivity` + 「运单详情」`IntExpMoveDetailActivity`(框架参考页)
|
||||
- 「国际出港出库交接」`IntExpOutHandoverActivity` + 「板箱详情」`IntExpOutHandoverDetailActivity`
|
||||
(公共组件首个完整落地页,双端实机验证通过)
|
||||
- ⏳ **待办**:
|
||||
- 把「移库」页筛选弹层内的 `PadSearchLayout` 换成 `PhoneDataLayout`,视觉统一
|
||||
- `PhoneSearchBar` 接入 AutoQuery 联想(`AutoQueryManager` 目前只对接了 Pad 系组件)
|
||||
- 出库交接 Tab 角标恒 0:`IntExpOutHandover/pageQueryTotal` 出参恒 0(后端缺陷,问题清单 #6),
|
||||
后端修复后角标自动恢复(`handoverState` 过滤本身已于 2026-07-29 实机验证通过)生效
|
||||
- 📋 **接口对接**:适配中发现的后端接口缺口统一记录在 `documents/后端接口对接问题清单.xlsx`
|
||||
(按模块/页面/接口定义/接口地址索引,与后端对接的正式文档,每页适配完必须维护);
|
||||
接口入参/出参一律以 api-doc MCP 为准核对,流程见
|
||||
`.claude/skills/phone-adapt/references/api-doc.md`——**后端对不认识的参数静默忽略**,
|
||||
猜参数名会得出「后端不支持」的误判(出库交接 `hoState`→`handoverState` 已踩过)
|
||||
|
||||
### 调试便利:直接拉起页面
|
||||
|
||||
|
||||
BIN
documents/后端接口对接问题清单.xlsx
Normal file
BIN
documents/后端接口对接问题清单.xlsx
Normal file
Binary file not shown.
@@ -219,7 +219,9 @@ class IntExpOutHandoverViewModel : BasePageViewModel() {
|
||||
"fdest" to fdest.value?.ifEmpty { null },
|
||||
"uld" to uldNo.value?.ifEmpty { null },
|
||||
// 以下两项为手机版新增筛选维度,平板端保持空值故不参与查询
|
||||
"hoState" to handoverState.value?.ifEmpty { null },
|
||||
// 参数名以 api-doc 为准:交接状态为 handoverState(integer,0 未交接 / 1 已交接)。
|
||||
// hoId(交接人)后端查询入参暂不支持,已记录到 documents/后端接口对接问题清单.xlsx
|
||||
"handoverState" to handoverState.value?.ifEmpty { null },
|
||||
"hoId" to hoId.value?.ifEmpty { null }
|
||||
)
|
||||
|
||||
@@ -255,12 +257,12 @@ class IntExpOutHandoverViewModel : BasePageViewModel() {
|
||||
/**
|
||||
* 加载「待交接 / 已交接」Tab 角标数量。
|
||||
*
|
||||
* 复用统计接口,分别以 hoState=0 / 1 各查一次,且沿用当前其余筛选条件,
|
||||
* 复用统计接口,分别以 handoverState=0 / 1 各查一次,且沿用当前其余筛选条件,
|
||||
* 保证角标与列表口径一致。仅手机版调用。
|
||||
*/
|
||||
private fun loadTabCounts(filterParams: Map<String, Any?>) {
|
||||
listOf("0", "1").forEachIndexed { index, state ->
|
||||
val params = (filterParams + mapOf("hoState" to state)).toRequestBody()
|
||||
val params = (filterParams + mapOf("handoverState" to state)).toRequestBody()
|
||||
launchCollect({ NetApply.api.getIntExpOutHandoverTotal(params) }) {
|
||||
onSuccess = { result ->
|
||||
val count = (result.data?.wbNumber ?: 0).toString()
|
||||
|
||||
Reference in New Issue
Block a user