Files
aerologic-app/.claude/skills/phone-adapt/references/verification.md
YANG JIANKUAN db062c8ee3 feat: 完成国际出港移库/出库交接页面5.5寸手机端适配
- 落地手机端双形态适配框架:同名布局+资源限定符(layout/手机、
  layout-sw600dp/平板)自动切换,DeviceUtil 设备形态判定,7个
  手机版公共组件(PhoneSearchBar/StatusTab/DataLayout/FilterPanel/
  BottomBar/StatBox/KvItem)
- 完成「国际出港移库」「国际出港出库交接」两页手机端适配,新增
  各自详情页(IntExpMoveDetailActivity/IntExpOutHandoverDetailActivity)
- 手机端首页菜单接入"出港移库""出库交接"入口
- 修复手机端进入页面先横屏后转竖屏的闪屏问题:Manifest 全项目
  192 处改为 screenOrientation="unspecified",由 BaseActivity
  按设备形态运行时锁定方向
- 修复该方案的中间版本在平板端引入的回归(先竖后横 + UI放大1.6倍)
- AutoSize 补充手机竖屏 390×844 设计基准
- 修复 CHANGELOG.md 因脚本异常导致的内容重复损坏(膨胀至12万行),
  恢复正常结构并补充本次变更记录

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 14:18:29 +08:00

167 lines
6.7 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.

# 端到端验证手册
目标:在**真实接口**下把手机端走通,并确认平板端没退化。只看编译通过是不够的——
本次适配里"筛选条件被静默清空"和"后端不认新参数"两个问题,都只有跑真机才暴露。
## 环境
```bash
~/Library/Android/sdk/emulator/emulator -list-avds # Medium_Phone_API_36.1(手机) / Aerologic_Tablet(平板)
adb devices -l
adb -s <serial> shell wm size && adb -s <serial> shell wm density
```
判定形态:手机 1080×2400@420dpi`PHONE(sw=411dp)`;平板 1280×800@160dpi`TABLET(sw=800dp)`
`ui_probe.sh` 封装了常用操作,比手写 adb 省事且更可靠:
```bash
P=.claude/skills/phone-adapt/scripts/ui_probe.sh
$P <serial> form # 打印形态识别结果
$P <serial> texts # 当前界面所有可见文字(做断言用)
$P <serial> bounds "已交接" # 元素 bounds + 中心点
$P <serial> tap-text "确认" # 按文字点击(自动取中心点)
$P <serial> wait-text "待交接" # 等页面就绪
$P <serial> wait-gone "请稍候……" # 等加载弹窗消失
$P <serial> shot /tmp/a.png # 截图
$P <serial> req pageQuery # 抓请求/响应体
$P <serial> crash # 崩溃次数 + 栈
```
## 构建安装
```bash
./gradlew assembleDebug 2>&1 | grep -E "^(BUILD|FAILURE)|error:"
adb -s <serial> install -r -d app/build/outputs/apk/debug/app-debug.apk
```
注意 gradle 输出里大量 `e: 注: ARouter::Compiler …` 是注解处理器的**提示**被误标成 `e:`
不是错误;只看 `BUILD SUCCESSFUL` / `error:` 就行。
## 进入目标页面的两条路
### 正规路径(优先):登录 → 首页菜单
```bash
adb -s <serial> shell monkey -p com.lukouguoji.aerologic -c android.intent.category.LAUNCHER 1
```
登录页账号密码需要用户提供。填表用 `input text`,注意 `#` 等字符要转义(`Aa123456\#`)。
底部有「首页 / 国内 / 国际 / 我的」Tab国际业务页找对应入口。
**首页菜单是按权限过滤的**:当前账号角色没有该权限时,入口根本不显示(本次 ADMIN 登录后
国际业务只有「板箱过磅」「进港理货」,没有「出库交接」)。这时走下面的直启。
### 直启(权限缺失时的替代):冷启动 + am start
`app/src/debug/AndroidManifest.xml` 里把调试页临时 `exported="true"`,就能直接拉起。
**必须冷启动**(先 force-stop否则会撞上既有的 LoadingModel 崩溃:
```bash
adb -s <serial> shell am force-stop com.lukouguoji.aerologic
sleep 2
adb -s <serial> shell am start -n com.lukouguoji.aerologic/<完整类名>
```
登录态存在 SharedPreference 里,冷启动直启同样带 token接口能正常返回真实数据。
> 先经首页再 `am start` 会崩在 `LoadingModel.showLoading` → XPopup `popupInfo is null`
> 这是既有缺陷(未改动的页面也复现),不是本次改动引入的。
## 点击前先等就绪
加载弹窗("请稍候……")会盖住整屏并**吞掉 `input tap`**,表现为"点了没反应"。
本次验证就因此白跑了几轮。所以:
```bash
$P <serial> wait-text "待交接" # 等目标元素出现
$P <serial> tap-text "已交接"
$P <serial> texts | grep -c "全选" # 用文字变化确认点击确实生效
```
点完**一定要断言效果**(用 `texts` 或截图),别假设点中了。
## 逐项走查清单
每一步做完都跑一次 `crash`(必须为 0
- [ ] 列表加载:卡片字段、各种状态形态(如待处理 / 已完成两种卡片)
- [ ] Tab 切换:指示条位置、角标数字、底部条按设计显隐
- [ ] 筛选弹层:打开、各控件取值、重置、确认后重新查询
- [ ] 下拉字典:真实取到数据(不是空列表)
- [ ] 多选:单选、全选联动、"已选 N 项"计数
- [ ] 详情页/二级页:字段绑定、条件显示的区块
- [ ] 扫码入口能拉起
## 核对请求参数是否真的生效
新增的查询字段要确认两件事:客户端**发出去了**,服务端**认**。
```bash
$P <serial> req pageQuery
```
OkHttp 日志是多行 pretty JSON。看到请求体里有你的字段`"hoState": "0"`)只说明客户端对了。
**服务端是否生效要用行为验证**:切到另一个 Tab看返回数据是否真的变了。
本次实测:请求正确携带 `hoState:"0"/"1"`,但两个 Tab 返回同一批数据 → 后端没做过滤。
这种情况**必须在交付说明里如实标注**,不能让它看起来是通的。
## 无数据时怎么核对 UI
服务端当天可能没数据(列表空)。放宽条件(清空日期)再查;仍为空时,临时注入样例 Bean
```kotlin
// 临时:覆盖各种形态各一条,验证后删除
binding.rv.postDelayed({
binding.rv.commonAdapter()?.refresh(listOf(/* 待处理 / 危险品 / 已完成 各一条 */))
}, 2500)
```
截图核对完**必须删除**,然后重新构建 + grep 确认无残留:
```bash
grep -rn "injectMockForVisualCheck\|TODO 临时" module_base/src module_gjc/src | wc -l # 应为 0
```
## 屏幕方向验证
新增/修改 Activity 后,确认手机端不会「先横屏再转竖屏」:
```bash
# 冷启动时连续采样:应全程 mRotation=0
adb -s <phone> shell am force-stop com.lukouguoji.aerologic; sleep 2
adb -s <phone> shell monkey -p com.lukouguoji.aerologic -c android.intent.category.LAUNCHER 1
for i in $(seq 1 10); do adb -s <phone> shell dumpsys window | grep -m1 -oE "mRotation=[0-9]+"; sleep 0.4; done
```
平板则应首屏就是横屏(截图宽 > 高)。若手机出现横屏帧,检查该 Activity 的 Manifest
是否漏写 `android:screenOrientation="@integer/screen_orientation"`
## Pad 端回归(必做)
```bash
adb -s <tablet> install -r -d app/build/outputs/apk/debug/app-debug.apk
adb -s <tablet> shell am start -n com.lukouguoji.aerologic/<同一个类名>
$P <tablet> shot /tmp/tablet.png
$P <tablet> form # 应为 TABLET(sw=800dp…)
```
对比改造前后:搜索条数量与顺序、日期默认值、按钮(平板可能比手机多按钮)、底部统计文案。
改过 `module_base` 里的公共基类或 Pad 系组件时,这一步不能省,且要多看几个页面。
## 定位"值被神秘修改"类问题
在 setter 里打带堆栈的日志,一次就能看到调用来源:
```kotlin
android.util.Log.d("DBG", "value '$field' -> '$v'", Throwable("trace"))
```
```bash
adb -s <serial> logcat -d -s DBG | grep -A14 "'旧值' -> ''" | grep "at com.lukouguoji\|at android"
```
本次靠这招定位到 `TextView.onRestoreInstanceState → EditText.setText → doOnTextChanged`
即复合自定义 View 的 id 串档(见 `components.md`)。**排完删日志。**