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>
This commit is contained in:
277
.claude/skills/phone-adapt/SKILL.md
Normal file
277
.claude/skills/phone-adapt/SKILL.md
Normal file
@@ -0,0 +1,277 @@
|
||||
---
|
||||
name: phone-adapt
|
||||
description: 参照 documents/5.5寸手持端设计文件/ 的 HTML 设计稿,把 AirLogistics 指定页面适配成 5.5 寸手机竖屏版本——浏览器渲染设计稿逐态截图分析、复用 Phone* 公共组件写手机布局、复用现有 ViewModel 与接口、保证 Pad 端零改动,最后走完整登录流程实机联调并与设计稿逐项核对 UI。当用户说"适配手机端/做手机版/手持端适配"、点名某个设计稿入口(如"02 出库交接"、"做一下出港计重")、要求"按设计稿还原页面"、或提到 5.5 寸/手机版/双形态时,都要使用本 skill,即使没明说"适配"二字。
|
||||
---
|
||||
|
||||
# 5.5 寸手持端页面适配
|
||||
|
||||
把一个已有的平板页面,按设计稿适配出手机竖屏版本。核心约束是**只重写布局,业务逻辑与接口全部复用**,
|
||||
Pad 端行为必须零变化。
|
||||
|
||||
先读 `CLAUDE.md` 的「5.5 寸手持端(手机版)双形态适配规范」章节——那里是权威规范,本 skill 是执行流程。
|
||||
|
||||
## 前置确认
|
||||
|
||||
需要用户给出**目标页面**(设计稿目录名或业务名,如「02 出库交接」/「出港计重」)。
|
||||
其余自己查:`documents/5.5寸手持端设计文件/00_全部入口总览.html` 列出全部 11 个入口,
|
||||
每个子目录下可能有多个 HTML(主页 + 详情 + 子页面),都要处理。
|
||||
|
||||
对应的平板页面靠业务名在 `module_gjc/gjj/gnc/gnj` 里搜 Activity。**注意认准首页菜单实际跳转的那个
|
||||
Activity**,同名旧版文件在这个仓库里很常见。
|
||||
|
||||
## 阶段 1:渲染设计稿,看图不看代码
|
||||
|
||||
设计稿是带 Tailwind + JS 的交互原型,一个 HTML 里常塞了列表页、Tab、详情页、筛选弹层,靠 JS 切换。
|
||||
|
||||
**只读 HTML 源码会出错**,必须渲染成图:
|
||||
|
||||
```bash
|
||||
S=.claude/skills/phone-adapt/scripts
|
||||
# 1) 先看有哪些状态可切
|
||||
python3 $S/design_shots.py --html "documents/5.5寸手持端设计文件/02_出库交接/出库交接.html" --list
|
||||
|
||||
# 2) 按 --list 结果逐态渲染(照抄 onclick 里的真实参数最省事)
|
||||
python3 $S/design_shots.py --html "…/出库交接.html" --out /tmp/design \
|
||||
--state "01_列表:" \
|
||||
--state "02_已交接Tab:switchTab('shipped');" \
|
||||
--state "03_详情:showDetail({id:'PMC90901CZ',status:'已交接',rackNo:'076'});" \
|
||||
--state "04_筛选弹层:toggleFilterDrawer(true);"
|
||||
```
|
||||
|
||||
然后用 Read 工具**逐张看图**,抽取:配色、字号、圆角、间距、图标、控件顺序、空态、
|
||||
各状态下哪些元素显示/隐藏(例如「已交接」Tab 下底部操作条整条消失)。
|
||||
|
||||
`--list` 输出里如果出现 `window.location.href='xxx.html'`,说明是多文件原型,那些文件同样要渲染。
|
||||
|
||||
**设计稿的 JS 可能有 bug,别照抄字段映射。** 出库交接的详情页就把「航班日期」绑成了重量、
|
||||
「备注」绑成了航班号(HTML 里 id 重复)。判断依据是**静态 label 文案**:label 写「航班日期」
|
||||
就绑 `fdate`,写「板型」就绑 `boardType`。label 是设计意图,JS 只是 demo 接线。
|
||||
|
||||
标签文案同理:卡片上写死的「IMP代码」是占位符,实际要绑 `bean.dgrCode` 的值。
|
||||
|
||||
## 阶段 2:对齐接口能力,不要臆想
|
||||
|
||||
把设计稿的每个筛选项/Tab/字段,对照平板 ViewModel 已有的字段:
|
||||
|
||||
- **已有字段** → 直接复用(如出港移库的 Tab 直接绑既有 `moveState`)
|
||||
- **平板没有的新维度**(新 Tab 状态、新下拉、新排序)→ **停下来问用户**要用什么接口参数名,
|
||||
连带问数据源(如「交接人」用 `DictUtils.getWHSUserList()`)
|
||||
|
||||
这一步不能猜。出库交接就是实测发现后端 `pageQuery` 根本不认 `hoState`,两个 Tab 返回同一批数据——
|
||||
参数名对不对只有后端知道。用 `AskUserQuestion` 一次问清,并在阶段 5 用真实请求体验证是否生效;
|
||||
**没生效就在交付说明里如实标注**,不要让它看起来是通的。
|
||||
|
||||
### 开工前对齐方案
|
||||
|
||||
分析完先把结论摊给用户,避免闷头做错方向。一次说清,别挤牙膏:
|
||||
|
||||
```
|
||||
📱 页面适配方案:<页面名>
|
||||
|
||||
设计稿:documents/5.5寸手持端设计文件/<目录>/(N 个 HTML:主页 / 详情 / …)
|
||||
平板页面:XxxActivity + XxxViewModel(module_xxx)
|
||||
|
||||
手机版结构:
|
||||
搜索行:<字段>(含扫码/筛选按钮)
|
||||
状态 Tab:<Tab1> / <Tab2> → 绑定 <字段名>
|
||||
列表卡片:<字段…>,两种形态(<形态A> / <形态B>)
|
||||
底部操作条:全选 + 已选统计 + [<按钮>]
|
||||
筛选弹层:<字段1> / <字段2> / <字段3>
|
||||
二级页:<详情页名>(新增 Activity / 复用已有)
|
||||
|
||||
复用组件:PhoneSearchBar、PhoneStatusTab、PhoneStatBox…(无需新增 / 需新增 XXX)
|
||||
|
||||
接口:列表 <接口> 复用;新增参数 <名>(待你确认);新增字典 <DictUtils 方法>
|
||||
Pad 端影响:无(新字段默认空、额外请求由手机开关控制)
|
||||
|
||||
设计稿与平板的差异:<如手机版无「配运」按钮>
|
||||
|
||||
确认后开始编码。
|
||||
```
|
||||
|
||||
## 阶段 3:写布局,复用公共组件
|
||||
|
||||
`module_base/.../ui/weight/phone/` 已有 7 个组件,**优先复用,不要重写 XML**。
|
||||
组件清单、属性表与用法见 `references/components.md`(写布局前读一遍)。
|
||||
|
||||
改造步骤:
|
||||
|
||||
1. 平板布局 `git mv` 到 `res/layout-sw600dp/`(item 布局同理)
|
||||
2. 在 `res/layout/` 下建**同名**文件写手机布局
|
||||
3. Kotlin 侧 `layoutId()` / `itemLayoutId` **一律不动**,不做设备分支
|
||||
|
||||
三条硬约束(违反会编译失败或运行时崩溃,细节见 `references/pad-safety.md`):
|
||||
|
||||
| 约束 | 说明 |
|
||||
|------|------|
|
||||
| 两变体 `<variable>` 完全一致 | 手机变体新增 `activity` 变量时,平板变体也要补上(写注释说明平板不用它) |
|
||||
| 共用 id 双方都要有 | ViewHolder/Activity 引用的 id(`srl`/`rv`/`iv_icon` 等)两边都保留 |
|
||||
| 单侧独有 id 自动 `@Nullable` | 如手机独有的 `ll_detail`,ViewHolder 里必须判空调用 |
|
||||
|
||||
**一套 item 布局覆盖多种卡片形态**,不要为「待交接/已交接」建两个布局。按 bean 状态切
|
||||
visibility 即可(Bean 上加个只读计算属性如 `isHandovered` 让 XML 更干净;计算属性不参与 Gson
|
||||
序列化,安全)。
|
||||
|
||||
## 阶段 4:接业务,且不碰 Pad
|
||||
|
||||
手机端新增的查询维度用这个模式,能做到平板端请求次数与行为零变化:
|
||||
|
||||
```kotlin
|
||||
// ViewModel:新字段默认空值,getData() 里仅非空才进请求
|
||||
val handoverState = MutableLiveData("") // 平板布局无对应控件 → 恒为空 → 不进请求
|
||||
val filterParams = mapOf(
|
||||
/* 原有字段… */
|
||||
"hoState" to handoverState.value?.ifEmpty { null },
|
||||
)
|
||||
|
||||
// 手机专属的额外请求(Tab 角标计数、新下拉字典)用内部开关控制
|
||||
private var phoneExtrasEnabled = false
|
||||
fun initPhoneExtras() { phoneExtrasEnabled = true; handoverState.value = "0"; /* 加载字典 */ }
|
||||
```
|
||||
|
||||
```kotlin
|
||||
// Activity:只有手机形态才开启,平板不调用
|
||||
if (DeviceUtil.isPhone()) viewModel.initPhoneExtras()
|
||||
```
|
||||
|
||||
设备判断只用于**数据默认值与额外请求**,**绝不用于选布局**(选布局靠资源限定符)。
|
||||
|
||||
纯 UI 交互(弹层显隐、Tab 切换)写在 Activity,用 `ObservableBoolean` 暴露给 XML,不下沉到 ViewModel。
|
||||
|
||||
设计稿若少了平板上的某个按钮(如出库交接手机版只有「交接」没有「配运」),按设计稿做,
|
||||
但要在交付说明里点出来这个功能在手机端不可达,让用户决定。
|
||||
|
||||
新增手机专属页面(详情页等)记得:`app/src/main/AndroidManifest.xml` 注册 +
|
||||
`app/src/debug/AndroidManifest.xml` 追加 `exported="true"` 便于直启调试。
|
||||
|
||||
注册时 `screenOrientation` **必须写 `unspecified`**:
|
||||
|
||||
```xml
|
||||
<activity android:name="…"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:screenOrientation="unspecified" />
|
||||
```
|
||||
|
||||
- 写死 `userLandscape` → 手机端「先横屏、约 1 秒后转竖屏」闪一下(方向在建窗口时就定了,代码再纠正已晚)
|
||||
- 用 `@integer/xxx` 资源引用 → **更糟**:系统解析 Manifest 不套用设备限定符,平板会取到默认(竖屏)值,
|
||||
先竖后横,且 AutoSize 随之选错基准导致 UI 放大约 1.6 倍
|
||||
|
||||
`unspecified` 让系统按设备当前物理方向建窗口,两端起始方向本就正确,
|
||||
`BaseActivity` 的形态锁定不会产生二次纠正。
|
||||
|
||||
### ⚠️ 别忘了加手机端首页入口
|
||||
|
||||
手机端首页是 `module_p` 的 `PDAEnterActivity`,「国际」Tab 的菜单在
|
||||
`module_p/.../ui/enter/gj/GjViewModel.kt`(国内在 `gn/GnViewModel.kt`)。
|
||||
**菜单是手写清单,不是接口下发**;权限串只做过滤(`authList.contains(bean.key)`)。
|
||||
|
||||
适配完页面不回来加一行,手机上就**根本没有入口**——出库交接第一版就漏了这步,
|
||||
只能靠 adb 直启才能进去。加法:
|
||||
|
||||
```kotlin
|
||||
ActionBean(
|
||||
"出港移库",
|
||||
R.drawable.gjc_yi_ku_icon, // 图标复用 Pad 同款,复制 PNG 到 module_p/res/drawable-xxhdpi/
|
||||
Constant.AuthName.GjcYiKuListActivity, // 权限串与 Pad 端菜单一致
|
||||
ARouterConstants.ACTIVITY_URL_INT_EXP_MOVE // 填了 route 就走通用跳转,无需 when 分支
|
||||
),
|
||||
```
|
||||
|
||||
权限串和路由**都以 Pad 端 `app/.../HomeFragment.kt` 里该权限对应的 `ARouter.build(...)` 为准**
|
||||
(同一业务常有新旧两个 Activity,Pad 菜单实际跳的才是要适配的那个)。
|
||||
`module_p` 只依赖 `module_base`,看不到业务模块,所以跨模块必须走 ARouter、图标必须复制一份。
|
||||
|
||||
菜单没出现时先分清是「没权限」还是「没加清单」:
|
||||
|
||||
```bash
|
||||
adb -s <serial> shell "run-as com.lukouguoji.aerologic \
|
||||
cat /data/data/com.lukouguoji.aerologic/shared_prefs/data.xml" | tr ',' '\n' | grep -oE "App[A-Za-z]+" | sort -u
|
||||
```
|
||||
|
||||
## 阶段 5:端到端实机验证
|
||||
|
||||
先构建,再双端跑。命令与排障细节见 `references/verification.md`。
|
||||
|
||||
```bash
|
||||
./gradlew assembleDebug 2>&1 | grep -E "^(BUILD|FAILURE)|error:"
|
||||
```
|
||||
|
||||
构建过后核对 DataBinding 变体是否如预期(单侧 id 应为 `@Nullable`):
|
||||
|
||||
```bash
|
||||
grep -B3 "llDetail" module_gjc/build/generated/data_binding_base_class_source_out/debug/out/\
|
||||
com/lukouguoji/gjc/databinding/ItemXxxBinding.java
|
||||
```
|
||||
|
||||
**手机端**(真实数据链路,从登录开始):
|
||||
|
||||
```bash
|
||||
P=.claude/skills/phone-adapt/scripts/ui_probe.sh
|
||||
adb -s <phone> install -r -d app/build/outputs/apk/debug/app-debug.apk
|
||||
# 走登录 → 底部「国际」Tab → 点新加的菜单项进入(顺便验证入口加对了)
|
||||
# 同名文字多处时 tap-text 会提示命中数,用第 4 个参数指定序号(如底部 Tab 的「国际」)
|
||||
# 若账号确实没有该权限,改用冷启动直启(见 verification.md)
|
||||
$P <phone> form # 应打印 PHONE(sw=411dp…)
|
||||
$P <phone> wait-text "待交接" # 点击前先等页面就绪:加载弹窗会吞掉点击
|
||||
$P <phone> tap-text "已交接" # 按文字点击,比手算坐标可靠
|
||||
$P <phone> texts # 用文字变化断言点击确实生效(如底部条应消失)
|
||||
$P <phone> shot /tmp/p1.png
|
||||
$P <phone> crash # 必须为 0
|
||||
$P <phone> req pageQuery # 核对请求体参数确实带上了新字段
|
||||
```
|
||||
|
||||
逐项走查:列表/卡片两种形态、详情页、筛选弹层(重置+确认)、Tab 切换、单选与全选联动、
|
||||
下拉真实取数、扫码入口。每步 `crash` 都要为 0。
|
||||
|
||||
点完**一定要断言效果**(`texts` 或截图),不要假设点中了——加载弹窗盖屏时 `input tap` 会被静默吞掉。
|
||||
|
||||
手机端还要确认方向没闪屏(冷启动采样 `mRotation` 应恒为 0),命令见 `references/verification.md`。
|
||||
|
||||
**Pad 端回归**(必做):同样安装到平板模拟器打开同一页面截图,与改造前逐项对比——
|
||||
搜索条数量、默认值、按钮、底部统计都应一致。改过 `module_base` 公共组件时这步尤其不能省。
|
||||
|
||||
## 阶段 6:与设计稿二次核对
|
||||
|
||||
把阶段 1 的设计稿截图和阶段 5 的实机截图放在一起,用 Read 工具对照看,逐项核对:
|
||||
|
||||
配色 / 字号 / 字重 / 圆角 / 内外间距 / 图标形状与大小 / 控件顺序 / 选中态 / 状态标签配色 /
|
||||
**空值兜底**(真实数据空字段很常见——出库交接就出现过「库位」为空时橙色高亮标签只剩一个孤零零的
|
||||
小色块,需要空值时不套标签底)。
|
||||
|
||||
服务端当天没数据时(列表空)先放宽条件重查;仍为空就临时在 Activity 里注入样例 Bean 截图比对:
|
||||
|
||||
```kotlin
|
||||
// import com.lukouguoji.module_base.ktx.commonAdapter
|
||||
binding.rv.postDelayed({
|
||||
binding.rv.commonAdapter()?.refresh(listOf(/* 覆盖每种卡片形态各一条 */))
|
||||
}, 2500) // 等首次请求结束,否则会被真实结果覆盖
|
||||
```
|
||||
|
||||
**核对完必须删掉**,删完重新构建并 grep 确认无残留。
|
||||
|
||||
## 收尾:文档与交付说明
|
||||
|
||||
代码之外还要落三处文档,否则下一个人会重复踩坑:
|
||||
|
||||
- 新增了公共组件/资源 → 更新 `CLAUDE.md`「手机版公共组件库」表 + `references/components.md`
|
||||
- 踩到新坑(崩溃、静默失效、环境问题)→ 写进 `CLAUDE.md`「常见编译错误速查」和本 skill 的
|
||||
对应 reference,把**定位手法**也写上,不只写结论
|
||||
- `CHANGELOG.md` 追加条目(新增 / 改进 / 修复 / 验证 四段),记忆文件
|
||||
`phone-5.5inch-adaptation.md` 同步
|
||||
|
||||
向用户汇报时明确写出这四块:
|
||||
|
||||
1. 新增/修改的文件清单(布局、组件、Activity/ViewModel/Bean)
|
||||
2. Pad 端零破坏的具体做法 + 回归结论
|
||||
3. 复用与新增的公共组件
|
||||
4. **待确认事项**:后端未生效的参数、自己推断的业务规则(如 IMP 代码红/绿的判定)、
|
||||
设计稿与平板功能的差异。如实说,别含糊。
|
||||
|
||||
## 参考文件
|
||||
|
||||
- `references/components.md` — 7 个手机版公共组件的属性表、用法与封装新组件的规矩(写布局前读)
|
||||
- `references/pad-safety.md` — 布局变体约束、Pad 零破坏模式、易崩点(写代码前读)
|
||||
- `references/verification.md` — 双端 adb 命令、登录与直启、已知环境坑(验证前读)
|
||||
- `scripts/design_shots.py` — 设计稿逐态渲染截图
|
||||
- `scripts/ui_probe.sh` — 实机取文字/按文字点击/等待/截图/抓请求体/查崩溃
|
||||
Reference in New Issue
Block a user