Files
aerologic-app/.claude/skills/phone-adapt/references/components.md
YANG JIANKUAN 33c9f0e030 feat: 完成出港查询页面5.5寸手机端适配
- 列表/详情/运单追踪三页同名布局双变体(平板移 layout-sw600dp/,Kotlin 零设备分支)
- 列表:搜索行 + 三格统计(总票数/已入库/已离港,已加载数据客户端计数)+ 状态标签卡片
  (已离港/已入库/未入库,前端按 fclose/opDate 推断)+ 9 项底部筛选弹层(特码改下拉)
- 详情:3 Tab 复用 ViewPager2+Fragment,PhoneKvItem 网格;入库件数/重量按 warehouseList 求和
- 运单追踪:公共页 LogDetailActivity 双变体首例,手机竖排时间线动态构建,平板横向步骤条不动
- 修复 PhoneFilterPanel 筛选项超屏时底部按钮被挤出屏幕(内容插槽加 ScrollView)
- PhoneKvItem 新增 tagColor 绿色高亮(海关放行),占位符不套标签底
- 手机首页国际 Tab 加出港查询入口;后端缺口 #21/#22 登记问题清单
- 双端实机验证通过(内网真实数据 + Proxyman Map Local 补验未入库兜底态)
- skill 新增 references/proxyman.md:抓包/直调 A/B/Mock 与 api-doc 配合规范

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-31 18:13:44 +08:00

8.5 KiB
Raw Blame History

手机版公共组件(module_base/.../ui/weight/phone/

7 个组件覆盖设计稿反复出现的部件。先复用,实在没有再新增——每个新组件都要维护、都要踩一遍 状态恢复的坑,能少一个是一个。

所有属性都是无命名空间写法(与平板端 PadSearchLayout 一致DataBinding 适配器集中在 PhoneWidgetKtx.kt。同名属性(hint/value/title/list)按控件类型解析,与 Pad 系组件不冲突。

组件与属性

组件 用途 属性
PhoneSearchBar 顶部搜索行:搜索框 + 扫码 + 圆形筛选按钮 hintvalue(双向)、showScanshowFilterupperCasesetOnScanClickListenersetOnFilterClickListenersetRefreshCallBack
PhoneStatusTab 状态 Tab 条(文字 + 角标 + 选中下划线) tabs(List<KeyValue>)、counts(List<String>)、value(双向)、setOnTabChanged
PhoneDataLayout 表单/筛选项,三形态 titlehinttype(PhoneDataLayoutType.INPUT/DATE/SPINNER)、value(双向)、listrequiredenablesetRefreshCallBack
PhoneFilterPanel 底部筛选弹层(遮罩 + 面板 + 重置/确认) titlepanelVisiblesetOnResetClicksetOnConfirmClicksetOnDismissClick
PhoneBottomBar 底部操作条(全选 + 已选统计 + 主按钮) allCheckedcountTextactionTextsetOnAllCheckClicksetOnActionClick
PhoneStatBox 卡片内 2~4 列统计框,未传 label 的列自动隐藏 label1..4value1..4
PhoneKvItem 详情页字段项(灰标签 + 数值) titlevaluetag(true → 高亮标签)、tagColor("orange" 默认 / "green" 放行正常态);占位符 "-"/"--" 不套标签底
PhoneFormRow 行式表单项(左标签 minWidth 110dp + 右值 + 1px 分隔线),覆盖新增/修改/详情三态表单 titlehinttype(PhoneFormRowType.INPUT/SELECT/DATE/TEXT)、value(双向)、list(SELECT 选项)、requiredenablenumericsetRefreshCallBack

PhoneFormRow 要点SELECT 内置 XPopup 底部选择面板点击整行弹出无需页面代码TEXT 为只读右对齐、 空值自动占位 "--"详情态type 用三元按 pageType 切换即可一套布局覆盖三态页面(参考 app/res/layout/activity_uld_edit.xml)。PhoneBottomBaractionDanger="@{true}" 切浅红删除按钮。

PhoneFilterPanel 内容插槽已内置 ScrollView2026-07-31 加):筛选项少时行为不变; 筛选项多到超屏(出港查询 9 项)时内容区收缩滚动,底部「重置/确认」按钮始终可见—— 此前无滚动时按钮会被挤出屏幕外且毫无报错,只表现为"点不到确认"。

⚠️ 两个已踩坑:

  • FAB 盖住弹层:浮动按钮带 elevation 时会浮在 PhoneFilterPanel 之上,弹层实例要加 android:elevation="8dp"> FAB 的 6dp
  • Spinner 选择回调是异步的onItemSelected 由系统 post自动化测试里 tap 选项后立即 tap「确认」 可能抢在回值之前发出请求(表现为 UI 已显示选中但请求参数为 null——脚本里选完要 sleep 1 以上; 真人操作间隔足够,不受影响

KeyValuedev.utils.app.info.KeyValue,字段是 key(显示文案)+ value(业务值) 不是 namePhoneStatusTabtabs 就按 KeyValue("待交接","0") 传。

PhoneFilterPanel 的内容插槽

筛选项直接写在标签内,组件 onFinishInflate 会自动把它们移进面板中部并施加 20dp 项间距, 使用方不用关心内部层级、也不用逐个写 marginTop

<com.lukouguoji.module_base.ui.weight.phone.PhoneFilterPanel
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    title='@{"筛选条件"}'
    panelVisible="@{activity.filterPanelVisible}"
    setOnResetClick="@{(v)-> activity.resetFilter()}"
    setOnConfirmClick="@{(v)-> activity.confirmFilter()}"
    setOnDismissClick="@{(v)-> activity.toggleFilterPanel()}">

    <com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        title='@{"航班日期"}' hint='@{"请选择航班日期"}'
        type="@{PhoneDataLayoutType.DATE}" value="@={viewModel.flightDate}" />

</com.lukouguoji.module_base.ui.weight.phone.PhoneFilterPanel>

记得在 <data><import type="com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayoutType" />

⚠️ 新增复合组件必做:屏蔽子控件状态恢复

同一页面放多个同类组件时(比如筛选弹层里 3 个 PhoneDataLayout),它们的内部子控件 et/tv/spinner…)共用同一套 id。系统按 id 保存/恢复视图状态会跨实例串档。

实际后果出库交接踩过DATE 项的默认日期被邻近输入框恢复的空文本覆盖, EditText.setText("") 触发 doOnTextChanged,再经双向绑定把 ViewModel 的航班日期也写成空—— 筛选条件静默丢失,界面上只表现为"日期没了",极难联想到状态恢复

所以每个含内部 id 的组件都要写:

override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) =
    dispatchFreezeSelfOnly(container)

override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) =
    dispatchThawSelfOnly(container)

取值本来就由 ViewModel + DataBinding 持有,不需要视图级恢复,屏蔽掉没有副作用。

配套的第二道防线:多形态组件里,文本回写要限定形态,别让隐藏控件影响取值:

et.doOnTextChanged { text, _, _, _ ->
    if (type == PhoneDataLayoutType.INPUT) value = text.toString()
}

遇到"值被神秘清空"怎么定位

在 setter 里打一条带堆栈的日志,一次就能看到真凶:

android.util.Log.d("DBG", "value '$field' -> '$v'", Throwable("trace"))

出库交接那次的栈是 TextView.onRestoreInstanceState → EditText.setText → doOnTextChanged 指向非常明确。排完记得删日志。

新增组件的写法约定

照现有 7 个组件的模式来,保持一致:

  1. 继承 LinearLayout/FrameLayoutinitinflate(context, R.layout.layout_phone_xxx, this)
  2. 内部布局用 <merge tools:parentTag="android.widget.LinearLayout">,避免多一层嵌套
  3. 属性做成 Kotlin var + setter 里立即生效;双向绑定值用 onChangeListener: InverseBindingListener?
  4. BindingAdapter 统一加到 PhoneWidgetKtx.kt,函数名带组件名前缀避免顶层函数重名
  5. 有互斥形态时,type 要先于 value 生效(把 type 放进同一个多属性 adapter 的第一个 ?.let),否则 value 会写进错误的子控件
  6. 加上上面那两个 dispatch*SelfOnly 覆写
  7. 完成后同步更新 CLAUDE.md 的组件表和记忆文件 phone-5.5inch-adaptation.md

资源

配色一律用 phone_*,背景用 bg_phone_*,图标用 ic_phone_*,禁止写死色值。 已有的(不够再加,加完更新 CLAUDE.md

  • 色:phone_primary phone_page_bg phone_bg phone_stat_bg phone_text_title/body/hint/label/strong/weak_btn phone_divider phone_badge_bg phone_green phone_check_border phone_tag_gray_* phone_tag_green_* phone_tag_red_* phone_orange_bg/text
  • 背景:bg_phone_card bg_phone_search bg_phone_input bg_phone_stat_box bg_phone_panel_top bg_phone_icon_round bg_phone_btn_primary bg_phone_btn_outline bg_phone_btn_weak(浅蓝弱按钮) bg_phone_badge bg_phone_tag_green/gray/red/orange bg_phone_card_header_blue/green bg_phone_card_top_accent(卡片顶部主色描边条,出港计重头部卡片)
  • 图标:ic_phone_airplane ic_phone_location ic_phone_person ic_phone_check_circle ic_phone_chevron_right/down ic_phone_calendar ic_phone_close ic_phone_check_round_checked/unchecked(圆形复选框) ic_phone_clipboard(剪贴板,白色 FAB 用)ic_phone_weight(砝码,可 tint ic_phone_package(件数)ic_phone_tag(特码/标签)
  • 圆点:bg_phone_dot_green/blue/gray(状态指示点;蓝=出港查询卡片头,灰=时间线未达节点)
  • 复用 Pad 的矢量图:img_search img_scan img_filter(可 app:tint 染色)

设计稿里的 MDI 图标若缺失,直接写 24×24 的 vector用 MDI 官方 path比找位图靠谱且能染色。