From ef8e7cfacbef9d9b9337ce5608b577eb7ecaa24a Mon Sep 17 00:00:00 2001 From: YANG JIANKUAN Date: Thu, 30 Jul 2026 11:44:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E5=87=BA=E6=B8=AF?= =?UTF-8?q?=E8=BF=90=E6=8A=B5=E9=A1=B5=E9=9D=A25.5=E5=AF=B8=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E7=AB=AF=E9=80=82=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 主列表新增待运抵/已运抵 Tab(客户端按申报状态拆分)+ 卡片长按浮层(补充/重置/回执) - 新增分单明细页:复用主列表已加载数据,零新增请求 - 新增回执内容只读页 - 新增补充信息页:参照 Pad 端收发货人信息批量应用模式(后端接口暂缺,已登记问题清单) - 手机首页「国际」Tab 增加「出港运抵」入口 - 修复两个平台坑:RecyclerView 内 GONE 不塌陷高度;Serializable Intent 传递 遇 @Transient 字段反序列化为 null 导致的崩溃 - 登记后端接口缺口 2 项(#19~#20)至问题清单 Co-Authored-By: Claude Opus 4.6 --- .../skills/phone-adapt/references/api-doc.md | 7 + .../phone-adapt/references/pad-safety.md | 94 ++- CHANGELOG.md | 31 + CLAUDE.md | 14 + app/src/debug/AndroidManifest.xml | 24 + app/src/main/AndroidManifest.xml | 18 + documents/后端接口对接问题清单.xlsx | Bin 16942 -> 19513 bytes .../lukouguoji/module_base/bean/GjcMaWb.kt | 43 +- .../lukouguoji/module_base/http/net/Api.kt | 10 + .../main/res/drawable/bg_phone_fab_amber.xml | 6 + .../main/res/drawable/bg_phone_fab_green.xml | 6 + .../main/res/drawable/bg_phone_fab_purple.xml | 6 + .../main/res/drawable/bg_phone_tag_amber.xml | 7 + .../main/res/drawable/bg_phone_tag_indigo.xml | 7 + module_base/src/main/res/values/colors.xml | 4 + .../gjc/activity/IntExpArriveActivity.kt | 94 +++ .../activity/IntExpArriveReceiptActivity.kt | 43 + .../activity/IntExpArriveSubOrderActivity.kt | 92 +++ .../IntExpArriveSupplementActivity.kt | 41 + .../holder/IntExpArriveReceiptViewHolder.kt | 19 + .../holder/IntExpArriveSubOrderViewHolder.kt | 32 + .../gjc/holder/IntExpArriveViewHolder.kt | 50 +- .../viewModel/IntExpArriveReceiptViewModel.kt | 34 + .../IntExpArriveSubOrderViewModel.kt | 137 +++ .../IntExpArriveSupplementViewModel.kt | 119 +++ .../gjc/viewModel/IntExpArriveViewModel.kt | 151 +++- .../activity_int_exp_arrive.xml | 244 ++++++ .../layout-sw600dp/item_int_exp_arrive.xml | 520 ++++++++++++ .../res/layout/activity_int_exp_arrive.xml | 400 ++++----- .../activity_int_exp_arrive_receipt.xml | 44 + .../activity_int_exp_arrive_sub_order.xml | 114 +++ .../activity_int_exp_arrive_supplement.xml | 219 +++++ .../main/res/layout/item_int_exp_arrive.xml | 777 ++++++------------ .../layout/item_int_exp_arrive_receipt.xml | 57 ++ .../layout/item_int_exp_arrive_sub_order.xml | 130 +++ .../sixin/module_p/ui/enter/gj/GjViewModel.kt | 6 + .../drawable-xxhdpi/img_gjc_chugang_diyun.png | Bin 0 -> 33013 bytes 37 files changed, 2825 insertions(+), 775 deletions(-) create mode 100644 module_base/src/main/res/drawable/bg_phone_fab_amber.xml create mode 100644 module_base/src/main/res/drawable/bg_phone_fab_green.xml create mode 100644 module_base/src/main/res/drawable/bg_phone_fab_purple.xml create mode 100644 module_base/src/main/res/drawable/bg_phone_tag_amber.xml create mode 100644 module_base/src/main/res/drawable/bg_phone_tag_indigo.xml create mode 100644 module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveReceiptActivity.kt create mode 100644 module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveSubOrderActivity.kt create mode 100644 module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveSupplementActivity.kt create mode 100644 module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveReceiptViewHolder.kt create mode 100644 module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveSubOrderViewHolder.kt create mode 100644 module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveReceiptViewModel.kt create mode 100644 module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveSubOrderViewModel.kt create mode 100644 module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveSupplementViewModel.kt create mode 100644 module_gjc/src/main/res/layout-sw600dp/activity_int_exp_arrive.xml create mode 100644 module_gjc/src/main/res/layout-sw600dp/item_int_exp_arrive.xml create mode 100644 module_gjc/src/main/res/layout/activity_int_exp_arrive_receipt.xml create mode 100644 module_gjc/src/main/res/layout/activity_int_exp_arrive_sub_order.xml create mode 100644 module_gjc/src/main/res/layout/activity_int_exp_arrive_supplement.xml create mode 100644 module_gjc/src/main/res/layout/item_int_exp_arrive_receipt.xml create mode 100644 module_gjc/src/main/res/layout/item_int_exp_arrive_sub_order.xml create mode 100644 module_p/src/main/res/drawable-xxhdpi/img_gjc_chugang_diyun.png diff --git a/.claude/skills/phone-adapt/references/api-doc.md b/.claude/skills/phone-adapt/references/api-doc.md index 8634cee..6570f2e 100644 --- a/.claude/skills/phone-adapt/references/api-doc.md +++ b/.claude/skills/phone-adapt/references/api-doc.md @@ -70,6 +70,13 @@ curl -s -X POST "$URL" -H "$AUTH" -H "Content-Type: application/json" \ 0 正常/1 故障(三态需求=缺口 #7);无「来源」字段(#8)、无 `checkInDate`/`ifNo` 筛选入参(#9)、 无批量删除(#10,`deleteUld` 是 `@Query` 单条);出参 `ifNo`/`efNo`/`checkInDate`/`checkOutDate` 文档无描述,按命名推断进/出港航班号与出入库时间(#11 待确认) +- `IntExpArrive`(出港运抵,2026-07-30 内网实测):`declareStatus`(申报状态)只在出参, + `pageQuery`/`pageQueryTotal` 入参均无此字段(缺口 #20,待运抵/已运抵 Tab 靠客户端拆分); + `declareStatus`/`GjcHaWb.arrivalStatus` 三色编码:`01`=正常(绿)、`W`=靛蓝态、其余非空=琥珀异常态; + `haWbList[].response`/`arrivalOpDate` 是回执文本与对应时间,实测确认真实存在非空数据; + 分单补充收发货人信息**后端完全没有对应接口**(`search_endpoints` 对 `IntExpArrive`/`HaWb` + 关键字均搜不到分单更新接口,只有国际进港的 `IntImpAirManifest/complete`), + 实机调用拟定路径 `IntExpArrive/haWb/supplement` 确认 404(缺口 #19) - `IntExpCheckIn`(出港计重,2026-07-29 内网实测):`likeNo` **并非任意模糊**——按 8 位 `no` 匹配(11 位 wbNo 或部分号均 0 条),11 位全号必须走 `wbNo`;`checkIn`/`checkInList` 文档有定义但 `pageQuery`/`pageQueryTotal` **均静默忽略**(缺口 #12,待计重/计重中计数依赖); diff --git a/.claude/skills/phone-adapt/references/pad-safety.md b/.claude/skills/phone-adapt/references/pad-safety.md index 9cc07d5..5c30f26 100644 --- a/.claude/skills/phone-adapt/references/pad-safety.md +++ b/.claude/skills/phone-adapt/references/pad-safety.md @@ -130,7 +130,7 @@ binding.btnMove?.setOnClickListener { showMoveConfirmDialog() } 例:手机「待交接」Tab 下理论上只有未交接记录(服务端按状态过滤),所以全选照原样全量勾选就是对的; 若为此加上「跳过已交接」的过滤,反而改变了平板端(平板列表混排两种状态)的行为。 -## 屏幕方向:Manifest 必须写资源引用 +## 屏幕方向:Manifest 一律写 `unspecified` 新增 Activity 注册时: @@ -138,15 +138,20 @@ binding.btnMove?.setOnClickListener { showMoveConfirmDialog() } + android:screenOrientation="unspecified" /> ``` -**不要写死 `userLandscape`。** `screenOrientation` 由系统在创建 Activity 窗口时生效, -那时业务代码还没跑;写死横屏后即使 `BaseActivity` 在 `super.onCreate` 之前改成竖屏, -用户也会看到「先横屏、约 1 秒后转竖屏」的闪屏和重建。 +**两种错误方案都实测踩过,别再试:** +- 写死 `userLandscape` → 手机「先横屏、约 1 秒后转竖屏」(方向在建窗口时就定了, + `BaseActivity` 在 `super.onCreate` 之前纠正也来不及) +- 用 `@integer/screen_orientation` 资源引用(曾经以为是优雅方案)→ **更糟**:系统解析 + Manifest 属性不套用设备限定符,所有 Activity 实际都拿到 `values/` 默认值(竖屏), + 平板因此先竖后横,且 AutoSize 在竖屏配置下选中平板竖屏 720dp 基准 + (正常应为 1152×720 横屏基准),交互几次后整个 UI 放大约 1.6 倍 -资源取值:`module_base/res/values/integers.xml` = 1(portrait), -`values-sw600dp/integers.xml` = 11(userLandscape)。全项目已统一替换。 +`unspecified` 让系统按设备当前物理方向建窗口(平板横屏摆放→横屏起,手机→竖屏起, +两端起始方向本就正确),`BaseActivity.applyDeviceOrientation()` 再按形态锁定, +不产生二次纠正。全项目 192 处已统一为 `unspecified`。 ## 改 module_base 公共代码时 @@ -158,6 +163,81 @@ binding.btnMove?.setOnClickListener { showMoveConfirmDialog() } - Bean 上加只读计算属性是安全的(Gson 只序列化字段,不动 getter); 加**字段**要谨慎,会进请求体 +## RecyclerView 里不要用 item 级 visibility 做 Tab/分类过滤 + +普通 ViewGroup(LinearLayout 等)里 `View.GONE` 的子 View 会自动塌陷为 0 高度、不占布局空间—— +但 **RecyclerView + LinearLayoutManager 不是这样**:它按子项的测量高度参与滚动区域计算, +不会因为子项是 GONE 就跳过。给 item 根节点加 `visibility="@{tab条件 ? VISIBLE : GONE}"` +切 Tab,会在列表里留下一段空白(隐藏项的高度仍被保留),角标数字和实际卡片数量对不上。 + +出港运抵的「待运抵/已运抵」Tab 就踩了这个坑(实机截图验证:切到「已运抵」后顶部有一大块 +空白,隐藏的待运抵卡片高度并未塌陷)。正确做法是在数据层过滤,而不是视图层: + +```kotlin +// ViewModel:自己维护完整列表,不依赖 adapter.items 的历史状态 +// (见下一节:adapter.items 在 loadMore 场景可能已经是上次过滤后的子集,不能当作真源) +private var allLoadedList: List = emptyList() + +override fun getData() { + launchLoadingCollect({ ... }) { + onSuccess = { result -> + val newPageItems = result.list ?: emptyList() + allLoadedList = if (pageModel.page <= 1) newPageItems else allLoadedList + newPageItems + + pageModel.handleListBean(result) { + // 无论 handleListBean 内部把 adapter.items 搞成什么样,这里用 allLoadedList + // 强制覆盖为「当前 Tab 的正确子集」,兜底纠正 + val filtered = allLoadedList.filter { it.isPending == (currentTab.value == "pending") } + pageModel.rv?.commonAdapter()?.refresh(filtered) + } + } + } +} + +fun onTabChangedPhone() { + val filtered = allLoadedList.filter { it.isPending == (currentTab.value == "pending") } + pageModel.rv?.commonAdapter()?.refresh(filtered) // 只是 items.clear()+addAll()+notifyDataSetChanged,无副作用 +} +``` + +`CommonAdapter.refresh()` 只是替换内部列表并 `notifyDataSetChanged()`,不影响 +SmartRefreshLayout 的分页/完成状态,可以放心在 `handleListBean` 回调里再调用一次。 + +## Intent 传递 Serializable Bean:`@Transient` 字段反序列化后是 null + +`GjcMaWb`/`GjcHaWb` 的 `checked`/`showMore`/`actionMenuVisible` 三个 UI 状态字段都标了 +`@Transient`(本意是避免 Gson 把 `ObservableBoolean` 的内部结构序列化进网络请求体)。 +但 Kotlin 的 `@Transient` 注解生成的就是 JVM 的 `transient` 修饰符,这个修饰符对 **Java +对象序列化**(`Intent.putExtra(String, Serializable)` 走的正是这条路径)有实际含义: +反序列化时会跳过 `transient` 字段的恢复,对象引用类型的字段结果就是 **null**。 + +新增手机专属页面时,如果要把 `List` 通过 `ArrayList` 塞进 `Intent` +传给下一个页面,接收方拿到的对象「看起来」字段都对(wbNo、pc、weight 这些正常字段没问题, +因为它们不是 `@Transient`),但只要调用 `bean.checked.set(...)` 就会立刻崩溃: + +``` +NullPointerException: Attempt to invoke virtual method +'void androidx.databinding.ObservableBoolean.set(boolean)' on a null object reference +``` + +出港运抵的「分单明细」页第一版全选按钮就是这样崩的。**修复只需一行**,在接收方 +`initOnCreated` 里对拿到的列表整体 `.copy()` 一遍——`.copy()` 会重新触发一次真正的对象构造, +类体里 `val checked = ObservableBoolean(false)` 这类初始化逻辑跟着重新跑一遍, +把 null 补成全新实例: + +```kotlin +@Suppress("UNCHECKED_CAST") +fun initOnCreated(intent: Intent) { + val list = intent.getSerializableExtra(Constant.Key.DATA) as? ArrayList + // .copy() 补上被 Java 序列化跳过的 @Transient 字段 + fullList = (list ?: emptyList()).map { it.copy() } +} +``` + +这个坑只在「Intent 传递 Serializable Bean + 接收方要切换 checked/showMore 等状态」时才会 +触发;纯只读展示(如回执页)不调用这些字段的 setter,不会崩,但补一道 `.copy()` 无害, +建议只要通过 Intent 传了 haWbList/maWbList 就统一加上,别等下次踩到才想起来。 + ## 已知的既有问题(不是你改坏的) 用 `am start` 直启页面时,若**先经过首页**再启目标页,会崩在 diff --git a/CHANGELOG.md b/CHANGELOG.md index c2753ac..73ea40e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,37 @@ ## [Unreleased] ### 新增 (Added) +- 「出港运抵」5.5 寸手机端适配(主列表 + 3 个手机专属新页,设计稿 04_出港运抵): + 主列表 `IntExpArriveActivity` 新增「待运抵/已运抵」Tab(客户端按 declareStatus 是否为空拆分数据集, + 接口无此查询参数)+ 卡片长按浮层(待运抵→补充、已运抵→重置+回执,纯 UI 长按)+ 底部全选/已选/申报 + (仅待运抵 Tab 显示,选中范围独立于 Pad 全量选择逻辑);新增「分单明细」`IntExpArriveSubOrderActivity` + (数据直接复用主列表已加载的 haWbList,零新增请求,长按单条重置 + 底部批量申报);新增「回执内容」 + `IntExpArriveReceiptActivity`(只读页,复用 haWbList[].response + arrivalOpDate);新增「补充信息」 + `IntExpArriveSupplementActivity`(参照 Pad 端国际进港「收发货人信息」批量应用模式,对整个运单的 + haWbList 批量应用同一份表单,后端接口不存在,已实机确认 404,见问题清单 #19) +- 手机首页「国际」Tab 增加「出港运抵」入口 +- `GjcMaWb`/`GjcHaWb` 新增 `isPending`/`declareTagColor`/`haWbTagColor`/`arrivalOpDateText` 等 + 手机版专属计算属性;`GjcHaWb` 补充 `Serializable` 实现(供 Intent 跨页传递分单列表) +- 新增手机版资源:`bg_phone_tag_indigo`/`bg_phone_tag_amber`(申报状态三色标签)、 + `bg_phone_fab_purple`/`bg_phone_fab_amber`/`bg_phone_fab_green`(长按浮层圆形操作按钮) +- 后端缺口 2 项登记问题清单 #19~#20(分单补充信息保存接口不存在、Tab 无申报状态查询参数) + +### 修复 (Fixed) +- **RecyclerView + Tab 过滤新坑**:给 item 根节点加 `visibility="@{...}"` 切 Tab 会在列表里 + 留下空白——RecyclerView/LinearLayoutManager 不像普通 ViewGroup 那样让 GONE 子项自动塌陷为 + 0 高度。改为在 ViewModel 数据层维护完整列表,Tab 切换时把过滤后的子集整体 `refresh()` 给 + adapter(已实机截图验证空白消失、卡片数与角标一致) +- **Serializable Intent + @Transient 崩溃新坑**:`GjcMaWb`/`GjcHaWb` 的 `checked` 等 UI 状态 + 字段标了 `@Transient`(避免 Gson 序列化 ObservableBoolean),但这同时是 JVM `transient` + 修饰符,Java 序列化(Intent 传递机制)会跳过其恢复,反序列化后为 null,切换选中状态时 + NullPointerException 崩溃。修复:接收方 `initOnCreated` 对列表 `.map { it.copy() }` 重新 + 触发类体初始化补上(已实机崩溃复现并验证修复) + +### 验证 (Verified) +- 出港运抵双端实机验证通过:手机端 Tab 切换(角标/卡片双向正确)/全选/单选/长按菜单/查看分单 + /分单明细全选与长按重置/补充信息(真实数据回填+下拉字典+提交请求体完整+后端404如实呈现)/ + 回执页 全链路 crash=0、方向无闪屏;平板端布局与交互(4 搜索条+分单内嵌展开+底部 5 元素操作栏) + 与改造前逐项一致 - 「出港计重」5.5 寸手机端适配(首个全链路 4+1 页模块,设计稿 03_出港计重): 待计重列表(三格统计 总票数/待计重/计重中 + 卡片状态标签 未计重/计重中 + 卡片内单票「提前运抵」+ FAB 进计重记录 + 筛选弹层 通道号/承运人/运单类型/代理人)、开始计重(头部预配统计卡 + PhoneFormRow 表单, diff --git a/CLAUDE.md b/CLAUDE.md index 9be348d..347b5f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1110,6 +1110,18 @@ override fun dispatchRestoreInstanceState(container: SparseArray) = isEditMode 复用为手机全屏「修改记录」覆层)+ 手机专属新页 `GjcWeighingWbDetailActivity`(运单详情)。 搜索分流规则(实测):含字母→fno、11 位数字→wbNo、其余→likeNo(likeNo 按 8 位 no 匹配并非模糊); checkIn 过滤等 7 项后端缺口见问题清单 #12~#18 + - 「出港运抵」主列表 + 3 个手机专属新页:`IntExpArriveActivity`(待运抵/已运抵 Tab,客户端按 + `declareStatus` 是否为空拆分数据集,卡片长按浮层:待运抵→补充、已运抵→重置+回执)、 + `IntExpArriveSubOrderActivity`(分单明细,数据直接复用主列表已加载的 `haWbList`,零新增请求)、 + `IntExpArriveReceiptActivity`(回执内容只读页,复用 `haWbList[].response`)、 + `IntExpArriveSupplementActivity`(补充信息,参照 Pad 端国际进港「收发货人信息」批量应用模式, + 后端接口不存在,实机 404 已确认,见问题清单 #19)。 + Tab 过滤两个坑:① **RecyclerView 里 `View.GONE` 不会让 item 塌陷为 0 高度**(不同于普通 + ViewGroup),必须在数据层整体 `refresh()` 过滤后的子集给 adapter,不能给 item 根节点加 + visibility 绑定(实机截图验证过,见「常见编译错误速查」);② 通过 `Intent.putExtra(Serializable)` + 传递的 `GjcHaWb`/`GjcMaWb`,其 `checked`/`showMore`/`actionMenuVisible` 字段标了 + `@Transient`(= JVM `transient`,Java 序列化会跳过),反序列化后为 null,直接 + `.checked.set(...)` 会崩溃——接收方 `initOnCreated` 必须对列表 `.map { it.copy() }` 补上 - ⏳ **待办**: - `PhoneSearchBar` 接入 AutoQuery 联想(`AutoQueryManager` 目前只对接了 Pad 系组件) - 出库交接 Tab 角标恒 0:`IntExpOutHandover/pageQueryTotal` 出参恒 0(后端缺陷,问题清单 #6), @@ -1202,6 +1214,8 @@ adb shell am start -n com.lukouguoji.aerologic/com.lukouguoji.gjc.activity.IntEx | 手机版筛选项默认值(如日期)自动被清空 | 多个复合自定义 View 内部子控件 id 相同,系统按 id 恢复状态时跨实例串档 | 组件重写 `dispatchSaveInstanceState`/`dispatchRestoreInstanceState` 为 `dispatchFreezeSelfOnly`/`dispatchThawSelfOnly`(见「手机版公共组件库」) | | 手机上页面先横屏再转竖屏(闪一下) | Manifest 写死 `screenOrientation="userLandscape"`,系统建窗口时已按横屏,代码纠正为时已晚 | 改成 `android:screenOrientation="unspecified"`,由 `BaseActivity` 按形态锁定(见「屏幕方向」小节) | | 平板先竖屏再转横屏 + 交互后 UI 放大约 1.6 倍 | Manifest 用了 `@integer/screen_orientation` 资源引用;系统解析 Manifest 不套用限定符,全部取到竖屏值,AutoSize 随之选了平板竖屏 720dp 基准 | Manifest 改回 `unspecified`(见「屏幕方向」小节的 ⛔ 说明) | +| Tab/分类切换后列表顶部留一大块空白,卡片数量却不对 | 给 item 根节点加了 `visibility="@{tab条件 ? VISIBLE : GONE}"`——RecyclerView/LinearLayoutManager 不会像普通 ViewGroup 那样让 GONE 的子项自动塌陷为 0 高度,仍按测量高度参与滚动区域计算 | 不要用 item 级 visibility 做过滤;在 ViewModel 里自行维护完整列表,Tab 切换时把过滤后的子集整体 `commonAdapter()?.refresh(filteredList)` 给 adapter | +| 通过 `Intent.putExtra(Serializable)` 传递的 Bean,切换其 `checked`/`showMore` 等状态时崩溃 `NullPointerException: ... on a null object reference` | 这些字段标了 `@Transient`(= JVM `transient`,本意是避免 Gson 网络请求序列化 `ObservableBoolean`),但 Java 对象序列化(Intent 传递走的机制)会跳过 `transient` 字段的恢复,反序列化后是 null | 接收方 `initOnCreated` 里对列表做 `.map { it.copy() }`——`.copy()` 会重新走一遍类体初始化逻辑,用全新 `ObservableBoolean(false)` 补上 | --- diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml index 3fabae5..a169c60 100644 --- a/app/src/debug/AndroidManifest.xml +++ b/app/src/debug/AndroidManifest.xml @@ -80,5 +80,29 @@ android:exported="true" tools:replace="android:exported" /> + + + + + + + + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cc1e1d7..c02e4dd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -145,6 +145,24 @@ android:configChanges="orientation|keyboardHidden" android:exported="false" android:screenOrientation="unspecified" /> + + + + + + +qP}nw$;ThPCw6lGqb*T|2SFuTFFUHew>xP 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 delta 10890 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 diff --git a/module_base/src/main/java/com/lukouguoji/module_base/bean/GjcMaWb.kt b/module_base/src/main/java/com/lukouguoji/module_base/bean/GjcMaWb.kt index e05c916..fb47c05 100644 --- a/module_base/src/main/java/com/lukouguoji/module_base/bean/GjcMaWb.kt +++ b/module_base/src/main/java/com/lukouguoji/module_base/bean/GjcMaWb.kt @@ -1,6 +1,7 @@ package com.lukouguoji.module_base.bean import androidx.databinding.ObservableBoolean +import java.io.Serializable import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -122,12 +123,31 @@ data class GjcMaWb( val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态 @Transient val showMore: ObservableBoolean = ObservableBoolean(false) // 展开状态 + @Transient + val actionMenuVisible: ObservableBoolean = ObservableBoolean(false) // 5.5寸手机版:卡片长按操作浮层显隐 // 兼容现有API的isSelected属性 var isSelected: Boolean get() = checked.get() set(value) = checked.set(value) + /** + * 5.5寸手机版:是否处于「待运抵」Tab(申报状态为空)。 + * 接口无按申报状态区分的查询参数,Tab 由客户端对已加载数据拆分。 + */ + val isPending: Boolean + get() = declareStatus.isNullOrEmpty() + + /** + * 5.5寸手机版:申报状态三色标签的着色分类(01=绿/W=靛蓝/其余非空=琥珀)。 + */ + val declareTagColor: String + get() = when (declareStatus) { + "01" -> "green" + "W" -> "indigo" + else -> if (declareStatus.isNullOrEmpty()) "" else "amber" + } + // ==================== 状态转换扩展属性 ==================== /** * 计重状态中文 @@ -277,15 +297,36 @@ data class GjcHaWb( var tallyOpId: String? = null, // 理货操作人ID var declareCount: Long? = null, // 申报次数 var activeId: Long? = null // 活动ID -) { +) : Serializable { // ==================== UI扩展字段 ==================== @Transient val checked: ObservableBoolean = ObservableBoolean(false) // 选中状态 + @Transient + val actionMenuVisible: ObservableBoolean = ObservableBoolean(false) // 5.5寸手机版:卡片长按操作浮层显隐 // 兼容现有API的isSelected属性 var isSelected: Boolean get() = checked.get() set(value) = checked.set(value) + + /** + * 5.5寸手机版:申报状态三色标签的着色分类(01=绿/W=靛蓝/其余非空=琥珀)。 + * 与 GjcMaWb.declareTagColor 同源逻辑,作用于分单自身的 arrivalStatus。 + */ + val haWbTagColor: String + get() = when (arrivalStatus) { + "01" -> "green" + "W" -> "indigo" + else -> if (arrivalStatus.isNullOrEmpty()) "" else "amber" + } + + /** + * 5.5寸手机版「回执」页:运抵操作时间(格式化)。 + */ + val arrivalOpDateText: String + get() = arrivalOpDate?.let { + SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(it) + } ?: "" } /** diff --git a/module_base/src/main/java/com/lukouguoji/module_base/http/net/Api.kt b/module_base/src/main/java/com/lukouguoji/module_base/http/net/Api.kt index b17d31f..284433f 100644 --- a/module_base/src/main/java/com/lukouguoji/module_base/http/net/Api.kt +++ b/module_base/src/main/java/com/lukouguoji/module_base/http/net/Api.kt @@ -851,6 +851,16 @@ interface Api { @POST("IntExpArrive/deleteDeclare") suspend fun deleteArriveDeclare(@Body data: RequestBody): BaseResultBean + /** + * 国际出港运抵-分单补充收发货人信息(5.5寸手机版新增) + * + * 后端暂无此接口(api-doc 搜索 IntExpArrive、HaWb 均无匹配),路径为参照 Pad 端国际进港 + * IntImpAirManifest/complete 命名习惯拟定的建议值,见 documents/后端接口对接问题清单.xlsx。 + * 请求体为分单数组,仅需 haWbId 与收发货人相关字段。 + */ + @POST("IntExpArrive/haWb/supplement") + suspend fun supplementArriveHaWb(@Body data: RequestBody): BaseResultBean + /** * 国际出港移库-分页查询 * 接口路径: /IntExpMove/pageQuery diff --git a/module_base/src/main/res/drawable/bg_phone_fab_amber.xml b/module_base/src/main/res/drawable/bg_phone_fab_amber.xml new file mode 100644 index 0000000..7d921e1 --- /dev/null +++ b/module_base/src/main/res/drawable/bg_phone_fab_amber.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/module_base/src/main/res/drawable/bg_phone_fab_green.xml b/module_base/src/main/res/drawable/bg_phone_fab_green.xml new file mode 100644 index 0000000..cc2e7b6 --- /dev/null +++ b/module_base/src/main/res/drawable/bg_phone_fab_green.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/module_base/src/main/res/drawable/bg_phone_fab_purple.xml b/module_base/src/main/res/drawable/bg_phone_fab_purple.xml new file mode 100644 index 0000000..15aa096 --- /dev/null +++ b/module_base/src/main/res/drawable/bg_phone_fab_purple.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/module_base/src/main/res/drawable/bg_phone_tag_amber.xml b/module_base/src/main/res/drawable/bg_phone_tag_amber.xml new file mode 100644 index 0000000..abb4e6a --- /dev/null +++ b/module_base/src/main/res/drawable/bg_phone_tag_amber.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/module_base/src/main/res/drawable/bg_phone_tag_indigo.xml b/module_base/src/main/res/drawable/bg_phone_tag_indigo.xml new file mode 100644 index 0000000..8979188 --- /dev/null +++ b/module_base/src/main/res/drawable/bg_phone_tag_indigo.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/module_base/src/main/res/values/colors.xml b/module_base/src/main/res/values/colors.xml index 8247886..074da37 100644 --- a/module_base/src/main/res/values/colors.xml +++ b/module_base/src/main/res/values/colors.xml @@ -75,5 +75,9 @@ #EA580C #FEE2E2 #DC2626 + #E0E7FF + #6366F1 + #FEF3C7 + #D97706 \ No newline at end of file diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveActivity.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveActivity.kt index a1ab477..ce5498f 100644 --- a/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveActivity.kt +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveActivity.kt @@ -3,6 +3,7 @@ package com.lukouguoji.gjc.activity import android.app.Activity import android.content.Intent import android.os.Bundle +import androidx.databinding.ObservableBoolean import com.alibaba.android.arouter.facade.annotation.Autowired import com.alibaba.android.arouter.facade.annotation.Route import com.alibaba.android.arouter.launcher.ARouter @@ -10,15 +11,24 @@ import com.lukouguoji.gjc.R import com.lukouguoji.gjc.databinding.ActivityIntExpArriveBinding import com.lukouguoji.gjc.viewModel.IntExpArriveViewModel import com.lukouguoji.module_base.base.BaseBindingActivity +import com.lukouguoji.module_base.bean.GjcHaWb +import com.lukouguoji.module_base.bean.GjcMaWb import com.lukouguoji.module_base.common.Constant import com.lukouguoji.module_base.common.ConstantEvent import com.lukouguoji.module_base.impl.FlowBus import com.lukouguoji.module_base.impl.observe import com.lukouguoji.module_base.ktx.addOnItemClickListener +import com.lukouguoji.module_base.ktx.commonAdapter import com.lukouguoji.module_base.router.ARouterConstants +import com.lukouguoji.module_base.util.DeviceUtil +import java.util.ArrayList /** * 国际出港-出港运抵页面 + * + * 布局 id 不区分设备,平板/手机由资源限定符自动选择: + * layout-sw600dp/activity_int_exp_arrive.xml(平板) + * layout/activity_int_exp_arrive.xml(手机) */ @Route(path = ARouterConstants.ACTIVITY_URL_INT_EXP_ARRIVE) class IntExpArriveActivity : @@ -31,12 +41,25 @@ class IntExpArriveActivity : override fun layoutId() = R.layout.activity_int_exp_arrive override fun viewModelClass() = IntExpArriveViewModel::class.java + /** 手机版筛选弹层显隐(仅 UI 状态,不涉及业务逻辑) */ + val filterPanelVisible = ObservableBoolean(false) + + /** 供 [com.lukouguoji.gjc.holder.IntExpArriveViewHolder] 绑定 item 布局的 viewModel 变量(父类 viewModel 为 protected) */ + val itemViewModel: IntExpArriveViewModel get() = viewModel + override fun initOnCreate(savedInstanceState: Bundle?) { // 注入 ARouter 参数 ARouter.getInstance().inject(this) setBackArrow("国际出港运抵") binding.viewModel = viewModel + binding.activity = this + + // 手机版专属数据:加载「代理」筛选下拉 + // 平板端不调用,查询链路与请求次数与改造前完全一致 + if (DeviceUtil.isPhone()) { + viewModel.initPhoneExtras() + } // 如果有传入运单号,自动填充并触发搜索 if (!wbNoParam.isNullOrEmpty()) { @@ -63,6 +86,77 @@ class IntExpArriveActivity : } } + // ==================== 手机版专用交互(纯 UI,复用既有 ViewModel 字段与数据) ==================== + + /** + * 手机版 Tab 切换回调:PhoneStatusTab 已通过双向绑定写入 viewModel.currentTab, + * 这里触发 ViewModel 把适配器数据集替换为过滤后的子集(不发起新请求)。 + */ + fun onArriveTabChanged() { + viewModel.onTabChangedPhone() + } + + /** 卡片长按:展开操作浮层(View.OnLongClickListener 要求返回 Boolean) */ + fun showCardActionPhone(bean: GjcMaWb): Boolean { + bean.actionMenuVisible.set(true) + return true + } + + /** 点击浮层遮罩/空白处:收起操作浮层 */ + fun hideCardActionPhone(bean: GjcMaWb) { + bean.actionMenuVisible.set(false) + } + + /** 选择圈勾选状态变化后回调(由 ViewHolder 调用) */ + fun onItemCheckChanged() { + viewModel.onItemCheckChanged() + } + + /** 手机版筛选弹层显隐切换 */ + fun toggleFilterPanel() { + filterPanelVisible.set(!filterPanelVisible.get()) + } + + /** 手机版筛选:重置 */ + fun resetFilter() { + viewModel.flightDate.value = "" + viewModel.flightNo.value = "" + viewModel.agentCodePhone.value = "" + } + + /** 手机版筛选:确认 */ + fun confirmFilter() { + filterPanelVisible.set(false) + viewModel.searchClick() + } + + /** + * 进入「分单明细」页:直接传递该运单已加载的 haWbList,零新增请求。 + */ + fun openSubOrder(bean: GjcMaWb) { + val list = ArrayList(bean.haWbList.orEmpty()) + IntExpArriveSubOrderActivity.start(this, bean.wbNo.orEmpty(), list) + } + + /** + * 进入「回执」页(手机专属只读页):复用 haWbList[].response + arrivalOpDate,零新增请求。 + */ + fun openReceipt(bean: GjcMaWb) { + bean.actionMenuVisible.set(false) + val list = ArrayList(bean.haWbList.orEmpty()) + IntExpArriveReceiptActivity.start(this, bean.wbNo.orEmpty(), list) + } + + /** + * 进入「补充信息」页(手机专属新页):对该运单整个 haWbList 批量应用(参照 Pad 端国际进港 + * IntArrSupplementInfoViewModel 的批量复制模式)。后端暂无保存接口,见接口问题清单。 + */ + fun openSupplement(bean: GjcMaWb) { + bean.actionMenuVisible.set(false) + val list = ArrayList(bean.haWbList.orEmpty()) + IntExpArriveSupplementActivity.start(this, bean.wbNo.orEmpty(), list) + } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveReceiptActivity.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveReceiptActivity.kt new file mode 100644 index 0000000..1571b25 --- /dev/null +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveReceiptActivity.kt @@ -0,0 +1,43 @@ +package com.lukouguoji.gjc.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.lukouguoji.gjc.R +import com.lukouguoji.gjc.databinding.ActivityIntExpArriveReceiptBinding +import com.lukouguoji.gjc.viewModel.IntExpArriveReceiptViewModel +import com.lukouguoji.module_base.base.BaseBindingActivity +import com.lukouguoji.module_base.bean.GjcHaWb +import com.lukouguoji.module_base.common.Constant +import com.lukouguoji.module_base.ktx.refresh + +/** + * 国际出港运抵-回执内容页(5.5寸手机版专属只读新页,平板端无对应页面) + * + * 数据来自主列表页已加载的 haWbList,零新增请求。 + * 对应设计稿 documents/5.5寸手持端设计文件/04_出港运抵/receipt.html。 + */ +class IntExpArriveReceiptActivity : + BaseBindingActivity() { + + override fun layoutId() = R.layout.activity_int_exp_arrive_receipt + override fun viewModelClass() = IntExpArriveReceiptViewModel::class.java + + override fun initOnCreate(savedInstanceState: Bundle?) { + setBackArrow("回执内容") + binding.viewModel = viewModel + + viewModel.initOnCreated(intent) + viewModel.receiptList.observe(this) { binding.rv.refresh(it) } + } + + companion object { + @JvmStatic + fun start(context: Context, wbNo: String, haWbList: ArrayList) { + val starter = Intent(context, IntExpArriveReceiptActivity::class.java) + .putExtra(Constant.Key.CODE, wbNo) + .putExtra(Constant.Key.DATA, haWbList) + context.startActivity(starter) + } + } +} diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveSubOrderActivity.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveSubOrderActivity.kt new file mode 100644 index 0000000..3e2259d --- /dev/null +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveSubOrderActivity.kt @@ -0,0 +1,92 @@ +package com.lukouguoji.gjc.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.databinding.ObservableBoolean +import com.lukouguoji.gjc.R +import com.lukouguoji.gjc.databinding.ActivityIntExpArriveSubOrderBinding +import com.lukouguoji.gjc.viewModel.IntExpArriveSubOrderViewModel +import com.lukouguoji.module_base.base.BaseBindingActivity +import com.lukouguoji.module_base.bean.GjcHaWb +import com.lukouguoji.module_base.common.Constant +import com.lukouguoji.module_base.ktx.commonAdapter +import com.lukouguoji.module_base.ktx.refresh + +/** + * 国际出港运抵-分单明细页(5.5寸手机版专属新页,平板端无对应页面) + * + * 数据来自主列表页已加载的 haWbList(Intent 传入),零新增请求。 + * 对应设计稿 documents/5.5寸手持端设计文件/04_出港运抵/suborder.html。 + */ +class IntExpArriveSubOrderActivity : + BaseBindingActivity() { + + override fun layoutId() = R.layout.activity_int_exp_arrive_sub_order + override fun viewModelClass() = IntExpArriveSubOrderViewModel::class.java + + /** 筛选弹层显隐(仅 UI 状态) */ + val filterPanelVisible = ObservableBoolean(false) + + override fun initOnCreate(savedInstanceState: Bundle?) { + setBackArrow("分单明细") + binding.viewModel = viewModel + binding.activity = this + + viewModel.initOnCreated(intent) + + // 监听数据变化刷新列表 + viewModel.displayList.observe(this) { binding.rv.refresh(it) } + } + + /** 子项选中状态变化后回调(由 ViewHolder 调用) */ + fun onItemCheckChanged() { + viewModel.onItemCheckChanged() + } + + /** 全选按钮点击:切换全部选中状态后强制刷新列表(checked 为原地修改,不触发 LiveData) */ + fun checkAllClick() { + viewModel.checkAllClick() + binding.rv.commonAdapter()?.notifyDataSetChanged() + } + + /** 卡片长按:展开操作浮层(View.OnLongClickListener 要求返回 Boolean) */ + fun showActionMenu(bean: GjcHaWb): Boolean { + bean.actionMenuVisible.set(true) + return true + } + + /** 点击浮层遮罩:收起操作浮层 */ + fun hideActionMenu(bean: GjcHaWb) { + bean.actionMenuVisible.set(false) + } + + /** 长按菜单「重置」(单条) */ + fun resetSingleClick(bean: GjcHaWb) { + bean.actionMenuVisible.set(false) + viewModel.resetSingleClick(bean) + } + + fun toggleFilterPanel() { + filterPanelVisible.set(!filterPanelVisible.get()) + } + + fun resetFilter() { + viewModel.hnoFilter.value = "" + } + + fun confirmFilter() { + filterPanelVisible.set(false) + viewModel.searchClick() + } + + companion object { + @JvmStatic + fun start(context: Context, wbNo: String, haWbList: ArrayList) { + val starter = Intent(context, IntExpArriveSubOrderActivity::class.java) + .putExtra(IntExpArriveSubOrderViewModel.KEY_WB_NO, wbNo) + .putExtra(Constant.Key.DATA, haWbList) + context.startActivity(starter) + } + } +} diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveSupplementActivity.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveSupplementActivity.kt new file mode 100644 index 0000000..1d510b2 --- /dev/null +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/activity/IntExpArriveSupplementActivity.kt @@ -0,0 +1,41 @@ +package com.lukouguoji.gjc.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.lukouguoji.gjc.R +import com.lukouguoji.gjc.databinding.ActivityIntExpArriveSupplementBinding +import com.lukouguoji.gjc.viewModel.IntExpArriveSupplementViewModel +import com.lukouguoji.module_base.base.BaseBindingActivity +import com.lukouguoji.module_base.bean.GjcHaWb +import com.lukouguoji.module_base.common.Constant + +/** + * 国际出港运抵-补充信息页(5.5寸手机版专属新页,平板端无对应页面) + * + * 参照 Pad 端国际进港「收发货人信息」补充页的批量应用模式:对该运单整个 haWbList 批量应用 + * 同一份收发货人表单。后端暂无保存接口,见 IntExpArriveSupplementViewModel 顶部注释。 + * 对应设计稿 documents/5.5寸手持端设计文件/04_出港运抵/supplement.html。 + */ +class IntExpArriveSupplementActivity : + BaseBindingActivity() { + + override fun layoutId() = R.layout.activity_int_exp_arrive_supplement + override fun viewModelClass() = IntExpArriveSupplementViewModel::class.java + + override fun initOnCreate(savedInstanceState: Bundle?) { + setBackArrow("补充信息") + binding.viewModel = viewModel + viewModel.initOnCreated(intent) + } + + companion object { + @JvmStatic + fun start(context: Context, wbNo: String, haWbList: ArrayList) { + val starter = Intent(context, IntExpArriveSupplementActivity::class.java) + .putExtra(Constant.Key.CODE, wbNo) + .putExtra(Constant.Key.DATA, haWbList) + context.startActivity(starter) + } + } +} diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveReceiptViewHolder.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveReceiptViewHolder.kt new file mode 100644 index 0000000..d757114 --- /dev/null +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveReceiptViewHolder.kt @@ -0,0 +1,19 @@ +package com.lukouguoji.gjc.holder + +import android.view.View +import com.lukouguoji.gjc.databinding.ItemIntExpArriveReceiptBinding +import com.lukouguoji.module_base.base.BaseViewHolder +import com.lukouguoji.module_base.bean.GjcHaWb + +/** + * 国际出港运抵-回执内容列表 ViewHolder(5.5寸手机版专属只读页) + */ +class IntExpArriveReceiptViewHolder(view: View) : + BaseViewHolder(view) { + + override fun onBind(item: Any?, position: Int) { + val bean = getItemBean(item) ?: return + binding.bean = bean + binding.executePendingBindings() + } +} diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveSubOrderViewHolder.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveSubOrderViewHolder.kt new file mode 100644 index 0000000..ca6450e --- /dev/null +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveSubOrderViewHolder.kt @@ -0,0 +1,32 @@ +package com.lukouguoji.gjc.holder + +import android.view.View +import com.lukouguoji.gjc.activity.IntExpArriveSubOrderActivity +import com.lukouguoji.gjc.databinding.ItemIntExpArriveSubOrderBinding +import com.lukouguoji.module_base.base.BaseViewHolder +import com.lukouguoji.module_base.bean.GjcHaWb + +/** + * 国际出港运抵-分单明细列表 ViewHolder(5.5寸手机版专属页面) + * + * 选中状态变化只影响本页自身的 ViewModel(不复用全局 FlowBus.EVENT_CHECK_CHANGED, + * 避免与主列表页共享同一事件、产生跨页面的无意义联动)。 + */ +class IntExpArriveSubOrderViewHolder(view: View) : + BaseViewHolder(view) { + + override fun onBind(item: Any?, position: Int) { + val bean = getItemBean(item) ?: return + val activity = itemView.context as? IntExpArriveSubOrderActivity + binding.bean = bean + binding.activity = activity + binding.executePendingBindings() + + // 整卡点击切换选择状态 + binding.ll.setOnClickListener { + bean.checked.set(!bean.checked.get()) + activity?.onItemCheckChanged() + binding.executePendingBindings() + } + } +} diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveViewHolder.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveViewHolder.kt index 6c2750a..963cb75 100644 --- a/module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveViewHolder.kt +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/holder/IntExpArriveViewHolder.kt @@ -2,6 +2,7 @@ package com.lukouguoji.gjc.holder import android.view.View import com.lukouguoji.gjc.R +import com.lukouguoji.gjc.activity.IntExpArriveActivity import com.lukouguoji.gjc.databinding.ItemIntExpArriveBinding import com.lukouguoji.module_base.adapter.setCommonAdapter import com.lukouguoji.module_base.base.BaseViewHolder @@ -12,52 +13,73 @@ import com.lukouguoji.module_base.ktx.refresh /** * 国际出港-出港运抵 列表项ViewHolder + * + * item 布局分平板(layout-sw600dp/item_int_exp_arrive.xml,内嵌分单子列表 rv_sub 展开/收起) + * 与手机(layout/item_int_exp_arrive.xml,分单改为独立「分单明细」页,无 rv_sub/iv_show 等 id) + * 两个变体,单侧独有的 id 在 Binding 基类中为 `@Nullable`,需判空调用。 */ class IntExpArriveViewHolder(view: View) : BaseViewHolder(view) { override fun onBind(item: Any?, position: Int) { val bean = getItemBean(item) ?: return + val activity = itemView.context as? IntExpArriveActivity binding.bean = bean binding.position = position + binding.activity = activity + binding.viewModel = activity?.itemViewModel binding.executePendingBindings() - // 添加图标点击事件 - 切换选择状态 - binding.ivIcon.setOnClickListener { + // 添加图标点击事件 - 切换选择状态(平板独有) + binding.ivIcon?.setOnClickListener { bean.checked.set(!bean.checked.get()) FlowBus.with(ConstantEvent.EVENT_CHECK_CHANGED).tryEmit("check_changed") binding.executePendingBindings() } - // 展开按钮点击事件 - binding.ivShow.setOnClickListener { + // 展开按钮点击事件(平板独有) + binding.ivShow?.setOnClickListener { bean.showMore.set(!bean.showMore.get()) } + // 选择圈点击事件(手机独有,仅待运抵 Tab 可见) + binding.ivSelect?.setOnClickListener { + bean.checked.set(!bean.checked.get()) + activity?.onItemCheckChanged() + binding.executePendingBindings() + } + + // 子列表 RecyclerView 仅平板存在,手机版分单改走独立页面 + val rvSub = binding.rvSub ?: return + // 初始化子列表 RecyclerView setCommonAdapter( - binding.rvSub, + rvSub, IntExpArriveSubViewHolder::class.java, R.layout.item_int_exp_arrive_sub ) // 刷新子列表数据 val subList = bean.haWbList ?: emptyList() - binding.rvSub.refresh(subList) + rvSub.refresh(subList) updateSubListVisibility(subList.isNotEmpty()) } private fun updateSubListVisibility(hasData: Boolean) { + val llHeader = binding.llHeader ?: return + val dividerHeader = binding.dividerHeader ?: return + val rvSub = binding.rvSub ?: return + val tvEmpty = binding.tvEmpty ?: return if (hasData) { - binding.llHeader.visibility = View.VISIBLE - binding.dividerHeader.visibility = View.VISIBLE - binding.rvSub.visibility = View.VISIBLE - binding.tvEmpty.visibility = View.GONE + llHeader.visibility = View.VISIBLE + dividerHeader.visibility = View.VISIBLE + rvSub.visibility = View.VISIBLE + tvEmpty.visibility = View.GONE } else { - binding.llHeader.visibility = View.GONE - binding.dividerHeader.visibility = View.GONE - binding.rvSub.visibility = View.GONE - binding.tvEmpty.visibility = View.VISIBLE + llHeader.visibility = View.GONE + dividerHeader.visibility = View.GONE + rvSub.visibility = View.GONE + tvEmpty.visibility = View.VISIBLE } } } diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveReceiptViewModel.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveReceiptViewModel.kt new file mode 100644 index 0000000..f303f50 --- /dev/null +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveReceiptViewModel.kt @@ -0,0 +1,34 @@ +package com.lukouguoji.gjc.viewModel + +import android.content.Intent +import androidx.lifecycle.MutableLiveData +import com.lukouguoji.gjc.R +import com.lukouguoji.gjc.holder.IntExpArriveReceiptViewHolder +import com.lukouguoji.module_base.base.BaseViewModel +import com.lukouguoji.module_base.bean.GjcHaWb +import com.lukouguoji.module_base.common.Constant + +/** + * 国际出港运抵-回执内容 ViewModel(5.5寸手机版专属只读页) + * + * 数据直接复用主列表页已加载的 haWbList.response / arrivalOpDate 字段,无需任何新增请求。 + * 对应设计稿 documents/5.5寸手持端设计文件/04_出港运抵/receipt.html。 + */ +class IntExpArriveReceiptViewModel : BaseViewModel() { + + val receiptList = MutableLiveData>(emptyList()) + + // 适配器配置 + val itemViewHolder = IntExpArriveReceiptViewHolder::class.java + val itemLayoutId = R.layout.item_int_exp_arrive_receipt + + @Suppress("UNCHECKED_CAST") + fun initOnCreated(intent: Intent) { + val list = intent.getSerializableExtra(Constant.Key.DATA) as? ArrayList + // .copy() 补上被 Java 序列化跳过的 @Transient 字段(checked 等),见分单明细页同款注释 + receiptList.value = (list ?: emptyList()) + .map { it.copy() } + .filter { !it.response.isNullOrEmpty() } + .sortedByDescending { it.arrivalOpDate } + } +} diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveSubOrderViewModel.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveSubOrderViewModel.kt new file mode 100644 index 0000000..1c564e6 --- /dev/null +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveSubOrderViewModel.kt @@ -0,0 +1,137 @@ +package com.lukouguoji.gjc.viewModel + +import android.content.Intent +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.lukouguoji.gjc.R +import com.lukouguoji.gjc.dialog.IntExpArriveResetDialogModel +import com.lukouguoji.gjc.holder.IntExpArriveSubOrderViewHolder +import com.lukouguoji.module_base.base.BaseViewModel +import com.lukouguoji.module_base.bean.GjcHaWb +import com.lukouguoji.module_base.common.Constant +import com.lukouguoji.module_base.common.ConstantEvent +import com.lukouguoji.module_base.http.net.NetApply +import com.lukouguoji.module_base.impl.FlowBus +import com.lukouguoji.module_base.ktx.launchLoadingCollect +import com.lukouguoji.module_base.ktx.showToast +import com.lukouguoji.module_base.ktx.toRequestBody +import kotlinx.coroutines.launch + +/** + * 国际出港运抵-分单明细 ViewModel(5.5寸手机版专属页面) + * + * 数据直接复用主列表页已加载的 haWbList(Intent 传入),无需任何新增请求。 + * 筛选仅「分单号」在客户端生效——航班日期/航班号/代理等字段在单个运单的分单集合内 + * 恒为同一值,筛选它们没有实际意义(设计稿疑似沿用主列表筛选组件模板,未按此页场景定制)。 + */ +class IntExpArriveSubOrderViewModel : BaseViewModel() { + + // 运单号(页面标题辅助展示,非必需) + var wbNo: String = "" + private set + + // 完整分单列表(Intent 传入,不随筛选变化) + private var fullList: List = emptyList() + + // 当前展示列表(按分单号筛选后的结果) + val displayList = MutableLiveData>(emptyList()) + + // 筛选:分单号(客户端 contains 匹配) + val hnoFilter = MutableLiveData("") + + // 全选状态与已选计数 + val isAllChecked = MutableLiveData(false) + val selectedCount = MutableLiveData("0") + + // 适配器配置 + val itemViewHolder = IntExpArriveSubOrderViewHolder::class.java + val itemLayoutId = R.layout.item_int_exp_arrive_sub_order + + @Suppress("UNCHECKED_CAST") + fun initOnCreated(intent: Intent) { + wbNo = intent.getStringExtra(KEY_WB_NO).orEmpty() + val list = intent.getSerializableExtra(Constant.Key.DATA) as? ArrayList + // Java 序列化会跳过 checked/actionMenuVisible(标了 @Transient,即 JVM transient 修饰符), + // 反序列化后这两个 ObservableBoolean 字段为 null。.copy() 会重新走一遍类体初始化逻辑, + // 用全新的 ObservableBoolean(false) 补上,避免全选/长按时 NPE(已实机崩溃验证过)。 + fullList = (list ?: emptyList()).map { it.copy() } + displayList.value = fullList + } + + /** + * 分单号筛选(客户端 contains 匹配,非后端查询)。 + */ + fun searchClick() { + val key = hnoFilter.value?.trim().orEmpty() + displayList.value = if (key.isEmpty()) { + fullList + } else { + fullList.filter { it.hno?.contains(key, ignoreCase = true) == true } + } + updateCheckAllStatus() + } + + fun onItemCheckChanged() { + updateCheckAllStatus() + } + + fun updateCheckAllStatus() { + val list = displayList.value ?: return + isAllChecked.value = list.isNotEmpty() && list.all { it.checked.get() } + selectedCount.value = list.count { it.checked.get() }.toString() + } + + fun checkAllClick() { + val list = displayList.value ?: return + val shouldCheckAll = !isAllChecked.value!! + list.forEach { it.checked.set(shouldCheckAll) } + isAllChecked.value = shouldCheckAll + selectedCount.value = if (shouldCheckAll) list.size.toString() else "0" + } + + /** + * 长按菜单「重置」(单条分单)。 + */ + fun resetSingleClick(bean: GjcHaWb) { + val dialog = IntExpArriveResetDialogModel { dialogModel -> + val params = mutableMapOf("haWbList" to listOf(bean)) + if (dialogModel.resetStatusCode != null) { + params["restStatus"] = dialogModel.resetStatusCode + } + launchLoadingCollect({ NetApply.api.resetArriveDeclare(params.toRequestBody()) }) { + onSuccess = { + showToast("状态重置成功,请返回列表页下拉刷新查看最新状态") + viewModelScope.launch { + FlowBus.with(ConstantEvent.EVENT_REFRESH).emit("refresh") + } + } + } + } + dialog.show() + } + + /** + * 底部「申报」:批量申报当前选中的分单。 + */ + fun declareClick() { + val selected = displayList.value?.filter { it.isSelected } ?: emptyList() + if (selected.isEmpty()) { + showToast("请选择要申报的分单") + return + } + val params = mapOf("haWbList" to selected).toRequestBody() + launchLoadingCollect({ NetApply.api.arriveDeclare(params) }) { + onSuccess = { + showToast("运抵申报成功") + viewModelScope.launch { + FlowBus.with(ConstantEvent.EVENT_REFRESH).emit("refresh") + } + getTopActivity().finish() + } + } + } + + companion object { + const val KEY_WB_NO = "wbNo" + } +} diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveSupplementViewModel.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveSupplementViewModel.kt new file mode 100644 index 0000000..eff362a --- /dev/null +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveSupplementViewModel.kt @@ -0,0 +1,119 @@ +package com.lukouguoji.gjc.viewModel + +import android.content.Intent +import androidx.lifecycle.MutableLiveData +import com.lukouguoji.module_base.base.BaseViewModel +import com.lukouguoji.module_base.bean.GjcHaWb +import com.lukouguoji.module_base.common.Constant +import com.lukouguoji.module_base.http.net.NetApply +import com.lukouguoji.module_base.ktx.launchCollect +import com.lukouguoji.module_base.ktx.launchLoadingCollect +import com.lukouguoji.module_base.ktx.showToast +import com.lukouguoji.module_base.ktx.toRequestBody +import com.lukouguoji.module_base.ktx.verifyNullOrEmpty +import dev.utils.app.info.KeyValue + +/** + * 国际出港运抵-补充信息 ViewModel(5.5寸手机版专属新页) + * + * 参照 Pad 端国际进港 [com.lukouguoji.gjj.viewModel.IntArrSupplementInfoViewModel] 的 + * 「单一表单批量复制到选中列表」模式:对该运单的整个 haWbList 批量应用同一份收发货人信息。 + * + * ⚠️ 后端暂无保存接口,见 [com.lukouguoji.module_base.http.net.Api.supplementArriveHaWb] 注释 + * 与 documents/后端接口对接问题清单.xlsx。 + */ +class IntExpArriveSupplementViewModel : BaseViewModel() { + + // 表单编辑用数据对象(取首个分单填充,让用户看到已有数据) + val dataBean = MutableLiveData(GjcHaWb()) + + // 批量应用的目标分单列表 + private var haWbList: List = emptyList() + + // 国家代码下拉列表 + val countryCodeList = MutableLiveData>(emptyList()) + + // 通讯方式下拉列表 + val comTypeList = MutableLiveData>(emptyList()) + + @Suppress("UNCHECKED_CAST") + fun initOnCreated(intent: Intent) { + val list = intent.getSerializableExtra(Constant.Key.DATA) as? ArrayList + // .copy() 补上被 Java 序列化跳过的 @Transient 字段(checked 等),见分单明细页同款注释 + haWbList = (list ?: emptyList()).map { it.copy() } + dataBean.value = haWbList.firstOrNull()?.copy() ?: GjcHaWb() + + loadCountryCodeList() + loadComTypeList() + } + + private fun loadCountryCodeList() { + launchCollect({ NetApply.api.getCountryCodeList() }) { + onSuccess = { result -> + countryCodeList.value = result.data?.mapNotNull { bean -> + if (bean.code != null && bean.name != null) KeyValue(bean.name, bean.code) else null + } ?: emptyList() + } + } + } + + private fun loadComTypeList() { + launchCollect({ NetApply.api.getCommunicateTypeList() }) { + onSuccess = { result -> + comTypeList.value = result.data?.mapNotNull { bean -> + if (bean.code != null && bean.name != null) KeyValue(bean.name, bean.code) else null + } ?: emptyList() + } + } + } + + fun cancel() { + getTopActivity().finish() + } + + /** + * 保存按钮点击 - 将表单信息复制到该运单的所有分单,批量提交。 + */ + fun save() { + val formBean = dataBean.value ?: return + + if (formBean.consigneeName.verifyNullOrEmpty("收货人名称不能为空")) return + if (formBean.consigneeCountryCode.verifyNullOrEmpty("收货人国家代码不能为空")) return + if (formBean.consigneeComType.verifyNullOrEmpty("收货人通讯方式不能为空")) return + if (formBean.consigneePnum.verifyNullOrEmpty("收货人联系号码不能为空")) return + if (formBean.consignorName.verifyNullOrEmpty("发货人名称不能为空")) return + if (formBean.consignorCountryCode.verifyNullOrEmpty("发货人国家代码不能为空")) return + if (formBean.consignorComType.verifyNullOrEmpty("发货人通讯方式不能为空")) return + if (formBean.consignorPnum.verifyNullOrEmpty("发货人联系号码不能为空")) return + + if (haWbList.isEmpty()) { + showToast("无可保存的分单数据") + return + } + + val updatedList = haWbList.map { haWb -> + haWb.copy( + consigneeName = formBean.consigneeName, + consigneeCountryCode = formBean.consigneeCountryCode, + consigneeComType = formBean.consigneeComType, + consigneePnum = formBean.consigneePnum, + consigneeAddress = formBean.consigneeAddress, + consignorName = formBean.consignorName, + consignorCountryCode = formBean.consignorCountryCode, + consignorComType = formBean.consignorComType, + consignorPnum = formBean.consignorPnum, + consignorAddress = formBean.consignorAddress + ) + } + + launchLoadingCollect({ NetApply.api.supplementArriveHaWb(updatedList.toRequestBody()) }) { + onSuccess = { + showToast("保存成功") + getTopActivity().finish() + } + onFailed = { code, msg -> + showToast("保存失败:$msg(后端接口暂未支持,见接口问题清单)") + } + } + } +} diff --git a/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveViewModel.kt b/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveViewModel.kt index 663daf9..4693b2f 100644 --- a/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveViewModel.kt +++ b/module_gjc/src/main/java/com/lukouguoji/gjc/viewModel/IntExpArriveViewModel.kt @@ -20,6 +20,7 @@ import com.lukouguoji.module_base.ktx.launchLoadingCollect import com.lukouguoji.module_base.ktx.showToast import com.lukouguoji.module_base.ktx.toRequestBody import com.lukouguoji.module_base.model.ScanModel +import dev.utils.app.info.KeyValue import dev.utils.common.DateUtils import com.lukouguoji.module_base.ktx.formatDate import kotlinx.coroutines.launch @@ -45,6 +46,7 @@ class IntExpArriveViewModel : BasePageViewModel() { fun onItemCheckChanged() { updateCheckAllStatus() + updateCheckAllStatusPhone() } fun updateCheckAllStatus() { @@ -52,6 +54,101 @@ class IntExpArriveViewModel : BasePageViewModel() { isAllChecked.value = list.isNotEmpty() && list.all { it.checked.get() } } + // ========== 5.5寸手机版专属(平板布局不引用,零影响) ========== + + /** 当前 Tab:"pending"(待运抵) / "shipped"(已运抵)。接口无申报状态查询参数,Tab 由客户端对已加载数据拆分 */ + val currentTab = MutableLiveData("pending") + + /** Tab 配置 */ + val arriveTabs = MutableLiveData( + listOf(KeyValue("待运抵", "pending"), KeyValue("已运抵", "shipped")) + ) + + /** 两个 Tab 的角标计数(当前已加载数据的客户端统计,非全量,见接口问题清单) */ + val arriveTabCounts = MutableLiveData(listOf("", "")) + + /** 手机版全选状态(仅统计待运抵 Tab 内的项,与 Pad 端 [isAllChecked] 统计口径不同) */ + val isAllCheckedPhone = MutableLiveData(false) + + /** 手机版已选数量(底部操作条「已选 N 项」) */ + val selectedCountPhone = MutableLiveData("0") + + /** 手机版筛选弹层「代理」(后端 pageQuery 支持 agentCode 入参,复用既有 agentCode 语义查询) */ + val agentCodePhone = MutableLiveData("") + val agentListPhone = MutableLiveData>(emptyList()) + + private var phoneExtrasEnabled = false + + /** + * 完整已加载列表(跨分页累计,未按 Tab 过滤)。 + * + * ⚠️ RecyclerView/LinearLayoutManager 不会像普通 ViewGroup 那样让 `View.GONE` 的子项 + * 自动塌陷为 0 高度——它仍按子项的测量高度参与滚动区域计算,导致"隐藏"的卡片在列表里 + * 留下一段空白(已实机截图验证)。因此 Tab 切换不能靠给 item 加 visibility 绑定, + * 必须真正地把适配器的数据集替换成过滤后的子集([applyTabFilterPhone])。 + */ + private var allLoadedList: List = emptyList() + + /** + * 开启手机版专属数据:加载「代理」筛选下拉。仅由手机形态的 Activity 调用。 + */ + fun initPhoneExtras() { + phoneExtrasEnabled = true + launchCollect({ NetApply.api.getIntExpAgentList() }) { + onSuccess = { result -> + agentListPhone.postValue( + listOf(KeyValue("全部", "")) + (result.data ?: emptyList()).map { KeyValue(it.name ?: "", it.code ?: "") } + ) + } + } + } + + /** + * 手机版 Tab 切换:将适配器数据集替换为过滤后的子集(不重新请求)。 + * 由 Activity 在 [com.lukouguoji.module_base.ui.weight.phone.PhoneStatusTab] 的 + * 双向绑定写入 currentTab 后调用。 + */ + fun onTabChangedPhone() { + applyTabFilterPhone() + } + + private fun applyTabFilterPhone() { + val isPendingTab = currentTab.value == "pending" + val filtered = allLoadedList.filter { it.isPending == isPendingTab } + pageModel.rv?.commonAdapter()?.refresh(filtered) + updateCheckAllStatusPhone() + } + + /** + * 根据完整已加载列表重新计算 Tab 角标计数(每次数据变化后调用)。 + */ + private fun updatePhoneTabCounts() { + val pending = allLoadedList.count { it.isPending }.toString() + val shipped = allLoadedList.count { !it.isPending }.toString() + arriveTabCounts.value = listOf(pending, shipped) + } + + /** + * 手机版全选(适配器此时已只包含当前 Tab 的子集,待运抵 Tab 下即为全部可选项)。 + */ + fun checkAllClickPhone() { + val list = pageModel.rv?.commonAdapter()?.items as? List ?: return + val shouldCheckAll = !isAllCheckedPhone.value!! + list.forEach { it.checked.set(shouldCheckAll) } + isAllCheckedPhone.value = shouldCheckAll + updateCheckAllStatusPhone() + pageModel.rv?.commonAdapter()?.notifyDataSetChanged() + } + + /** + * 更新手机版全选状态与已选计数(基于适配器当前子集,即当前 Tab 的数据)。 + */ + fun updateCheckAllStatusPhone() { + val list = pageModel.rv?.commonAdapter()?.items as? List ?: return + isAllCheckedPhone.value = list.isNotEmpty() && list.all { it.checked.get() } + selectedCountPhone.value = list.count { it.checked.get() }.toString() + } + // ========== 全局展开状态 ========== /** * 全局展开状态 @@ -292,18 +389,53 @@ class IntExpArriveViewModel : BasePageViewModel() { } } + /** + * 手机版:卡片长按菜单「重置」(单条,已运抵 Tab 专用,对应设计稿长按浮层)。 + */ + fun resetSingleDeclareClick(bean: GjcMaWb) { + bean.actionMenuVisible.set(false) + + val dialog = IntExpArriveResetDialogModel { dialogModel -> + val params = mutableMapOf("maWbList" to listOf(bean)) + if (dialogModel.resetStatusCode != null) { + params["restStatus"] = dialogModel.resetStatusCode + } + launchLoadingCollect({ NetApply.api.resetArriveDeclare(params.toRequestBody()) }) { + onSuccess = { + showToast("状态重置成功") + viewModelScope.launch { + FlowBus.with(ConstantEvent.EVENT_REFRESH).emit("refresh") + } + refresh() + } + } + } + dialog.show() + } + /** * 获取数据 (重写BasePageViewModel) */ override fun getData() { // 构建搜索条件 - val filterParams = mapOf( + val filterParams = mutableMapOf( "fdate" to flightDate.value?.ifEmpty { null }, "fno" to flightNo.value?.ifEmpty { null }, "wbNo" to waybillNo.value?.ifEmpty { null }, "hno" to hno.value?.ifEmpty { null } ) + // 手机版专属条件(平板 phoneExtrasEnabled 恒为 false,请求体与改造前完全一致) + if (phoneExtrasEnabled) { + // 搜索分流(与出港计重页一致):11 位数字精确匹配 wbNo,其余走 likeNo 模糊 + val key = waybillNo.value?.trim() ?: "" + if (key.isNotEmpty() && key.length != 11) { + filterParams["wbNo"] = null + filterParams["likeNo"] = key + } + filterParams["agentCode"] = agentCodePhone.value?.ifEmpty { null } + } + // 列表参数 (含分页) val listParams = (filterParams + mapOf( "pageNum" to pageModel.page, @@ -315,8 +447,21 @@ class IntExpArriveViewModel : BasePageViewModel() { // 获取列表 (带Loading) launchLoadingCollect({ NetApply.api.getIntExpArriveList(listParams) }) { - onSuccess = { - pageModel.handleListBean(it) { updateCheckAllStatus() } + onSuccess = { result -> + // 手机版:自行维护完整累计列表(不依赖 adapter.items 的历史状态,见 allLoadedList 注释) + if (phoneExtrasEnabled) { + val newPageItems = result.list ?: emptyList() + allLoadedList = if (pageModel.page <= 1) newPageItems else allLoadedList + newPageItems + } + + pageModel.handleListBean(result) { + updateCheckAllStatus() + if (phoneExtrasEnabled) { + // 用完整列表强制覆盖 adapter 数据集为当前 Tab 的过滤子集 + applyTabFilterPhone() + updatePhoneTabCounts() + } + } // 数据加载完成后,重置全局展开状态为收起 isAllExpanded.value = false } diff --git a/module_gjc/src/main/res/layout-sw600dp/activity_int_exp_arrive.xml b/module_gjc/src/main/res/layout-sw600dp/activity_int_exp_arrive.xml new file mode 100644 index 0000000..c88ab26 --- /dev/null +++ b/module_gjc/src/main/res/layout-sw600dp/activity_int_exp_arrive.xml @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/module_gjc/src/main/res/layout-sw600dp/item_int_exp_arrive.xml b/module_gjc/src/main/res/layout-sw600dp/item_int_exp_arrive.xml new file mode 100644 index 0000000..1a6b1af --- /dev/null +++ b/module_gjc/src/main/res/layout-sw600dp/item_int_exp_arrive.xml @@ -0,0 +1,520 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/module_gjc/src/main/res/layout/activity_int_exp_arrive.xml b/module_gjc/src/main/res/layout/activity_int_exp_arrive.xml index 8d05466..e6523c7 100644 --- a/module_gjc/src/main/res/layout/activity_int_exp_arrive.xml +++ b/module_gjc/src/main/res/layout/activity_int_exp_arrive.xml @@ -1,239 +1,161 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/module_gjc/src/main/res/layout/activity_int_exp_arrive_receipt.xml b/module_gjc/src/main/res/layout/activity_int_exp_arrive_receipt.xml new file mode 100644 index 0000000..f9d7624 --- /dev/null +++ b/module_gjc/src/main/res/layout/activity_int_exp_arrive_receipt.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + diff --git a/module_gjc/src/main/res/layout/activity_int_exp_arrive_sub_order.xml b/module_gjc/src/main/res/layout/activity_int_exp_arrive_sub_order.xml new file mode 100644 index 0000000..4e4b534 --- /dev/null +++ b/module_gjc/src/main/res/layout/activity_int_exp_arrive_sub_order.xml @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/module_gjc/src/main/res/layout/activity_int_exp_arrive_supplement.xml b/module_gjc/src/main/res/layout/activity_int_exp_arrive_supplement.xml new file mode 100644 index 0000000..32bc29e --- /dev/null +++ b/module_gjc/src/main/res/layout/activity_int_exp_arrive_supplement.xml @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/module_gjc/src/main/res/layout/item_int_exp_arrive.xml b/module_gjc/src/main/res/layout/item_int_exp_arrive.xml index 6b026ed..af4e1f8 100644 --- a/module_gjc/src/main/res/layout/item_int_exp_arrive.xml +++ b/module_gjc/src/main/res/layout/item_int_exp_arrive.xml @@ -1,511 +1,266 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/module_gjc/src/main/res/layout/item_int_exp_arrive_receipt.xml b/module_gjc/src/main/res/layout/item_int_exp_arrive_receipt.xml new file mode 100644 index 0000000..b9b9915 --- /dev/null +++ b/module_gjc/src/main/res/layout/item_int_exp_arrive_receipt.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/module_gjc/src/main/res/layout/item_int_exp_arrive_sub_order.xml b/module_gjc/src/main/res/layout/item_int_exp_arrive_sub_order.xml new file mode 100644 index 0000000..db52c92 --- /dev/null +++ b/module_gjc/src/main/res/layout/item_int_exp_arrive_sub_order.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/module_p/src/main/java/com/sixin/module_p/ui/enter/gj/GjViewModel.kt b/module_p/src/main/java/com/sixin/module_p/ui/enter/gj/GjViewModel.kt index 15e7b15..77a6405 100644 --- a/module_p/src/main/java/com/sixin/module_p/ui/enter/gj/GjViewModel.kt +++ b/module_p/src/main/java/com/sixin/module_p/ui/enter/gj/GjViewModel.kt @@ -54,6 +54,12 @@ class GjViewModel : BaseViewModel() { Constant.AuthName.GjcCheckWeighing, ARouterConstants.ACTIVITY_URL_GJC_WEIGHING_LIST ), + ActionBean( + "出港运抵", + R.drawable.img_gjc_chugang_diyun, + Constant.AuthName.GjcIntExpArrive, + ARouterConstants.ACTIVITY_URL_INT_EXP_ARRIVE + ), // ULD管理属综合管理业务(Pad 端在「综合管理」菜单组),手机端首页暂无综合 Tab,挂在国际 Tab 下 ActionBean( "ULD管理", diff --git a/module_p/src/main/res/drawable-xxhdpi/img_gjc_chugang_diyun.png b/module_p/src/main/res/drawable-xxhdpi/img_gjc_chugang_diyun.png new file mode 100644 index 0000000000000000000000000000000000000000..430eb62ba7d680e5e1b8558ae63fc3af087a8f5f GIT binary patch literal 33013 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 r1*ec0B2BMeRKj{